When you write a Snowpark pandas (Modin) DataFrame back to a table via Session.write_pandas, Snowpark picks one of two upload strategies based on a memory threshold:
- ≤ 3 MB (default PandasToSnowflakeParquetThresholdBytes): switch the frame onto the Snowflake-pushdown backend (SnowflakeQueryCompiler) and INSERT. Works fine.
-
3 MB: skip the round trip and upload a Parquet file directly via the Snowflake connector. Broken. Crashes with AttributeError: 'list' object has no attribute 'upper'.
Root cause is in pandas_to_snowflake (src/snowflake/snowpark/modin/plugin/extensions/utils.py:839): it passes a list[str] to a helper annotated as taking str. Mypy already detects
this but the file is just excluded from the pre-commit mypy whitelist. Full trace, reproducer, and proposed fix below.
- What version of Python are you using?
The bug originates inside a Snowflake stored procedure declared with RUNTIME_VERSION = '3.11'.
End-to-end reproduction and trace done against current main of repo.
Python 3.14.0 (v3.14.0:ebf955df7a8, Oct 7 2025, 08:20:14) [Clang 16.0.0 (clang-1600.0.26.6)]
- What operating system and processor architecture are you using?
macOS-26.3.1-arm64-arm-64bit-Mach-O
Production failure environment: Snowflake-managed compute.
- What are the component versions in the environment (pip freeze)?
The bug runs inside a Snowflake stored procedure so there is no local pip freeze for the failing environment. Stored procedure declaration:
RUNTIME_VERSION = '3.11'
PACKAGES = ('snowflake-snowpark-python', 'modin')
- What did you do?
import modin.pandas as pd
import snowflake.snowpark.modin.plugin # noqa: F401
from snowflake.snowpark import Session
from snowflake.snowpark.modin.config.envvars import (
PandasToSnowflakeParquetThresholdBytes,
)
session = Session.builder.create()
# Force the Parquet path. In production this triggers naturally once the
# DataFrame's shallow memory usage exceeds 3,000,000 bytes (the default
# value of PandasToSnowflakeParquetThresholdBytes).
PandasToSnowflakeParquetThresholdBytes.put(0)
df = pd.DataFrame({"a": [1, 2, 3], "b": ["x", "y", "z"]})
session.write_pandas(df, "TEST_TABLE", auto_create_table=True, overwrite=True)
- What did you expect to see?
Expected
DataFrame written to TEST_TABLE.
Actual
AttributeError: 'list' object has no attribute 'upper'
File "snowflake/snowpark/modin/plugin/extensions/utils.py", line 727,
in _convert_to_snowflake_table_name_to_write_pandas_table_name
return name.upper()
Full call trace
Layer 1
Session.write_pandas (src/snowflake/snowpark/session.py:3384). Dispatcher at line 3554-3557:
modin_pandas, modin_is_imported = import_or_missing_modin_pandas()
if modin_is_imported and isinstance(df, (modin_pandas.DataFrame, modin_pandas.Series)):
self._write_modin_pandas_helper(df, table_name, location,
database=database, schema=schema, ...)
Modin DataFrame → enters the Modin path.
Layer 2
Session._write_modin_pandas_helper (session.py:3307). Builds a structured fully-qualified name at line 3363-3367:
name = [table_name] # ['TEST_TABLE']
if schema:
name = [quote_id(schema)] + name # ['"PUBLIC"', 'TEST_TABLE']
if database:
name = [quote_id(database)] + name # ['"DB"', '"PUBLIC"', 'TEST_TABLE']
Then at line 3375:
df.to_snowflake(name=name, ...) # passes the list
Layer 3
to_snowflake dispatch. Local-pandas-backend DataFrames route to pandas_to_snowflake, registered at
src/snowflake/snowpark/modin/plugin/extensions/dataframe_extensions.py:81:
register_dataframe_accessor("to_snowflake", backend="Pandas")(pandas_to_snowflake)
Layer 4
pandas_to_snowflake (src/snowflake/snowpark/modin/plugin/extensions/utils.py:730). Signature: name: str | Iterable[str]. Threshold gate at line 759-779:
if memory_usage <= PandasToSnowflakeParquetThresholdBytes.get():
return self.set_backend("Snowflake").to_snowflake(name=name, ...) # PATH A handles list correctly
# PATH B (Parquet upload) is the buggy one
Above threshold → falls through to line 839:
pd.session.write_pandas(
...,
table_name=_convert_to_snowflake_table_name_to_write_pandas_table_name(name), # Bug will happen here
auto_create_table=True,
overwrite=if_exists != "append",
table_type=table_type,
)
The full name list is passed where a str is expected.
Layer 5
_convert_to_snowflake_table_name_to_write_pandas_table_name (utils.py:707-727). Signature: name: str:
def _convert_to_snowflake_table_name_to_write_pandas_table_name(name: str) -> str:
if is_valid_snowflake_quoted_identifier(name):
return unquote_name_if_quoted(name)
else:
return name.upper() # 'list' object has no attribute 'upper'
is_valid_snowflake_quoted_identifier(['TEST_TABLE']): len = 1 < 2 → False → falls to name.upper() → AttributeError.
Type system already detects this
Running mypy directly on the file:
src/snowflake/snowpark/modin/plugin/extensions/utils.py:839: error:
Argument 1 to "_convert_to_snowflake_table_name_to_write_pandas_table_name"
has incompatible type "str | Iterable[str]"; expected "str" [arg-type]
Proposed fix
In pandas_to_snowflake, normalize name to a list of parts, extract table / schema / database individually, pass each as a separate keyword argument to write_pandas (which already
accepts database= and schema=):
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(
...,
table_name=table_name_converted,
database=database_converted,
schema=schema_converted,
...
)
- Can you set logging to DEBUG and collect the logs?
2026-05-18 14:14:10,496 - MainThread utils.py:1014 - enabled() - INFO - AST state has not been set explicitly. Defaulting to ast_enabled = True.
UserWarning: Snowpark pandas now runs with hybrid execution enabled by default, and will perform certain operations on smaller data using local, in-memory pandas. To disable this
behavior and force all computations to occur in Snowflake, run this line:
from modin.config import AutoSwitchBackend; AutoSwitchBackend.disable()
backend: NativeQueryCompiler
memory_usage: 1732 bytes
threshold forced to: 0
Traceback (most recent call last):
File "/Users/jonathanstefanov/Dev/snowpark-python/repro_with_logs.py", line 43, in
pandas_to_snowflake(df, name=["STIM_BLOCKS"], if_exists="replace", index=False)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "src/snowflake/snowpark/modin/plugin/extensions/utils.py", line 839, in pandas_to_snowflake
table_name=_convert_to_snowflake_table_name_to_write_pandas_table_name(name),
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "src/snowflake/snowpark/modin/plugin/extensions/utils.py", line 727, in _convert_to_snowflake_table_name_to_write_pandas_table_name
return name.upper()
^^^^^^^^^^
AttributeError: 'list' object has no attribute 'upper'
When you write a Snowpark pandas (Modin) DataFrame back to a table via Session.write_pandas, Snowpark picks one of two upload strategies based on a memory threshold:
Root cause is in pandas_to_snowflake (src/snowflake/snowpark/modin/plugin/extensions/utils.py:839): it passes a list[str] to a helper annotated as taking str. Mypy already detects
this but the file is just excluded from the pre-commit mypy whitelist. Full trace, reproducer, and proposed fix below.
The bug originates inside a Snowflake stored procedure declared with RUNTIME_VERSION = '3.11'.
End-to-end reproduction and trace done against current main of repo.
Python 3.14.0 (v3.14.0:ebf955df7a8, Oct 7 2025, 08:20:14) [Clang 16.0.0 (clang-1600.0.26.6)]
macOS-26.3.1-arm64-arm-64bit-Mach-O
Production failure environment: Snowflake-managed compute.
The bug runs inside a Snowflake stored procedure so there is no local pip freeze for the failing environment. Stored procedure declaration:
RUNTIME_VERSION = '3.11'
PACKAGES = ('snowflake-snowpark-python', 'modin')
Expected
DataFrame written to TEST_TABLE.
Actual
Full call trace
Layer 1
Session.write_pandas (src/snowflake/snowpark/session.py:3384). Dispatcher at line 3554-3557:
Modin DataFrame → enters the Modin path.
Layer 2
Session._write_modin_pandas_helper (session.py:3307). Builds a structured fully-qualified name at line 3363-3367:
Then at line 3375:
df.to_snowflake(name=name, ...) # passes the listLayer 3
to_snowflake dispatch. Local-pandas-backend DataFrames route to pandas_to_snowflake, registered at
src/snowflake/snowpark/modin/plugin/extensions/dataframe_extensions.py:81:
register_dataframe_accessor("to_snowflake", backend="Pandas")(pandas_to_snowflake)Layer 4
pandas_to_snowflake (src/snowflake/snowpark/modin/plugin/extensions/utils.py:730). Signature: name: str | Iterable[str]. Threshold gate at line 759-779:
The full name list is passed where a str is expected.
Layer 5
_convert_to_snowflake_table_name_to_write_pandas_table_name (utils.py:707-727). Signature: name: str:
is_valid_snowflake_quoted_identifier(['TEST_TABLE']): len = 1 < 2 → False → falls to name.upper() → AttributeError.
Type system already detects this
Running mypy directly on the file:
Proposed fix
In pandas_to_snowflake, normalize name to a list of parts, extract table / schema / database individually, pass each as a separate keyword argument to write_pandas (which already
accepts database= and schema=):
2026-05-18 14:14:10,496 - MainThread utils.py:1014 - enabled() - INFO - AST state has not been set explicitly. Defaulting to ast_enabled = True.
UserWarning: Snowpark pandas now runs with hybrid execution enabled by default, and will perform certain operations on smaller data using local, in-memory pandas. To disable this
behavior and force all computations to occur in Snowflake, run this line:
from modin.config import AutoSwitchBackend; AutoSwitchBackend.disable()
backend: NativeQueryCompiler
memory_usage: 1732 bytes
threshold forced to: 0
Traceback (most recent call last):
File "/Users/jonathanstefanov/Dev/snowpark-python/repro_with_logs.py", line 43, in
pandas_to_snowflake(df, name=["STIM_BLOCKS"], if_exists="replace", index=False)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "src/snowflake/snowpark/modin/plugin/extensions/utils.py", line 839, in pandas_to_snowflake
table_name=_convert_to_snowflake_table_name_to_write_pandas_table_name(name),
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "src/snowflake/snowpark/modin/plugin/extensions/utils.py", line 727, in _convert_to_snowflake_table_name_to_write_pandas_table_name
return name.upper()
^^^^^^^^^^
AttributeError: 'list' object has no attribute 'upper'