Skip to content

Commit feb7f07

Browse files
committed
SNOW-2333702 Fix types of fetchone, fetchmany, fetchall
1 parent 84fb3ed commit feb7f07

File tree

2 files changed

+49
-27
lines changed

2 files changed

+49
-27
lines changed

DESCRIPTION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
1616
- Added support for intermediate certificates as roots when they are stored in the trust store
1717
- Bumped up vendored `urllib3` to `2.5.0` and `requests` to `v2.32.5`
1818
- Dropped support for OpenSSL versions older than 1.1.1
19+
- Constrained the types of `fetchone`, `fetchmany`, `fetchall`
20+
- As part of this fix, `DictCursor` is no longer a subclass of `SnowflakeCursor`; use `SnowflakeCursorBase` as a superclass of both.
1921

2022
- v3.17.3(September 02,2025)
2123
- Enhanced configuration file permission warning messages.

src/snowflake/connector/cursor.py

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22
from __future__ import annotations
33

4+
import abc
45
import collections
56
import logging
67
import os
@@ -19,6 +20,7 @@
1920
TYPE_CHECKING,
2021
Any,
2122
Callable,
23+
Generic,
2224
Iterator,
2325
Literal,
2426
NamedTuple,
@@ -86,6 +88,7 @@
8688
from .result_batch import ResultBatch
8789

8890
T = TypeVar("T", bound=collections.abc.Sequence)
91+
FetchRow = TypeVar("FetchRow", bound=tuple[Any, ...] | dict[str, Any])
8992

9093
logger = getLogger(__name__)
9194

@@ -332,29 +335,7 @@ class ResultState(Enum):
332335
RESET = 3
333336

334337

335-
class SnowflakeCursor:
336-
"""Implementation of Cursor object that is returned from Connection.cursor() method.
337-
338-
Attributes:
339-
description: A list of namedtuples about metadata for all columns.
340-
rowcount: The number of records updated or selected. If not clear, -1 is returned.
341-
rownumber: The current 0-based index of the cursor in the result set or None if the index cannot be
342-
determined.
343-
sfqid: Snowflake query id in UUID form. Include this in the problem report to the customer support.
344-
sqlstate: Snowflake SQL State code.
345-
timestamp_output_format: Snowflake timestamp_output_format for timestamps.
346-
timestamp_ltz_output_format: Snowflake output format for LTZ timestamps.
347-
timestamp_tz_output_format: Snowflake output format for TZ timestamps.
348-
timestamp_ntz_output_format: Snowflake output format for NTZ timestamps.
349-
date_output_format: Snowflake output format for dates.
350-
time_output_format: Snowflake output format for times.
351-
timezone: Snowflake timezone.
352-
binary_output_format: Snowflake output format for binary fields.
353-
arraysize: The default number of rows fetched by fetchmany.
354-
connection: The connection object by which the cursor was created.
355-
errorhandle: The class that handles error handling.
356-
is_file_transfer: Whether, or not the current command is a put, or get.
357-
"""
338+
class SnowflakeCursorBase(abc.ABC, Generic[FetchRow]):
358339

359340
# TODO:
360341
# Most of these attributes have no reason to be properties, we could just store them in public variables.
@@ -1514,7 +1495,11 @@ def executemany(
15141495

15151496
return self
15161497

1517-
def fetchone(self) -> dict | tuple | None:
1498+
@abc.abstractmethod
1499+
def fetchone(self) -> FetchRow:
1500+
pass
1501+
1502+
def _fetchone(self) -> dict[str, Any] | tuple[Any, ...] | None:
15181503
"""Fetches one row."""
15191504
if self._prefetch_hook is not None:
15201505
self._prefetch_hook()
@@ -1539,7 +1524,7 @@ def fetchone(self) -> dict | tuple | None:
15391524
else:
15401525
return None
15411526

1542-
def fetchmany(self, size: int | None = None) -> list[tuple] | list[dict]:
1527+
def fetchmany(self, size: int | None = None) -> list[FetchRow]:
15431528
"""Fetches the number of specified rows."""
15441529
if size is None:
15451530
size = self.arraysize
@@ -1565,7 +1550,7 @@ def fetchmany(self, size: int | None = None) -> list[tuple] | list[dict]:
15651550

15661551
return ret
15671552

1568-
def fetchall(self) -> list[tuple] | list[dict]:
1553+
def fetchall(self) -> list[FetchRow]:
15691554
"""Fetches all of the results."""
15701555
ret = []
15711556
while True:
@@ -1925,7 +1910,37 @@ def _create_file_transfer_agent(
19251910
)
19261911

19271912

1928-
class DictCursor(SnowflakeCursor):
1913+
class SnowflakeCursor(SnowflakeCursorBase[tuple[Any, ...]]):
1914+
"""Implementation of Cursor object that is returned from Connection.cursor() method.
1915+
1916+
Attributes:
1917+
description: A list of namedtuples about metadata for all columns.
1918+
rowcount: The number of records updated or selected. If not clear, -1 is returned.
1919+
rownumber: The current 0-based index of the cursor in the result set or None if the index cannot be
1920+
determined.
1921+
sfqid: Snowflake query id in UUID form. Include this in the problem report to the customer support.
1922+
sqlstate: Snowflake SQL State code.
1923+
timestamp_output_format: Snowflake timestamp_output_format for timestamps.
1924+
timestamp_ltz_output_format: Snowflake output format for LTZ timestamps.
1925+
timestamp_tz_output_format: Snowflake output format for TZ timestamps.
1926+
timestamp_ntz_output_format: Snowflake output format for NTZ timestamps.
1927+
date_output_format: Snowflake output format for dates.
1928+
time_output_format: Snowflake output format for times.
1929+
timezone: Snowflake timezone.
1930+
binary_output_format: Snowflake output format for binary fields.
1931+
arraysize: The default number of rows fetched by fetchmany.
1932+
connection: The connection object by which the cursor was created.
1933+
errorhandle: The class that handles error handling.
1934+
is_file_transfer: Whether, or not the current command is a put, or get.
1935+
"""
1936+
1937+
def fetchone(self) -> tuple[Any, ...] | None:
1938+
row = self._fetchone()
1939+
assert row is None or isinstance(row, tuple)
1940+
return row
1941+
1942+
1943+
class DictCursor(SnowflakeCursorBase[dict[str, Any]]):
19291944
"""Cursor returning results in a dictionary."""
19301945

19311946
def __init__(self, connection) -> None:
@@ -1934,6 +1949,11 @@ def __init__(self, connection) -> None:
19341949
use_dict_result=True,
19351950
)
19361951

1952+
def fetchone(self) -> dict[str, Any] | None:
1953+
row = self._fetchone()
1954+
assert row is None or isinstance(row, dict)
1955+
return row
1956+
19371957

19381958
def __getattr__(name):
19391959
if name == "NanoarrowUsage":

0 commit comments

Comments
 (0)