From 33ececc34f8a775584bb1b68b084136477ec3967 Mon Sep 17 00:00:00 2001 From: Jonathan Stefanov Date: Fri, 22 May 2026 11:07:18 +0200 Subject: [PATCH 1/4] SNOW-3556267: Fix AttributeError when write_pandas receives list name on Parquet path --- .../snowpark/modin/plugin/extensions/utils.py | 19 ++++- tests/integ/modin/frame/test_to_snowflake.py | 70 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/snowflake/snowpark/modin/plugin/extensions/utils.py b/src/snowflake/snowpark/modin/plugin/extensions/utils.py index 86d702a90a..d05d818110 100644 --- a/src/snowflake/snowpark/modin/plugin/extensions/utils.py +++ b/src/snowflake/snowpark/modin/plugin/extensions/utils.py @@ -823,6 +823,21 @@ def pandas_to_snowflake( "to_snowflake", ", ".join(type(t).__name__ for t in unsupported_types) ) + name_parts = [name] if isinstance(name, str) else list(name) + table_name_converted = _convert_to_snowflake_table_name_to_write_pandas_table_name( + name_parts[-1] + ) + schema_converted = ( + _convert_to_snowflake_table_name_to_write_pandas_table_name(name_parts[-2]) + if len(name_parts) >= 2 + else None + ) + database_converted = ( + _convert_to_snowflake_table_name_to_write_pandas_table_name(name_parts[0]) + if len(name_parts) >= 3 + else None + ) + pd.session.write_pandas( # use set_axis() this way so that we can also flatten the tuple column # labels of a column multi-index, e.g. if `pandas_frame` has columns @@ -836,7 +851,9 @@ def pandas_to_snowflake( # column identifiers ourselves, we get the correct column names and we # don't have to modify the table name, but the snowflake connector seems # to incorrectly insert null data. - table_name=_convert_to_snowflake_table_name_to_write_pandas_table_name(name), + table_name=table_name_converted, + database=database_converted, + schema=schema_converted, auto_create_table=True, overwrite=if_exists != "append", table_type=table_type, diff --git a/tests/integ/modin/frame/test_to_snowflake.py b/tests/integ/modin/frame/test_to_snowflake.py index 569d323325..6514a4c80b 100644 --- a/tests/integ/modin/frame/test_to_snowflake.py +++ b/tests/integ/modin/frame/test_to_snowflake.py @@ -8,6 +8,7 @@ import modin.pandas as pd import pandas as native_pd import pytest +from modin.config import context as config_context import snowflake.snowpark.modin.plugin # noqa: F401 from tests.integ.modin.utils import ( @@ -386,3 +387,72 @@ def test_special_chars_unquoted(self, valid_unquoted_identifier_table_name): assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( written, native_df.rename(str, axis=1) ) + + +class TestListNameParquetPath: + @pytest.fixture(autouse=True) + def use_starting_backend_and_parquet_threshold(self): + with config_context(Backend="Pandas", PandasToSnowflakeParquetThresholdBytes=0): + yield + + def test_one_part_list(self, test_table_name): + native_df = native_pd.DataFrame({"a": [1]}) + df = pd.DataFrame(native_df) + if_exists = "replace" + with to_snowflake_counter(dataset=df, if_exists=if_exists): + df.to_snowflake([test_table_name], if_exists=if_exists, index=False) + written = pd.read_snowflake(test_table_name) + assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( + written, native_df.rename(str, axis=1) + ) + + def test_two_part_list(self, session, test_table_name): + native_df = native_pd.DataFrame({"a": [1]}) + df = pd.DataFrame(native_df) + if_exists = "replace" + schema = session.get_current_schema().strip('"') + with to_snowflake_counter(dataset=df, if_exists=if_exists): + df.to_snowflake([schema, test_table_name], if_exists=if_exists, index=False) + written = pd.read_snowflake(test_table_name) + assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( + written, native_df.rename(str, axis=1) + ) + + def test_three_part_list(self, session, test_table_name): + native_df = native_pd.DataFrame({"a": [1]}) + df = pd.DataFrame(native_df) + if_exists = "replace" + database = session.get_current_database().strip('"') + schema = session.get_current_schema().strip('"') + with to_snowflake_counter(dataset=df, if_exists=if_exists): + df.to_snowflake( + [database, schema, test_table_name], if_exists=if_exists, index=False + ) + written = pd.read_snowflake(test_table_name) + assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( + written, native_df.rename(str, axis=1) + ) + + def test_quoted_identifier_list(self, test_table_name): + native_df = native_pd.DataFrame({"a": [1]}) + df = pd.DataFrame(native_df) + if_exists = "replace" + with to_snowflake_counter(dataset=df, if_exists=if_exists): + df.to_snowflake( + [f'"{test_table_name}"'], if_exists=if_exists, index=False + ) + written = pd.read_snowflake(test_table_name) + assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( + written, native_df.rename(str, axis=1) + ) + + def test_str_name_parquet_path(self, test_table_name): + native_df = native_pd.DataFrame({"a": [1]}) + df = pd.DataFrame(native_df) + if_exists = "replace" + with to_snowflake_counter(dataset=df, if_exists=if_exists): + df.to_snowflake(test_table_name, if_exists=if_exists, index=False) + written = pd.read_snowflake(test_table_name) + assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( + written, native_df.rename(str, axis=1) + ) From df10af89a99e9758b24eb53f588332e05656498f Mon Sep 17 00:00:00 2001 From: Jonathan Stefanov Date: Fri, 22 May 2026 12:17:07 +0200 Subject: [PATCH 2/4] SNOW-3556267: Raise ValueError for name lists with more than 3 parts --- .../snowpark/modin/plugin/extensions/utils.py | 4 ++++ tests/integ/modin/frame/test_to_snowflake.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/snowflake/snowpark/modin/plugin/extensions/utils.py b/src/snowflake/snowpark/modin/plugin/extensions/utils.py index d05d818110..2b18707893 100644 --- a/src/snowflake/snowpark/modin/plugin/extensions/utils.py +++ b/src/snowflake/snowpark/modin/plugin/extensions/utils.py @@ -824,6 +824,10 @@ def pandas_to_snowflake( ) name_parts = [name] if isinstance(name, str) else list(name) + if len(name_parts) > 3: + raise ValueError( + f"name must have at most 3 parts (database, schema, table), got {len(name_parts)}: {name_parts}" + ) table_name_converted = _convert_to_snowflake_table_name_to_write_pandas_table_name( name_parts[-1] ) diff --git a/tests/integ/modin/frame/test_to_snowflake.py b/tests/integ/modin/frame/test_to_snowflake.py index 6514a4c80b..7e6af804c0 100644 --- a/tests/integ/modin/frame/test_to_snowflake.py +++ b/tests/integ/modin/frame/test_to_snowflake.py @@ -446,6 +446,17 @@ def test_quoted_identifier_list(self, test_table_name): written, native_df.rename(str, axis=1) ) + def test_too_many_parts_raises(self, test_table_name): + native_df = native_pd.DataFrame({"a": [1]}) + df = pd.DataFrame(native_df) + with SqlCounter(query_count=0): + with pytest.raises(ValueError, match="at most 3 parts"): + df.to_snowflake( + ["extra", "db", "schema", test_table_name], + if_exists="replace", + index=False, + ) + def test_str_name_parquet_path(self, test_table_name): native_df = native_pd.DataFrame({"a": [1]}) df = pd.DataFrame(native_df) From bcfe3aa7a82875a280aa2eb8331f8f0d2368746b Mon Sep 17 00:00:00 2001 From: Jonathan Stefanov Date: Fri, 22 May 2026 12:57:46 +0200 Subject: [PATCH 3/4] SNOW-3556267: Changed tests to use write_pandas --- tests/integ/modin/frame/test_to_snowflake.py | 58 +++++++------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/tests/integ/modin/frame/test_to_snowflake.py b/tests/integ/modin/frame/test_to_snowflake.py index 7e6af804c0..d95301f597 100644 --- a/tests/integ/modin/frame/test_to_snowflake.py +++ b/tests/integ/modin/frame/test_to_snowflake.py @@ -389,57 +389,50 @@ def test_special_chars_unquoted(self, valid_unquoted_identifier_table_name): ) -class TestListNameParquetPath: +class TestWritePandasParquetPath: @pytest.fixture(autouse=True) def use_starting_backend_and_parquet_threshold(self): with config_context(Backend="Pandas", PandasToSnowflakeParquetThresholdBytes=0): yield - def test_one_part_list(self, test_table_name): + def test_table_name_only(self, session, test_table_name): native_df = native_pd.DataFrame({"a": [1]}) df = pd.DataFrame(native_df) - if_exists = "replace" - with to_snowflake_counter(dataset=df, if_exists=if_exists): - df.to_snowflake([test_table_name], if_exists=if_exists, index=False) + with to_snowflake_counter(dataset=df, if_exists="replace"): + session.write_pandas( + df, test_table_name, auto_create_table=True, overwrite=True + ) written = pd.read_snowflake(test_table_name) assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( written, native_df.rename(str, axis=1) ) - def test_two_part_list(self, session, test_table_name): + def test_table_name_with_schema(self, session, test_table_name): native_df = native_pd.DataFrame({"a": [1]}) df = pd.DataFrame(native_df) - if_exists = "replace" schema = session.get_current_schema().strip('"') - with to_snowflake_counter(dataset=df, if_exists=if_exists): - df.to_snowflake([schema, test_table_name], if_exists=if_exists, index=False) + with to_snowflake_counter(dataset=df, if_exists="replace"): + session.write_pandas( + df, test_table_name, schema=schema, auto_create_table=True, overwrite=True + ) written = pd.read_snowflake(test_table_name) assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( written, native_df.rename(str, axis=1) ) - def test_three_part_list(self, session, test_table_name): + def test_table_name_with_database_and_schema(self, session, test_table_name): native_df = native_pd.DataFrame({"a": [1]}) df = pd.DataFrame(native_df) - if_exists = "replace" database = session.get_current_database().strip('"') schema = session.get_current_schema().strip('"') - with to_snowflake_counter(dataset=df, if_exists=if_exists): - df.to_snowflake( - [database, schema, test_table_name], if_exists=if_exists, index=False - ) - written = pd.read_snowflake(test_table_name) - assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( - written, native_df.rename(str, axis=1) - ) - - def test_quoted_identifier_list(self, test_table_name): - native_df = native_pd.DataFrame({"a": [1]}) - df = pd.DataFrame(native_df) - if_exists = "replace" - with to_snowflake_counter(dataset=df, if_exists=if_exists): - df.to_snowflake( - [f'"{test_table_name}"'], if_exists=if_exists, index=False + with to_snowflake_counter(dataset=df, if_exists="replace"): + session.write_pandas( + df, + test_table_name, + database=database, + schema=schema, + auto_create_table=True, + overwrite=True, ) written = pd.read_snowflake(test_table_name) assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( @@ -456,14 +449,3 @@ def test_too_many_parts_raises(self, test_table_name): if_exists="replace", index=False, ) - - def test_str_name_parquet_path(self, test_table_name): - native_df = native_pd.DataFrame({"a": [1]}) - df = pd.DataFrame(native_df) - if_exists = "replace" - with to_snowflake_counter(dataset=df, if_exists=if_exists): - df.to_snowflake(test_table_name, if_exists=if_exists, index=False) - written = pd.read_snowflake(test_table_name) - assert_snowpark_pandas_equals_to_pandas_without_dtypecheck( - written, native_df.rename(str, axis=1) - ) From b78487bebb28532aefcd3bddf53c112b2dca90e8 Mon Sep 17 00:00:00 2001 From: Jonathan Stefanov Date: Thu, 28 May 2026 10:45:04 +0200 Subject: [PATCH 4/4] SNOW-3556267: Changed superior to equal on name part length of db --- src/snowflake/snowpark/modin/plugin/extensions/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snowflake/snowpark/modin/plugin/extensions/utils.py b/src/snowflake/snowpark/modin/plugin/extensions/utils.py index 2b18707893..57cc78b03b 100644 --- a/src/snowflake/snowpark/modin/plugin/extensions/utils.py +++ b/src/snowflake/snowpark/modin/plugin/extensions/utils.py @@ -838,7 +838,7 @@ def pandas_to_snowflake( ) database_converted = ( _convert_to_snowflake_table_name_to_write_pandas_table_name(name_parts[0]) - if len(name_parts) >= 3 + if len(name_parts) == 3 else None )