From c3d30cd544f33b38500a360afe0b4c0187284e16 Mon Sep 17 00:00:00 2001 From: Afroz Alam Date: Fri, 26 Jul 2024 22:57:22 +0000 Subject: [PATCH] SNOW-1331032 Add function python_value_str_to_object (#1954) --- .../snowpark/_internal/type_utils.py | 48 +++++++++++ tests/unit/test_types.py | 81 +++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/src/snowflake/snowpark/_internal/type_utils.py b/src/snowflake/snowpark/_internal/type_utils.py index 88a5431f5d2..428c1308bad 100644 --- a/src/snowflake/snowpark/_internal/type_utils.py +++ b/src/snowflake/snowpark/_internal/type_utils.py @@ -66,6 +66,8 @@ Variant, VariantType, VectorType, + _FractionalType, + _IntegralType, _NumericType, ) @@ -525,6 +527,52 @@ def merge_type(a: DataType, b: DataType, name: Optional[str] = None) -> DataType return a +def python_value_str_to_object(value, tp: DataType) -> Any: + if isinstance(tp, StringType): + return value + + if isinstance( + tp, + ( + _IntegralType, + _FractionalType, + BooleanType, + BinaryType, + TimeType, + DateType, + TimestampType, + ), + ): + return eval(value) + + if isinstance(tp, ArrayType): + curr_list = eval(value) + if curr_list is None: + return None + element_tp = tp.element_type or StringType() + return [python_value_str_to_object(val, element_tp) for val in curr_list] + + if isinstance(tp, MapType): + curr_dict: dict = eval(value) + if curr_dict is None: + return None + key_tp = tp.key_type or StringType() + val_tp = tp.value_type or StringType() + return { + python_value_str_to_object(k, key_tp): python_value_str_to_object(v, val_tp) + for k, v in curr_dict.items() + } + + if isinstance(tp, (GeometryType, GeographyType, VariantType)): + if value.strip() == "None": + return None + return value + + raise TypeError( + f"Unsupported data type: {tp}, value {value} by python_value_str_to_object()" + ) + + def python_type_str_to_object( tp_str: str, is_return_type_for_sproc: bool = False ) -> Type: diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 0f1b44a8daf..6e756e1b6cb 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -3,6 +3,7 @@ # Copyright (c) 2012-2024 Snowflake Computing Inc. All rights reserved. # +import decimal import os import sys import typing @@ -38,6 +39,7 @@ infer_type, merge_type, python_type_to_snow_type, + python_value_str_to_object, retrieve_func_type_hints_from_source, snow_type_to_dtype_str, ) @@ -584,6 +586,85 @@ def test_decimal_regular_expression(decimal_word): assert get_number_precision_scale(f" {decimal_word} ( 2 , 1 ) ") == (2, 1) +@pytest.mark.parametrize( + "value_str,datatype,expected_value", + [ + ("1", IntegerType(), 1), + ("True", BooleanType(), True), + ("1.0", FloatType(), 1.0), + ("decimal.Decimal('3.14')", DecimalType(), decimal.Decimal("3.14")), + ("decimal.Decimal(1.0)", DecimalType(), decimal.Decimal(1.0)), + ("one", StringType(), "one"), + (None, StringType(), None), + ("None", StringType(), "None"), + ("POINT(-122.35 37.55)", GeographyType(), "POINT(-122.35 37.55)"), + ("POINT(-122.35 37.55)", GeometryType(), "POINT(-122.35 37.55)"), + ('{"key": "val"}', VariantType(), '{"key": "val"}'), + ("b'one'", BinaryType(), b"one"), + ("bytearray('one', 'utf-8')", BinaryType(), bytearray("one", "utf-8")), + ("datetime.date(2024, 4, 1)", DateType(), date(2024, 4, 1)), + ( + "datetime.time(12, 0, second=20, tzinfo=datetime.timezone.utc)", + TimeType(), + time(12, 0, second=20, tzinfo=timezone.utc), + ), + ( + "datetime.datetime(2024, 4, 1, 12, 0, 20)", + TimestampType(), + datetime(2024, 4, 1, 12, 0, 20), + ), + ("['1', '2', '3']", ArrayType(IntegerType()), [1, 2, 3]), + ("['a', 'b', 'c']", ArrayType(StringType()), ["a", "b", "c"]), + ("['a', 'b', 'c']", ArrayType(), ["a", "b", "c"]), + ( + "[\"['1', '2', '3']\", \"['4', '5', '6']\"]", + ArrayType(ArrayType(IntegerType())), + [[1, 2, 3], [4, 5, 6]], + ), + ("{'1': 'a'}", MapType(), {"1": "a"}), + ("{'1': 'a'}", MapType(IntegerType(), StringType()), {1: "a"}), + ( + "{'1': \"['a', 'b']\"}", + MapType(IntegerType(), ArrayType(StringType())), + {1: ["a", "b"]}, + ), + ], +) +def test_python_value_str_to_object(value_str, datatype, expected_value): + assert python_value_str_to_object(value_str, datatype) == expected_value + + +@pytest.mark.parametrize( + "datatype", + [ + IntegerType(), + BooleanType(), + FloatType(), + DecimalType(), + BinaryType(), + DateType(), + TimeType(), + TimestampType(), + ArrayType(), + MapType(), + VariantType(), + GeographyType(), + GeometryType(), + ], +) +def test_python_value_str_to_object_for_none(datatype): + "StringType() is excluded here and tested in test_python_value_str_to_object" + assert python_value_str_to_object("None", datatype) is None + + +def test_python_value_str_to_object_negative(): + with pytest.raises( + TypeError, + match="Unsupported data type: invalid type, value thanksgiving by python_value_str_to_object()", + ): + python_value_str_to_object("thanksgiving", "invalid type") + + def test_retrieve_func_type_hints_from_source(): func_name = "foo"