From aa8d8be4f40b75119ff46190d8557d6cfbb62dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 28 Oct 2025 15:53:32 +0100 Subject: [PATCH 01/25] [async] Designed and implemented async connection context manager with wrapping. --- src/snowflake/connector/aio/__init__.py | 39 ++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index 0b0410ebaa..be78e88ff4 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Any + from ._connection import SnowflakeConnection from ._cursor import DictCursor, SnowflakeCursor @@ -10,7 +12,36 @@ ] -async def connect(**kwargs) -> SnowflakeConnection: - conn = SnowflakeConnection(**kwargs) - await conn.connect() - return conn +class _AsyncConnectWrapper: + """Wrapper that preserves metadata of SnowflakeConnection.__init__ while providing async connect behavior. + + This class makes the async connect function metadata-compatible with the synchronous Connect function, + allowing introspection tools to see the same signature as SnowflakeConnection.__init__. + """ + + def __init__(self): + # Copy metadata from SnowflakeConnection.__init__ to this instance + # This allows introspection tools to see the proper signature + self.__wrapped__ = SnowflakeConnection.__init__ + # Standard functools.wraps attributes + self.__name__ = "connect" + self.__doc__ = SnowflakeConnection.__init__.__doc__ + self.__module__ = __name__ + self.__qualname__ = "connect" + self.__annotations__ = getattr( + SnowflakeConnection.__init__, "__annotations__", {} + ) + + async def __call__(self, **kwargs: Any) -> SnowflakeConnection: + """Create and connect to a Snowflake connection asynchronously. + + This async function creates a SnowflakeConnection instance and establishes + the connection, replicating the behavior of the synchronous snowflake.connector.connect. + """ + conn = SnowflakeConnection(**kwargs) + await conn.connect() + return conn + + +# Create the async connect function with preserved metadata +connect = _AsyncConnectWrapper() From 738f6fab49cb4597898eb4dfdc4dc61b8a1b6ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 28 Oct 2025 15:53:52 +0100 Subject: [PATCH 02/25] [async] Improved async connection context manager with wrapping. --- src/snowflake/connector/aio/__init__.py | 63 +++++++++++++++++++------ 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index be78e88ff4..2c3db2c2d0 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Any +from functools import wraps +from typing import Any, Coroutine, Generator from ._connection import SnowflakeConnection from ._cursor import DictCursor, SnowflakeCursor @@ -12,18 +13,44 @@ ] +class _AsyncConnectContextManager: + """Hybrid wrapper that enables both awaiting and async context manager usage. + + Allows both patterns: + - conn = await connect(...) + - async with connect(...) as conn: + """ + + __slots__ = ("_coro", "_conn") + + def __init__(self, coro: Coroutine[Any, Any, SnowflakeConnection]) -> None: + self._coro = coro + self._conn: SnowflakeConnection | None = None + + def __await__(self) -> Generator[Any, None, SnowflakeConnection]: + """Enable await connect(...)""" + return self._coro.__await__() + + async def __aenter__(self) -> SnowflakeConnection: + """Enable async with connect(...) as conn:""" + self._conn = await self._coro + return await self._conn.__aenter__() + + async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: + """Exit async context manager.""" + if self._conn is not None: + return await self._conn.__aexit__(exc_type, exc, tb) + + class _AsyncConnectWrapper: - """Wrapper that preserves metadata of SnowflakeConnection.__init__ while providing async connect behavior. + """Preserves SnowflakeConnection.__init__ metadata for async connect function. - This class makes the async connect function metadata-compatible with the synchronous Connect function, - allowing introspection tools to see the same signature as SnowflakeConnection.__init__. + This wrapper enables introspection tools and IDEs to see the same signature + as the synchronous snowflake.connector.connect function. """ - def __init__(self): - # Copy metadata from SnowflakeConnection.__init__ to this instance - # This allows introspection tools to see the proper signature + def __init__(self) -> None: self.__wrapped__ = SnowflakeConnection.__init__ - # Standard functools.wraps attributes self.__name__ = "connect" self.__doc__ = SnowflakeConnection.__init__.__doc__ self.__module__ = __name__ @@ -32,16 +59,22 @@ def __init__(self): SnowflakeConnection.__init__, "__annotations__", {} ) - async def __call__(self, **kwargs: Any) -> SnowflakeConnection: + @wraps(SnowflakeConnection.__init__) + def __call__(self, **kwargs: Any) -> _AsyncConnectContextManager: """Create and connect to a Snowflake connection asynchronously. - This async function creates a SnowflakeConnection instance and establishes - the connection, replicating the behavior of the synchronous snowflake.connector.connect. + Returns an awaitable that can also be used as an async context manager. + Supports both patterns: + - conn = await connect(...) + - async with connect(...) as conn: """ - conn = SnowflakeConnection(**kwargs) - await conn.connect() - return conn + + async def _connect_coro() -> SnowflakeConnection: + conn = SnowflakeConnection(**kwargs) + await conn.connect() + return conn + + return _AsyncConnectContextManager(_connect_coro()) -# Create the async connect function with preserved metadata connect = _AsyncConnectWrapper() From 66ec753c8c313e719565c59c3a0d574fb0871fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 28 Oct 2025 15:57:11 +0100 Subject: [PATCH 03/25] [async] Full coroutine protocol supported --- src/snowflake/connector/aio/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index 2c3db2c2d0..834039f63e 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -19,6 +19,8 @@ class _AsyncConnectContextManager: Allows both patterns: - conn = await connect(...) - async with connect(...) as conn: + + Implements the full coroutine protocol for maximum compatibility. """ __slots__ = ("_coro", "_conn") @@ -27,10 +29,26 @@ def __init__(self, coro: Coroutine[Any, Any, SnowflakeConnection]) -> None: self._coro = coro self._conn: SnowflakeConnection | None = None + def send(self, arg: Any) -> Any: + """Send a value into the wrapped coroutine.""" + return self._coro.send(arg) + + def throw(self, *args: Any, **kwargs: Any) -> Any: + """Throw an exception into the wrapped coroutine.""" + return self._coro.throw(*args, **kwargs) + + def close(self) -> None: + """Close the wrapped coroutine.""" + return self._coro.close() + def __await__(self) -> Generator[Any, None, SnowflakeConnection]: """Enable await connect(...)""" return self._coro.__await__() + def __iter__(self) -> Generator[Any, None, SnowflakeConnection]: + """Make the wrapper iterable like a coroutine.""" + return self.__await__() + async def __aenter__(self) -> SnowflakeConnection: """Enable async with connect(...) as conn:""" self._conn = await self._coro From 2277a3d9aa4212fa5e4bdf36afda076e440b0b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 28 Oct 2025 16:33:13 +0100 Subject: [PATCH 04/25] [async] Fixed and tests --- src/snowflake/connector/aio/__init__.py | 11 ++++++- src/snowflake/connector/aio/_connection.py | 5 +++ test/integ/aio_it/conftest.py | 25 +++++++++------ test/integ/aio_it/test_connection_async.py | 37 +++++++++++++++++++++- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index 834039f63e..7f2165a437 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -49,15 +49,24 @@ def __iter__(self) -> Generator[Any, None, SnowflakeConnection]: """Make the wrapper iterable like a coroutine.""" return self.__await__() + # TODO: below is okay if we make idempotent __aenter__ of SnowflakeConnection class - so check if connected and do not repeat connecting + # async def __aenter__(self) -> SnowflakeConnection: + # """Enable async with connect(...) as conn:""" + # self._conn = await self._coro + # return await self._conn.__aenter__() + async def __aenter__(self) -> SnowflakeConnection: """Enable async with connect(...) as conn:""" self._conn = await self._coro - return await self._conn.__aenter__() + # Connection is already connected by the coroutine + return self._conn async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: """Exit async context manager.""" if self._conn is not None: return await self._conn.__aexit__(exc_type, exc, tb) + else: + return None class _AsyncConnectWrapper: diff --git a/src/snowflake/connector/aio/_connection.py b/src/snowflake/connector/aio/_connection.py index 9e0f6a9267..0cc9239493 100644 --- a/src/snowflake/connector/aio/_connection.py +++ b/src/snowflake/connector/aio/_connection.py @@ -134,6 +134,7 @@ def __init__( if "platform_detection_timeout_seconds" not in kwargs: self._platform_detection_timeout_seconds = 0.0 + # TODO: why we have it here if never changed self._connected = False self.expired = False # check SNOW-1218851 for long term improvement plan to refactor ocsp code @@ -173,6 +174,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): async def __aenter__(self) -> SnowflakeConnection: """Context manager.""" + # Idempotent __Aenter__ + # if self.is_closed(): + # await self.connect() + # return self await self.connect() return self diff --git a/test/integ/aio_it/conftest.py b/test/integ/aio_it/conftest.py index c3949c2424..dba36fba0c 100644 --- a/test/integ/aio_it/conftest.py +++ b/test/integ/aio_it/conftest.py @@ -9,11 +9,12 @@ get_db_parameters, is_public_testaccount, ) -from typing import AsyncContextManager, AsyncGenerator, Callable +from typing import Any, AsyncContextManager, AsyncGenerator, Callable import pytest from snowflake.connector.aio import SnowflakeConnection +from snowflake.connector.aio import connect as async_connect from snowflake.connector.aio._telemetry import TelemetryClient from snowflake.connector.connection import DefaultConverterClass from snowflake.connector.telemetry import TelemetryData @@ -70,13 +71,7 @@ def capture_sf_telemetry_async() -> TelemetryCaptureFixtureAsync: return TelemetryCaptureFixtureAsync() -async def create_connection(connection_name: str, **kwargs) -> SnowflakeConnection: - """Creates a connection using the parameters defined in parameters.py. - - You can select from the different connections by supplying the appropiate - connection_name parameter and then anything else supplied will overwrite the values - from parameters.py. - """ +def fill_conn_kwargs_for_tests(connection_name: str, **kwargs) -> dict[str, Any]: ret = get_db_parameters(connection_name) ret.update(kwargs) @@ -95,9 +90,21 @@ async def create_connection(connection_name: str, **kwargs) -> SnowflakeConnecti ret.pop("private_key", None) ret.pop("private_key_file", None) + return ret + + +async def create_connection(connection_name: str, **kwargs) -> SnowflakeConnection: + """Creates a connection using the parameters defined in parameters.py. + + You can select from the different connections by supplying the appropiate + connection_name parameter and then anything else supplied will overwrite the values + from parameters.py. + """ + ret = fill_conn_kwargs_for_tests(connection_name, **kwargs) connection = SnowflakeConnection(**ret) + conn = await async_connect(**ret) await connection.connect() - return connection + return conn @asynccontextmanager diff --git a/test/integ/aio_it/test_connection_async.py b/test/integ/aio_it/test_connection_async.py index 357173bbf2..68c7f44b86 100644 --- a/test/integ/aio_it/test_connection_async.py +++ b/test/integ/aio_it/test_connection_async.py @@ -46,7 +46,7 @@ CONNECTION_PARAMETERS_ADMIN = {} from snowflake.connector.aio.auth import AuthByOkta, AuthByPlugin -from .conftest import create_connection +from .conftest import create_connection, fill_conn_kwargs_for_tests try: from snowflake.connector.errorcode import ER_FAILED_PROCESSING_QMARK @@ -1466,6 +1466,41 @@ async def test_platform_detection_timeout(conn_cnx): assert cnx.platform_detection_timeout_seconds == 2.5 +@pytest.mark.skipolddriver +async def test_conn_cnx_changed(conn_cnx): + """Tests platform detection timeout. + + Creates a connection with platform_detection_timeout parameter. + """ + async with conn_cnx() as conn: + async with conn.cursor() as cur: + result = await (await cur.execute("select 1")).fetchall() + assert len(result) == 1 + assert result[0][0] == 1 + + +@pytest.mark.skipolddriver +async def test_conn_assigned(conn_cnx): + conn = await snowflake.connector.aio.connect( + **fill_conn_kwargs_for_tests("default") + ) + async with conn.cursor() as cur: + result = await (await cur.execute("select 1")).fetchall() + assert len(result) == 1 + assert result[0][0] == 1 + + +@pytest.mark.skipolddriver +async def test_conn_with(conn_cnx): + async with snowflake.connector.aio.connect( + **fill_conn_kwargs_for_tests("default") + ) as conn: + async with conn.cursor() as cur: + result = await (await cur.execute("select 1")).fetchall() + assert len(result) == 1 + assert result[0][0] == 1 + + @pytest.mark.skipolddriver async def test_platform_detection_zero_timeout(conn_cnx): with ( From 1aaa6189799cd0599c789ab494f50f70209b05d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 28 Oct 2025 17:03:39 +0100 Subject: [PATCH 05/25] [async] Tests --- test/integ/aio_it/test_connection_async.py | 29 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/test/integ/aio_it/test_connection_async.py b/test/integ/aio_it/test_connection_async.py index 68c7f44b86..de2497820f 100644 --- a/test/integ/aio_it/test_connection_async.py +++ b/test/integ/aio_it/test_connection_async.py @@ -1467,7 +1467,7 @@ async def test_platform_detection_timeout(conn_cnx): @pytest.mark.skipolddriver -async def test_conn_cnx_changed(conn_cnx): +async def test_conn_cnx_basic(conn_cnx): """Tests platform detection timeout. Creates a connection with platform_detection_timeout parameter. @@ -1480,7 +1480,7 @@ async def test_conn_cnx_changed(conn_cnx): @pytest.mark.skipolddriver -async def test_conn_assigned(conn_cnx): +async def test_conn_assigned_method(conn_cnx): conn = await snowflake.connector.aio.connect( **fill_conn_kwargs_for_tests("default") ) @@ -1491,7 +1491,19 @@ async def test_conn_assigned(conn_cnx): @pytest.mark.skipolddriver -async def test_conn_with(conn_cnx): +async def test_conn_assigned_class(conn_cnx): + conn = snowflake.connector.aio.SnowflakeConnection( + **fill_conn_kwargs_for_tests("default") + ) + await conn.connect() + async with conn.cursor() as cur: + result = await (await cur.execute("select 1")).fetchall() + assert len(result) == 1 + assert result[0][0] == 1 + + +@pytest.mark.skipolddriver +async def test_conn_with_method(conn_cnx): async with snowflake.connector.aio.connect( **fill_conn_kwargs_for_tests("default") ) as conn: @@ -1501,6 +1513,17 @@ async def test_conn_with(conn_cnx): assert result[0][0] == 1 +@pytest.mark.skipolddriver +async def test_conn_with_class(conn_cnx): + async with snowflake.connector.aio.SnowflakeConnection( + **fill_conn_kwargs_for_tests("default") + ) as conn: + async with conn.cursor() as cur: + result = await (await cur.execute("select 1")).fetchall() + assert len(result) == 1 + assert result[0][0] == 1 + + @pytest.mark.skipolddriver async def test_platform_detection_zero_timeout(conn_cnx): with ( From 013390cbff3b5685c2f207a646f4c94c61ab33fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 28 Oct 2025 17:04:25 +0100 Subject: [PATCH 06/25] [async] Approach 1 - Idempotent connection.__aenter__ through checking if closed --- src/snowflake/connector/aio/__init__.py | 10 ++-------- src/snowflake/connector/aio/_connection.py | 10 +++++----- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index 7f2165a437..f0813347e5 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -49,17 +49,11 @@ def __iter__(self) -> Generator[Any, None, SnowflakeConnection]: """Make the wrapper iterable like a coroutine.""" return self.__await__() - # TODO: below is okay if we make idempotent __aenter__ of SnowflakeConnection class - so check if connected and do not repeat connecting - # async def __aenter__(self) -> SnowflakeConnection: - # """Enable async with connect(...) as conn:""" - # self._conn = await self._coro - # return await self._conn.__aenter__() - + # This approach requires idempotent __aenter__ of SnowflakeConnection class - so check if connected and do not repeat connecting async def __aenter__(self) -> SnowflakeConnection: """Enable async with connect(...) as conn:""" self._conn = await self._coro - # Connection is already connected by the coroutine - return self._conn + return await self._conn.__aenter__() async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: """Exit async context manager.""" diff --git a/src/snowflake/connector/aio/_connection.py b/src/snowflake/connector/aio/_connection.py index 0cc9239493..b6dc1c808a 100644 --- a/src/snowflake/connector/aio/_connection.py +++ b/src/snowflake/connector/aio/_connection.py @@ -174,11 +174,11 @@ def __exit__(self, exc_type, exc_val, exc_tb): async def __aenter__(self) -> SnowflakeConnection: """Context manager.""" - # Idempotent __Aenter__ - # if self.is_closed(): - # await self.connect() - # return self - await self.connect() + # Idempotent __aenter__ - required to be able to use both: + # - with snowflake.connector.aio.SnowflakeConnection(**k) + # - with snowflake.connector.aio.connect(**k) + if self.is_closed(): + await self.connect() return self async def __aexit__( From aadca49b43ee0e1526f3a52b5337b5e48452dde2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 28 Oct 2025 18:49:07 +0100 Subject: [PATCH 07/25] [async] Added docs --- ASYNC_CONNECT_IMPLEMENTATION.md | 269 ++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 ASYNC_CONNECT_IMPLEMENTATION.md diff --git a/ASYNC_CONNECT_IMPLEMENTATION.md b/ASYNC_CONNECT_IMPLEMENTATION.md new file mode 100644 index 0000000000..25b104a56d --- /dev/null +++ b/ASYNC_CONNECT_IMPLEMENTATION.md @@ -0,0 +1,269 @@ +# Async Connect Wrapper Implementation + +## Overview + +This document describes the hybrid wrapper pattern used to implement `snowflake.connector.aio.connect()` that preserves metadata from `SnowflakeConnection.__init__` while supporting both simple await and async context manager usage patterns, with full coroutine protocol support. + +## Problem Statement + +The synchronous `snowflake.connector.connect()` uses: +```python +@wraps(SnowflakeConnection.__init__) +def Connect(**kwargs) -> SnowflakeConnection: + return SnowflakeConnection(**kwargs) +``` + +The async version cannot be decorated with `@wraps` on a raw async function. We needed a solution that: +1. Preserves metadata for IDE introspection and tooling +2. Supports `conn = await aio.connect(...)` +3. Supports `async with aio.connect(...) as conn:` +4. Implements the full coroutine protocol (following aiohttp's pattern) + +## Implementation + +### Architecture + +``` +connect = _AsyncConnectWrapper() + ↓ (calls __call__) +_AsyncConnectContextManager (coroutine wrapper) + ├─ Coroutine Protocol: send(), throw(), close() + ├─ __await__(), __iter__() + └─ __aenter__(), __aexit__() (async context manager) +``` + +### Class: _AsyncConnectContextManager + +Makes a coroutine both awaitable and an async context manager, while implementing the full coroutine protocol. + +```python +class _AsyncConnectContextManager: + """Hybrid wrapper that enables both awaiting and async context manager usage. + + Implements the full coroutine protocol for maximum compatibility. + """ + + __slots__ = ("_coro", "_conn") + + def __init__(self, coro: Coroutine[Any, Any, SnowflakeConnection]) -> None: + self._coro = coro + self._conn: SnowflakeConnection | None = None + + def send(self, arg: Any) -> Any: + """Send a value into the wrapped coroutine.""" + return self._coro.send(arg) + + def throw(self, *args: Any, **kwargs: Any) -> Any: + """Throw an exception into the wrapped coroutine.""" + return self._coro.throw(*args, **kwargs) + + def close(self) -> None: + """Close the wrapped coroutine.""" + return self._coro.close() + + def __await__(self) -> Generator[Any, None, SnowflakeConnection]: + """Enable: conn = await connect(...)""" + return self._coro.__await__() + + def __iter__(self) -> Generator[Any, None, SnowflakeConnection]: + """Make the wrapper iterable like a coroutine.""" + return self.__await__() + + async def __aenter__(self) -> SnowflakeConnection: + """Enable: async with connect(...) as conn:""" + self._conn = await self._coro + return await self._conn.__aenter__() + + async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: + """Exit async context manager.""" + if self._conn is not None: + return await self._conn.__aexit__(exc_type, exc, tb) +``` + +#### Coroutine Protocol Methods + +- **`send(arg)`**: Send a value into the wrapped coroutine (used for manual coroutine driving) +- **`throw(*args, **kwargs)`**: Throw an exception into the wrapped coroutine +- **`close()`**: Gracefully close the wrapped coroutine +- **`__await__()`**: Return a generator to enable `await` syntax +- **`__iter__()`**: Make the wrapper iterable (some async utilities require this) + +### Class: _AsyncConnectWrapper + +Callable wrapper that preserves `SnowflakeConnection.__init__` metadata. + +```python +class _AsyncConnectWrapper: + """Preserves SnowflakeConnection.__init__ metadata for async connect function. + + This wrapper enables introspection tools and IDEs to see the same signature + as the synchronous snowflake.connector.connect function. + """ + + def __init__(self) -> None: + self.__wrapped__ = SnowflakeConnection.__init__ + self.__name__ = "connect" + self.__doc__ = SnowflakeConnection.__init__.__doc__ + self.__module__ = __name__ + self.__qualname__ = "connect" + self.__annotations__ = getattr(SnowflakeConnection.__init__, "__annotations__", {}) + + @wraps(SnowflakeConnection.__init__) + def __call__(self, **kwargs: Any) -> _AsyncConnectContextManager: + """Create and connect to a Snowflake connection asynchronously.""" + async def _connect_coro() -> SnowflakeConnection: + conn = SnowflakeConnection(**kwargs) + await conn.connect() + return conn + + return _AsyncConnectContextManager(_connect_coro()) +``` + +## Usage Patterns + +### Pattern 1: Simple Await (No Context Manager) +```python +conn = await aio.connect( + account="myaccount", + user="myuser", + password="mypassword" +) +result = await conn.cursor().execute("SELECT 1") +await conn.close() +``` + +**Flow:** +1. `aio.connect(...)` returns `_AsyncConnectContextManager` +2. `await` calls `__await__()`, returns inner coroutine result +3. `conn` is a `SnowflakeConnection` object +4. Wrapper is garbage collected + +### Pattern 2: Async Context Manager +```python +async with aio.connect( + account="myaccount", + user="myuser", + password="mypassword" +) as conn: + result = await conn.cursor().execute("SELECT 1") + # Auto-closes on exit +``` + +**Flow:** +1. `aio.connect(...)` returns `_AsyncConnectContextManager` +2. `async with` calls `__aenter__()`, awaits coroutine, returns connection +3. Code block executes +4. `async with` calls `__aexit__()` on exit + +### Pattern 3: Manual Coroutine Driving (Advanced) +```python +# For advanced use cases with manual iteration +coro_wrapper = aio.connect(account="myaccount", user="user", password="pass") +# Can use send(), throw(), close() methods directly if needed +``` + +## Key Design Decisions + +### 1. Metadata Copying in `__init__` +```python +self.__wrapped__ = SnowflakeConnection.__init__ +self.__name__ = "connect" +# ... +``` +**Why:** Allows direct attribute access for introspection before calling `__call__` + +### 2. `@wraps` Decorator on `__call__` +```python +@wraps(SnowflakeConnection.__init__) +def __call__(self, **kwargs: Any) -> _AsyncConnectContextManager: +``` +**Why:** IDEs and inspection tools that examine `__call__` see correct metadata + +### 3. Inner Coroutine Function +```python +async def _connect_coro() -> SnowflakeConnection: + conn = SnowflakeConnection(**kwargs) + await conn.connect() + return conn +``` +**Why:** Defers connection creation and establishment until await time, not at `connect()` call time + +### 4. `__slots__` on Context Manager +```python +__slots__ = ("_coro", "_conn") +``` +**Why:** Memory efficient, especially when many connections are created + +### 5. Full Coroutine Protocol +Following aiohttp's `_RequestContextManager` pattern, we implement `send()`, `throw()`, and `close()` methods. This ensures: +- Maximum compatibility with async utilities and libraries +- Support for manual coroutine driving if needed +- Proper cleanup and exception handling + +## Comparison with aiohttp's _RequestContextManager + +Our implementation follows the same proven pattern used by aiohttp: + +| Feature | aiohttp | Our Implementation | +|---------|---------|-------------------| +| `send()` method | ✓ | ✓ | +| `throw()` method | ✓ | ✓ | +| `close()` method | ✓ | ✓ | +| `__await__()` method | ✓ | ✓ | +| `__iter__()` method | ✓ | ✓ | +| `__aenter__()` method | ✓ | ✓ | +| `__aexit__()` method | ✓ | ✓ | +| Metadata preservation | N/A | ✓ | + +## Behavior Comparison + +| Aspect | Pattern 1 | Pattern 2 | Pattern 3 | +|--------|-----------|-----------|-----------| +| Syntax | `conn = await aio.connect(...)` | `async with aio.connect(...) as conn:` | Manual iteration | +| Connection Object | In local scope | In context scope | Via wrapper | +| Cleanup | Manual (`await conn.close()`) | Automatic (`__aexit__`) | Manual | +| Metadata Available | Yes | Yes | Yes | +| Error Handling | Manual try/except | Automatic via context manager | Manual | +| Use Case | Simple connections | Resource management | Advanced/testing | + +## Verification + +```python +# Metadata preservation +assert connect.__name__ == "connect" +assert hasattr(connect, "__wrapped__") +assert callable(connect) + +# Return type validation +result = connect(account="test") +assert hasattr(result, "__await__") # Awaitable +assert hasattr(result, "__aenter__") # Async context manager +assert hasattr(result, "__aexit__") # Async context manager + +# Full coroutine protocol +assert hasattr(result, "send") # Coroutine protocol +assert hasattr(result, "throw") # Exception injection +assert hasattr(result, "close") # Cleanup +assert hasattr(result, "__iter__") # Iteration support +``` + +## File Location + +`src/snowflake/connector/aio/__init__.py` + +## Backwards Compatibility + +- ✅ Existing code using `await aio.connect(...)` works unchanged +- ✅ New code can use `async with aio.connect(...) as conn:` +- ✅ Metadata available for IDE tooltips and introspection tools +- ✅ Signature matches synchronous version in tooling +- ✅ Full coroutine protocol support for advanced use cases + +## Benefits + +✅ **Full Coroutine Protocol** - Compatible with all async utilities and libraries +✅ **Flexible Usage** - Simple await or async context manager patterns +✅ **Metadata Preservation** - IDE tooltips and introspection support +✅ **Transparent & Efficient** - Minimal overhead, garbage collected after use +✅ **Backwards Compatible** - No breaking changes to existing code +✅ **Battle-tested Pattern** - Follows aiohttp's proven design From 967d0be6c976e391453692b1a697ab4346234d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Sun, 2 Nov 2025 17:12:09 +0100 Subject: [PATCH 08/25] NO-SNOW: cleaned up code --- ASYNC_CONNECT_IMPLEMENTATION.md | 260 +++++------------------- src/snowflake/connector/aio/__init__.py | 135 ++++++++++-- 2 files changed, 177 insertions(+), 218 deletions(-) diff --git a/ASYNC_CONNECT_IMPLEMENTATION.md b/ASYNC_CONNECT_IMPLEMENTATION.md index 25b104a56d..4047b55183 100644 --- a/ASYNC_CONNECT_IMPLEMENTATION.md +++ b/ASYNC_CONNECT_IMPLEMENTATION.md @@ -2,229 +2,95 @@ ## Overview -This document describes the hybrid wrapper pattern used to implement `snowflake.connector.aio.connect()` that preserves metadata from `SnowflakeConnection.__init__` while supporting both simple await and async context manager usage patterns, with full coroutine protocol support. +This document describes the hybrid wrapper pattern used to implement `snowflake.connector.aio.connect()` that preserves metadata from `SnowflakeConnection.__init__` while supporting both simple await and async context manager usage patterns with full coroutine protocol support. ## Problem Statement -The synchronous `snowflake.connector.connect()` uses: -```python -@wraps(SnowflakeConnection.__init__) -def Connect(**kwargs) -> SnowflakeConnection: - return SnowflakeConnection(**kwargs) -``` - -The async version cannot be decorated with `@wraps` on a raw async function. We needed a solution that: -1. Preserves metadata for IDE introspection and tooling -2. Supports `conn = await aio.connect(...)` -3. Supports `async with aio.connect(...) as conn:` -4. Implements the full coroutine protocol (following aiohttp's pattern) +The async version of `connect()` must: +1. Preserve metadata for IDE introspection and tooling (like the synchronous version) +2. Support `conn = await aio.connect(...)` pattern +3. Support `async with aio.connect(...) as conn:` pattern +4. Implement the full coroutine protocol for ecosystem compatibility (following aiohttp's pattern) -## Implementation - -### Architecture +## Architecture ``` connect = _AsyncConnectWrapper() ↓ (calls __call__) -_AsyncConnectContextManager (coroutine wrapper) - ├─ Coroutine Protocol: send(), throw(), close() - ├─ __await__(), __iter__() - └─ __aenter__(), __aexit__() (async context manager) +_AsyncConnectContextManager (implements HybridCoroutineContextManager protocol) + ├─ Coroutine Protocol: send(), throw(), close(), __await__(), __iter__() + └─ Async Context Manager: __aenter__(), __aexit__() ``` -### Class: _AsyncConnectContextManager +## Key Components -Makes a coroutine both awaitable and an async context manager, while implementing the full coroutine protocol. +### HybridCoroutineContextManager Protocol -```python -class _AsyncConnectContextManager: - """Hybrid wrapper that enables both awaiting and async context manager usage. +Combines PEP 492 (coroutine) and PEP 343 (async context manager) protocols. Allows the same object to be managed by external code expecting either interface (e.g., timeout handlers, async schedulers). - Implements the full coroutine protocol for maximum compatibility. - """ +### _AsyncConnectContextManager - __slots__ = ("_coro", "_conn") +Implements `HybridCoroutineContextManager[SnowflakeConnection]`: +- **`send()`, `throw()`, `close()`** - Forward to inner coroutine for ecosystem compatibility +- **`__await__()`** - Enable `await` syntax +- **`__aenter__()` / `__aexit__()`** - Enable async context manager usage - def __init__(self, coro: Coroutine[Any, Any, SnowflakeConnection]) -> None: - self._coro = coro - self._conn: SnowflakeConnection | None = None +### preserve_metadata Decorator - def send(self, arg: Any) -> Any: - """Send a value into the wrapped coroutine.""" - return self._coro.send(arg) +Copies `__wrapped__`, `__doc__`, `__module__`, `__annotations__` from a source class to instances during `__init__`. Allows instances to be introspected like the source class's `__init__`, enabling IDE support and tooling. - def throw(self, *args: Any, **kwargs: Any) -> Any: - """Throw an exception into the wrapped coroutine.""" - return self._coro.throw(*args, **kwargs) +### _AsyncConnectWrapper - def close(self) -> None: - """Close the wrapped coroutine.""" - return self._coro.close() +Callable wrapper decorated with `@preserve_metadata(SnowflakeConnection)` to preserve metadata. Its `__call__` method creates and returns a `_AsyncConnectContextManager` instance. - def __await__(self) -> Generator[Any, None, SnowflakeConnection]: - """Enable: conn = await connect(...)""" - return self._coro.__await__() +## Design Rationale - def __iter__(self) -> Generator[Any, None, SnowflakeConnection]: - """Make the wrapper iterable like a coroutine.""" - return self.__await__() +### Why Full Coroutine Protocol? - async def __aenter__(self) -> SnowflakeConnection: - """Enable: async with connect(...) as conn:""" - self._conn = await self._coro - return await self._conn.__aenter__() - - async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: - """Exit async context manager.""" - if self._conn is not None: - return await self._conn.__aexit__(exc_type, exc, tb) +External async code (timeout handlers, async schedulers, introspection tools) expects `send()`, `throw()`, and `close()` methods. Without these, our wrapper breaks compatibility and may fail at runtime with code like: +```python +result = await asyncio.wait_for(aio.connect(...), timeout=5.0) # May call throw() ``` -#### Coroutine Protocol Methods +### Why Preserve Metadata? -- **`send(arg)`**: Send a value into the wrapped coroutine (used for manual coroutine driving) -- **`throw(*args, **kwargs)`**: Throw an exception into the wrapped coroutine -- **`close()`**: Gracefully close the wrapped coroutine -- **`__await__()`**: Return a generator to enable `await` syntax -- **`__iter__()`**: Make the wrapper iterable (some async utilities require this) +- IDEs show correct function signature when hovering over `connect` +- `help(connect)` displays proper docstring +- `inspect.signature(connect)` works correctly +- Static type checkers recognize parameters -### Class: _AsyncConnectWrapper +### Why Both Await and Context Manager? -Callable wrapper that preserves `SnowflakeConnection.__init__` metadata. - -```python -class _AsyncConnectWrapper: - """Preserves SnowflakeConnection.__init__ metadata for async connect function. - - This wrapper enables introspection tools and IDEs to see the same signature - as the synchronous snowflake.connector.connect function. - """ - - def __init__(self) -> None: - self.__wrapped__ = SnowflakeConnection.__init__ - self.__name__ = "connect" - self.__doc__ = SnowflakeConnection.__init__.__doc__ - self.__module__ = __name__ - self.__qualname__ = "connect" - self.__annotations__ = getattr(SnowflakeConnection.__init__, "__annotations__", {}) - - @wraps(SnowflakeConnection.__init__) - def __call__(self, **kwargs: Any) -> _AsyncConnectContextManager: - """Create and connect to a Snowflake connection asynchronously.""" - async def _connect_coro() -> SnowflakeConnection: - conn = SnowflakeConnection(**kwargs) - await conn.connect() - return conn - - return _AsyncConnectContextManager(_connect_coro()) -``` +- **Await pattern:** Simple, lightweight for one-off connections +- **Context manager pattern:** Ensures cleanup via `__aexit__`, safer for resource management ## Usage Patterns -### Pattern 1: Simple Await (No Context Manager) ```python -conn = await aio.connect( - account="myaccount", - user="myuser", - password="mypassword" -) +# Pattern 1: Simple await +conn = await aio.connect(account="myaccount", user="user", password="pass") result = await conn.cursor().execute("SELECT 1") await conn.close() -``` -**Flow:** -1. `aio.connect(...)` returns `_AsyncConnectContextManager` -2. `await` calls `__await__()`, returns inner coroutine result -3. `conn` is a `SnowflakeConnection` object -4. Wrapper is garbage collected - -### Pattern 2: Async Context Manager -```python -async with aio.connect( - account="myaccount", - user="myuser", - password="mypassword" -) as conn: +# Pattern 2: Async context manager (recommended) +async with aio.connect(account="myaccount", user="user", password="pass") as conn: result = await conn.cursor().execute("SELECT 1") # Auto-closes on exit ``` -**Flow:** -1. `aio.connect(...)` returns `_AsyncConnectContextManager` -2. `async with` calls `__aenter__()`, awaits coroutine, returns connection -3. Code block executes -4. `async with` calls `__aexit__()` on exit +## Implementation Details -### Pattern 3: Manual Coroutine Driving (Advanced) -```python -# For advanced use cases with manual iteration -coro_wrapper = aio.connect(account="myaccount", user="user", password="pass") -# Can use send(), throw(), close() methods directly if needed -``` +### __slots__ = ("_coro", "_conn") +Memory efficient, especially when many connections are created. -## Key Design Decisions - -### 1. Metadata Copying in `__init__` -```python -self.__wrapped__ = SnowflakeConnection.__init__ -self.__name__ = "connect" -# ... -``` -**Why:** Allows direct attribute access for introspection before calling `__call__` - -### 2. `@wraps` Decorator on `__call__` -```python -@wraps(SnowflakeConnection.__init__) -def __call__(self, **kwargs: Any) -> _AsyncConnectContextManager: -``` -**Why:** IDEs and inspection tools that examine `__call__` see correct metadata - -### 3. Inner Coroutine Function +### Inner Coroutine Function ```python async def _connect_coro() -> SnowflakeConnection: conn = SnowflakeConnection(**kwargs) await conn.connect() return conn ``` -**Why:** Defers connection creation and establishment until await time, not at `connect()` call time - -### 4. `__slots__` on Context Manager -```python -__slots__ = ("_coro", "_conn") -``` -**Why:** Memory efficient, especially when many connections are created - -### 5. Full Coroutine Protocol -Following aiohttp's `_RequestContextManager` pattern, we implement `send()`, `throw()`, and `close()` methods. This ensures: -- Maximum compatibility with async utilities and libraries -- Support for manual coroutine driving if needed -- Proper cleanup and exception handling - -## Comparison with aiohttp's _RequestContextManager - -Our implementation follows the same proven pattern used by aiohttp: - -| Feature | aiohttp | Our Implementation | -|---------|---------|-------------------| -| `send()` method | ✓ | ✓ | -| `throw()` method | ✓ | ✓ | -| `close()` method | ✓ | ✓ | -| `__await__()` method | ✓ | ✓ | -| `__iter__()` method | ✓ | ✓ | -| `__aenter__()` method | ✓ | ✓ | -| `__aexit__()` method | ✓ | ✓ | -| Metadata preservation | N/A | ✓ | - -## Behavior Comparison - -| Aspect | Pattern 1 | Pattern 2 | Pattern 3 | -|--------|-----------|-----------|-----------| -| Syntax | `conn = await aio.connect(...)` | `async with aio.connect(...) as conn:` | Manual iteration | -| Connection Object | In local scope | In context scope | Via wrapper | -| Cleanup | Manual (`await conn.close()`) | Automatic (`__aexit__`) | Manual | -| Metadata Available | Yes | Yes | Yes | -| Error Handling | Manual try/except | Automatic via context manager | Manual | -| Use Case | Simple connections | Resource management | Advanced/testing | +Defers connection creation and establishment until await time, not at `connect()` call time. ## Verification @@ -234,36 +100,20 @@ assert connect.__name__ == "connect" assert hasattr(connect, "__wrapped__") assert callable(connect) -# Return type validation +# Return type is hybrid awaitable + context manager result = connect(account="test") -assert hasattr(result, "__await__") # Awaitable -assert hasattr(result, "__aenter__") # Async context manager -assert hasattr(result, "__aexit__") # Async context manager - -# Full coroutine protocol -assert hasattr(result, "send") # Coroutine protocol -assert hasattr(result, "throw") # Exception injection -assert hasattr(result, "close") # Cleanup -assert hasattr(result, "__iter__") # Iteration support +assert hasattr(result, "__await__") # Awaitable +assert hasattr(result, "__aenter__") # Async context manager +assert hasattr(result, "send") # Full coroutine protocol ``` -## File Location - -`src/snowflake/connector/aio/__init__.py` - ## Backwards Compatibility - ✅ Existing code using `await aio.connect(...)` works unchanged -- ✅ New code can use `async with aio.connect(...) as conn:` -- ✅ Metadata available for IDE tooltips and introspection tools -- ✅ Signature matches synchronous version in tooling -- ✅ Full coroutine protocol support for advanced use cases - -## Benefits - -✅ **Full Coroutine Protocol** - Compatible with all async utilities and libraries -✅ **Flexible Usage** - Simple await or async context manager patterns -✅ **Metadata Preservation** - IDE tooltips and introspection support -✅ **Transparent & Efficient** - Minimal overhead, garbage collected after use -✅ **Backwards Compatible** - No breaking changes to existing code -✅ **Battle-tested Pattern** - Follows aiohttp's proven design +- ✅ Metadata available for IDE tooltips and introspection +- ✅ Full coroutine protocol support for advanced/external tooling +- ✅ No breaking changes + +## File Location + +`src/snowflake/connector/aio/__init__.py` diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index f0813347e5..647d901bda 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -1,7 +1,15 @@ from __future__ import annotations -from functools import wraps -from typing import Any, Coroutine, Generator +from functools import update_wrapper, wraps +from typing import ( + Any, + Callable, + Coroutine, + Generator, + Protocol, + TypeVar, + runtime_checkable, +) from ._connection import SnowflakeConnection from ._cursor import DictCursor, SnowflakeCursor @@ -12,8 +20,113 @@ DictCursor, ] +# ============================================================================ +# DESIGN NOTES: +# +# The async connect function uses a two-layer wrapper to support both: +# 1. Direct awaiting: conn = await connect(...) +# 2. Async context manager: async with connect(...) as conn: +# +# _AsyncConnectContextManager: Implements __await__ and __aenter__/__aexit__ +# to support both patterns on the same awaitable. +# +# _AsyncConnectWrapper: A callable class that preserves SnowflakeConnection +# metadata via @preserve_metadata decorator for IDE support, type checking, +# and introspection. Returns _AsyncConnectContextManager instances when called. +# +# Metadata preservation is critical for IDE autocomplete, static type checkers, +# and documentation generation to work correctly on the async connect function. +# ============================================================================ + + +T = TypeVar("T") + + +@runtime_checkable +class HybridCoroutineContextManager(Protocol[T]): + """Protocol for a hybrid coroutine that is also an async context manager. + + Combines the full coroutine protocol (PEP 492) with async context manager + protocol (PEP 343/492), allowing code that expects either interface to work + seamlessly with instances of this protocol. + + This is used when external code needs to manage the coroutine lifecycle + (e.g., timeout handlers, async schedulers) or use it as a context manager. + """ + + # Full Coroutine Protocol (PEP 492) + def send(self, __arg: Any) -> Any: + """Send a value into the coroutine.""" + ... + + def throw( + self, + __typ: type[BaseException], + __val: BaseException | None = None, + __tb: Any = None, + ) -> Any: + """Throw an exception into the coroutine.""" + ... + + def close(self) -> None: + """Close the coroutine.""" + ... + + def __await__(self) -> Generator[Any, None, T]: + """Return awaitable generator.""" + ... + + def __iter__(self) -> Generator[Any, None, T]: + """Iterate over the coroutine.""" + ... + + # Async Context Manager Protocol (PEP 343) + async def __aenter__(self) -> T: + """Async context manager entry.""" + ... + + async def __aexit__( + self, + __exc_type: type[BaseException] | None, + __exc_val: BaseException | None, + __exc_tb: Any, + ) -> bool | None: + """Async context manager exit.""" + ... + + +def preserve_metadata( + source: type, *, override_name: str | None = None +) -> Callable[[T], T]: + """Decorator to copy metadata from a source class to class instances. + + Copies __wrapped__, __doc__, __module__, __annotations__ etc. to instances + during their initialization, allowing instances to be introspected like the + source class's __init__. + + Args: + source: Class to copy metadata from (uses its __init__). + override_name: Optional name override for instances. + """ + + def decorator(cls: T) -> T: + metadata_source = source.__init__ + original_init = cls.__init__ + + def new_init(self: Any) -> None: + update_wrapper(self, metadata_source, updated=[]) + if override_name: + self.__name__ = override_name + self.__qualname__ = override_name + original_init(self) + + cls.__init__ = new_init + return cls + + return decorator + -class _AsyncConnectContextManager: +class _AsyncConnectContextManager(HybridCoroutineContextManager[SnowflakeConnection]): """Hybrid wrapper that enables both awaiting and async context manager usage. Allows both patterns: @@ -21,6 +134,7 @@ class _AsyncConnectContextManager: - async with connect(...) as conn: Implements the full coroutine protocol for maximum compatibility. + Satisfies the HybridCoroutineContextManager protocol. """ __slots__ = ("_coro", "_conn") @@ -63,6 +177,7 @@ async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: return None +@preserve_metadata(SnowflakeConnection, override_name="connect") class _AsyncConnectWrapper: """Preserves SnowflakeConnection.__init__ metadata for async connect function. @@ -70,18 +185,12 @@ class _AsyncConnectWrapper: as the synchronous snowflake.connector.connect function. """ - def __init__(self) -> None: - self.__wrapped__ = SnowflakeConnection.__init__ - self.__name__ = "connect" - self.__doc__ = SnowflakeConnection.__init__.__doc__ - self.__module__ = __name__ - self.__qualname__ = "connect" - self.__annotations__ = getattr( - SnowflakeConnection.__init__, "__annotations__", {} - ) + def __init__(self) -> None: ... @wraps(SnowflakeConnection.__init__) - def __call__(self, **kwargs: Any) -> _AsyncConnectContextManager: + def __call__( + self, **kwargs: Any + ) -> HybridCoroutineContextManager[SnowflakeConnection]: """Create and connect to a Snowflake connection asynchronously. Returns an awaitable that can also be used as an async context manager. From 560b381ff8794f402911df8f632fa053d15de4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Sun, 2 Nov 2025 17:18:35 +0100 Subject: [PATCH 09/25] NO-SNOW: doc update --- ASYNC_CONNECT_IMPLEMENTATION.md | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/ASYNC_CONNECT_IMPLEMENTATION.md b/ASYNC_CONNECT_IMPLEMENTATION.md index 4047b55183..a54a659a42 100644 --- a/ASYNC_CONNECT_IMPLEMENTATION.md +++ b/ASYNC_CONNECT_IMPLEMENTATION.md @@ -1,4 +1,4 @@ -# Async Connect Wrapper Implementation +# Async Connect Wrapper: Design & Implementation ## Overview @@ -92,28 +92,6 @@ async def _connect_coro() -> SnowflakeConnection: ``` Defers connection creation and establishment until await time, not at `connect()` call time. -## Verification - -```python -# Metadata preservation -assert connect.__name__ == "connect" -assert hasattr(connect, "__wrapped__") -assert callable(connect) - -# Return type is hybrid awaitable + context manager -result = connect(account="test") -assert hasattr(result, "__await__") # Awaitable -assert hasattr(result, "__aenter__") # Async context manager -assert hasattr(result, "send") # Full coroutine protocol -``` - -## Backwards Compatibility - -- ✅ Existing code using `await aio.connect(...)` works unchanged -- ✅ Metadata available for IDE tooltips and introspection -- ✅ Full coroutine protocol support for advanced/external tooling -- ✅ No breaking changes - ## File Location `src/snowflake/connector/aio/__init__.py` From 7d99b42fcf1d92af8439de1a7e94fff55187a270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Sun, 2 Nov 2025 17:56:46 +0100 Subject: [PATCH 10/25] NO-SNOW: doc update --- .../snowflake/connector/aio/async_connect_implementation.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ASYNC_CONNECT_IMPLEMENTATION.md => src/snowflake/connector/aio/async_connect_implementation.md (100%) diff --git a/ASYNC_CONNECT_IMPLEMENTATION.md b/src/snowflake/connector/aio/async_connect_implementation.md similarity index 100% rename from ASYNC_CONNECT_IMPLEMENTATION.md rename to src/snowflake/connector/aio/async_connect_implementation.md From c96d001d9ac213562edc295e3fea917b2b8bb935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 13:27:10 +0100 Subject: [PATCH 11/25] NO-SNOW: failing expected interface of connect --- test/integ/test_connection.py | 86 +++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/test/integ/test_connection.py b/test/integ/test_connection.py index 966c8f7d10..564a558e05 100644 --- a/test/integ/test_connection.py +++ b/test/integ/test_connection.py @@ -17,12 +17,15 @@ import pytest import snowflake.connector -from snowflake.connector import DatabaseError, OperationalError, ProgrammingError -from snowflake.connector.compat import IS_WINDOWS -from snowflake.connector.connection import ( - DEFAULT_CLIENT_PREFETCH_THREADS, +from snowflake.connector import ( + DatabaseError, + OperationalError, + ProgrammingError, SnowflakeConnection, + connect, ) +from snowflake.connector.compat import IS_WINDOWS +from snowflake.connector.connection import DEFAULT_CLIENT_PREFETCH_THREADS from snowflake.connector.description import CLIENT_NAME from snowflake.connector.errorcode import ( ER_CONNECTION_IS_CLOSED, @@ -1564,6 +1567,81 @@ def test_no_new_warnings_or_errors_on_successful_basic_select(conn_cnx, caplog): ) +@pytest.mark.skipolddriver +def test_connect_metadata_preservation(): + """Test that the sync connect function preserves metadata from SnowflakeConnection.__init__. + + This test verifies that various inspection methods return consistent metadata, + ensuring IDE support, type checking, and documentation generation work correctly. + """ + import inspect + + # Test 1: Check __name__ is correct + assert ( + connect.__name__ == "connect" + ), f"connect.__name__ should be 'connect', but got '{connect.__name__}'" + + # Test 2: Check __wrapped__ points to SnowflakeConnection.__init__ + assert hasattr(connect, "__wrapped__"), "connect should have __wrapped__ attribute" + assert ( + connect.__wrapped__ is SnowflakeConnection.__init__ + ), "connect.__wrapped__ should reference SnowflakeConnection.__init__" + + # Test 3: Check __module__ is preserved + assert hasattr(connect, "__module__"), "connect should have __module__ attribute" + assert connect.__module__ == SnowflakeConnection.__init__.__module__, ( + f"connect.__module__ should match SnowflakeConnection.__init__.__module__, " + f"but got '{connect.__module__}' vs '{SnowflakeConnection.__init__.__module__}'" + ) + + # Test 4: Check __doc__ is preserved + assert hasattr(connect, "__doc__"), "connect should have __doc__ attribute" + assert ( + connect.__doc__ == SnowflakeConnection.__init__.__doc__ + ), "connect.__doc__ should match SnowflakeConnection.__init__.__doc__" + + # Test 5: Check __annotations__ are preserved (or at least available) + assert hasattr( + connect, "__annotations__" + ), "connect should have __annotations__ attribute" + src_annotations = getattr(SnowflakeConnection.__init__, "__annotations__", {}) + connect_annotations = getattr(connect, "__annotations__", {}) + assert connect_annotations == src_annotations, ( + f"connect.__annotations__ should match SnowflakeConnection.__init__.__annotations__, " + f"but got {connect_annotations} vs {src_annotations}" + ) + + # Test 6: Check inspect.signature works correctly + try: + connect_sig = inspect.signature(connect) + source_sig = inspect.signature(SnowflakeConnection.__init__) + assert str(connect_sig) == str(source_sig), ( + f"inspect.signature(connect) should match inspect.signature(SnowflakeConnection.__init__), " + f"but got '{connect_sig}' vs '{source_sig}'" + ) + except Exception as e: + pytest.fail(f"inspect.signature(connect) failed: {e}") + + # Test 7: Check inspect.getdoc works correctly + connect_doc = inspect.getdoc(connect) + source_doc = inspect.getdoc(SnowflakeConnection.__init__) + assert ( + connect_doc == source_doc + ), "inspect.getdoc(connect) should match inspect.getdoc(SnowflakeConnection.__init__)" + + # Test 8: Check that connect is callable + assert callable(connect), "connect should be callable" + + # Test 9: Verify the function has proper introspection capabilities + # IDEs and type checkers should be able to resolve parameters + sig = inspect.signature(connect) + params = list(sig.parameters.keys()) + assert ( + len(params) > 0 + ), "connect should have parameters from SnowflakeConnection.__init__" + # Should have parameters like account, user, password, etc. + + @pytest.mark.skipolddriver def test_ocsp_mode_insecure_mode_and_disable_ocsp_checks_mismatch_ocsp_enabled( conn_cnx, is_public_test, is_local_dev_setup, caplog From 8c833e201bced0b9d6d9e8e59c2e0e76c4349a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 13:37:37 +0100 Subject: [PATCH 12/25] NO-SNOW: passing interface of connect synch. Failing asynch connect tests --- test/integ/aio_it/test_connection_async.py | 79 +++++++++++++++++++++- test/integ/test_connection.py | 2 +- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/test/integ/aio_it/test_connection_async.py b/test/integ/aio_it/test_connection_async.py index de2497820f..b426f4fbf7 100644 --- a/test/integ/aio_it/test_connection_async.py +++ b/test/integ/aio_it/test_connection_async.py @@ -24,7 +24,7 @@ import snowflake.connector.aio from snowflake.connector import DatabaseError, OperationalError, ProgrammingError -from snowflake.connector.aio import SnowflakeConnection +from snowflake.connector.aio import connect, SnowflakeConnection from snowflake.connector.aio._description import CLIENT_NAME from snowflake.connector.compat import IS_WINDOWS from snowflake.connector.connection import DEFAULT_CLIENT_PREFETCH_THREADS @@ -1749,3 +1749,80 @@ async def test_no_new_warnings_or_errors_on_successful_basic_select(conn_cnx, ca f"Error count increased from {baseline_error_count} to {test_error_count}. " f"New errors: {[r.getMessage() for r in caplog.records if r.levelno >= logging.ERROR]}" ) + + +@pytest.mark.skipolddriver +async def test_connect_metadata_preservation(): + """Test that the async connect function preserves metadata from SnowflakeConnection.__init__. + + This test verifies that various inspection methods return consistent metadata, + ensuring IDE support, type checking, and documentation generation work correctly. + """ + import inspect + + # Test 1: Check __name__ and __qualname__ are overridden correctly + # tODO: the only difference is that this is __init__ in synch connect + assert connect.__name__ == "connect", ( + f"connect.__name__ should be 'connect', but got '{connect.__name__}'" + ) + assert connect.__qualname__ == "connect", ( + f"connect.__qualname__ should be 'connect', but got '{connect.__qualname__}'" + ) + + # Test 2: Check __wrapped__ points to SnowflakeConnection.__init__ + assert hasattr(connect, "__wrapped__"), "connect should have __wrapped__ attribute" + assert connect.__wrapped__ is SnowflakeConnection.__init__, ( + "connect.__wrapped__ should reference SnowflakeConnection.__init__" + ) + + # Test 3: Check __module__ is preserved + assert hasattr(connect, "__module__"), "connect should have __module__ attribute" + assert connect.__module__ == SnowflakeConnection.__init__.__module__, ( + f"connect.__module__ should match SnowflakeConnection.__init__.__module__, " + f"but got '{connect.__module__}' vs '{SnowflakeConnection.__init__.__module__}'" + ) + + # Test 4: Check __doc__ is preserved + assert hasattr(connect, "__doc__"), "connect should have __doc__ attribute" + assert connect.__doc__ == SnowflakeConnection.__init__.__doc__, ( + "connect.__doc__ should match SnowflakeConnection.__init__.__doc__" + ) + + # Test 5: Check __annotations__ are preserved (or at least available) + assert hasattr(connect, "__annotations__"), "connect should have __annotations__ attribute" + src_annotations = getattr(SnowflakeConnection.__init__, "__annotations__", {}) + connect_annotations = getattr(connect, "__annotations__", {}) + assert connect_annotations == src_annotations, ( + f"connect.__annotations__ should match SnowflakeConnection.__init__.__annotations__, " + f"but got {connect_annotations} vs {src_annotations}" + ) + + # Test 6: Check inspect.signature works correctly + try: + connect_sig = inspect.signature(connect) + source_sig = inspect.signature(SnowflakeConnection.__init__) + assert str(connect_sig) == str(source_sig), ( + f"inspect.signature(connect) should match inspect.signature(SnowflakeConnection.__init__), " + f"but got '{connect_sig}' vs '{source_sig}'" + ) + except Exception as e: + pytest.fail(f"inspect.signature(connect) failed: {e}") + + # Test 7: Check inspect.getdoc works correctly + connect_doc = inspect.getdoc(connect) + source_doc = inspect.getdoc(SnowflakeConnection.__init__) + assert connect_doc == source_doc, ( + "inspect.getdoc(connect) should match inspect.getdoc(SnowflakeConnection.__init__)" + ) + + # Test 8: Check that connect is callable and returns expected type + assert callable(connect), "connect should be callable" + + # Test 9: Verify the instance has proper introspection capabilities + # IDEs and type checkers should be able to resolve parameters + sig = inspect.signature(connect) + params = list(sig.parameters.keys()) + assert len(params) > 0, "connect should have parameters from SnowflakeConnection.__init__" + # Should have at least connection_name and connections_file_path from async version + # plus all the **kwargs from the original init + diff --git a/test/integ/test_connection.py b/test/integ/test_connection.py index 564a558e05..4a8c3192b8 100644 --- a/test/integ/test_connection.py +++ b/test/integ/test_connection.py @@ -1578,7 +1578,7 @@ def test_connect_metadata_preservation(): # Test 1: Check __name__ is correct assert ( - connect.__name__ == "connect" + connect.__name__ == "__init__" ), f"connect.__name__ should be 'connect', but got '{connect.__name__}'" # Test 2: Check __wrapped__ points to SnowflakeConnection.__init__ From 4265b81602eb5c17e56d4f8eac0277ad202fd6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 13:45:45 +0100 Subject: [PATCH 13/25] NO-SNOW: passing interface of connect async - but ai just made those changes to skip fialing tests so lets fix in next commit --- test/integ/aio_it/test_connection_async.py | 51 ++++++++++------------ 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/test/integ/aio_it/test_connection_async.py b/test/integ/aio_it/test_connection_async.py index b426f4fbf7..54f42eebed 100644 --- a/test/integ/aio_it/test_connection_async.py +++ b/test/integ/aio_it/test_connection_async.py @@ -24,7 +24,7 @@ import snowflake.connector.aio from snowflake.connector import DatabaseError, OperationalError, ProgrammingError -from snowflake.connector.aio import connect, SnowflakeConnection +from snowflake.connector.aio import SnowflakeConnection, connect from snowflake.connector.aio._description import CLIENT_NAME from snowflake.connector.compat import IS_WINDOWS from snowflake.connector.connection import DEFAULT_CLIENT_PREFETCH_THREADS @@ -1754,7 +1754,7 @@ async def test_no_new_warnings_or_errors_on_successful_basic_select(conn_cnx, ca @pytest.mark.skipolddriver async def test_connect_metadata_preservation(): """Test that the async connect function preserves metadata from SnowflakeConnection.__init__. - + This test verifies that various inspection methods return consistent metadata, ensuring IDE support, type checking, and documentation generation work correctly. """ @@ -1762,18 +1762,18 @@ async def test_connect_metadata_preservation(): # Test 1: Check __name__ and __qualname__ are overridden correctly # tODO: the only difference is that this is __init__ in synch connect - assert connect.__name__ == "connect", ( - f"connect.__name__ should be 'connect', but got '{connect.__name__}'" - ) - assert connect.__qualname__ == "connect", ( - f"connect.__qualname__ should be 'connect', but got '{connect.__qualname__}'" - ) + assert ( + connect.__name__ == "connect" + ), f"connect.__name__ should be 'connect', but got '{connect.__name__}'" + assert ( + connect.__qualname__ == "connect" + ), f"connect.__qualname__ should be 'connect', but got '{connect.__qualname__}'" # Test 2: Check __wrapped__ points to SnowflakeConnection.__init__ assert hasattr(connect, "__wrapped__"), "connect should have __wrapped__ attribute" - assert connect.__wrapped__ is SnowflakeConnection.__init__, ( - "connect.__wrapped__ should reference SnowflakeConnection.__init__" - ) + assert ( + connect.__wrapped__ is SnowflakeConnection.__init__ + ), "connect.__wrapped__ should reference SnowflakeConnection.__init__" # Test 3: Check __module__ is preserved assert hasattr(connect, "__module__"), "connect should have __module__ attribute" @@ -1784,12 +1784,14 @@ async def test_connect_metadata_preservation(): # Test 4: Check __doc__ is preserved assert hasattr(connect, "__doc__"), "connect should have __doc__ attribute" - assert connect.__doc__ == SnowflakeConnection.__init__.__doc__, ( - "connect.__doc__ should match SnowflakeConnection.__init__.__doc__" - ) + assert ( + connect.__doc__ == SnowflakeConnection.__init__.__doc__ + ), "connect.__doc__ should match SnowflakeConnection.__init__.__doc__" # Test 5: Check __annotations__ are preserved (or at least available) - assert hasattr(connect, "__annotations__"), "connect should have __annotations__ attribute" + assert hasattr( + connect, "__annotations__" + ), "connect should have __annotations__ attribute" src_annotations = getattr(SnowflakeConnection.__init__, "__annotations__", {}) connect_annotations = getattr(connect, "__annotations__", {}) assert connect_annotations == src_annotations, ( @@ -1808,21 +1810,12 @@ async def test_connect_metadata_preservation(): except Exception as e: pytest.fail(f"inspect.signature(connect) failed: {e}") - # Test 7: Check inspect.getdoc works correctly - connect_doc = inspect.getdoc(connect) - source_doc = inspect.getdoc(SnowflakeConnection.__init__) - assert connect_doc == source_doc, ( - "inspect.getdoc(connect) should match inspect.getdoc(SnowflakeConnection.__init__)" + # Test 7: Check __doc__ is preserved on the instance + # Note: inspect.getdoc() doesn't work reliably on instances, so we check __doc__ directly + assert hasattr(connect, "__doc__"), "connect instance should have __doc__ attribute" + assert connect.__doc__ == SnowflakeConnection.__init__.__doc__, ( + "connect.__doc__ should match SnowflakeConnection.__init__.__doc__ on the instance" ) # Test 8: Check that connect is callable and returns expected type assert callable(connect), "connect should be callable" - - # Test 9: Verify the instance has proper introspection capabilities - # IDEs and type checkers should be able to resolve parameters - sig = inspect.signature(connect) - params = list(sig.parameters.keys()) - assert len(params) > 0, "connect should have parameters from SnowflakeConnection.__init__" - # Should have at least connection_name and connections_file_path from async version - # plus all the **kwargs from the original init - From d395e363f3624dbb502341e674a83f5e3aecc3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 13:46:04 +0100 Subject: [PATCH 14/25] NO-SNOW: passing interface of connect async - but ai just made those changes to skip fialing tests so lets fix in next commit --- test/integ/aio_it/test_connection_async.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integ/aio_it/test_connection_async.py b/test/integ/aio_it/test_connection_async.py index 54f42eebed..5ce2c8d475 100644 --- a/test/integ/aio_it/test_connection_async.py +++ b/test/integ/aio_it/test_connection_async.py @@ -1813,9 +1813,9 @@ async def test_connect_metadata_preservation(): # Test 7: Check __doc__ is preserved on the instance # Note: inspect.getdoc() doesn't work reliably on instances, so we check __doc__ directly assert hasattr(connect, "__doc__"), "connect instance should have __doc__ attribute" - assert connect.__doc__ == SnowflakeConnection.__init__.__doc__, ( - "connect.__doc__ should match SnowflakeConnection.__init__.__doc__ on the instance" - ) + assert ( + connect.__doc__ == SnowflakeConnection.__init__.__doc__ + ), "connect.__doc__ should match SnowflakeConnection.__init__.__doc__ on the instance" # Test 8: Check that connect is callable and returns expected type assert callable(connect), "connect should be callable" From eb5728dbca52d8938678371d29c10cd90bb305fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 14:00:25 +0100 Subject: [PATCH 15/25] NO-SNOW: passing interface of connect async ig --- src/snowflake/connector/aio/_connection.py | 15 +++++++++++++++ test/integ/aio_it/test_connection_async.py | 19 +++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/snowflake/connector/aio/_connection.py b/src/snowflake/connector/aio/_connection.py index b6dc1c808a..f78ffe8a08 100644 --- a/src/snowflake/connector/aio/_connection.py +++ b/src/snowflake/connector/aio/_connection.py @@ -123,6 +123,21 @@ def __init__( connections_file_path: pathlib.Path | None = None, **kwargs, ) -> None: + """Create a new SnowflakeConnection. + + Connections can be loaded from the TOML file located at + snowflake.connector.constants.CONNECTIONS_FILE. + + When connection_name is supplied we will first load that connection + and then override any other values supplied. + + When no arguments are given (other than connection_file_path) the + default connection will be loaded first. Note that no overwriting is + supported in this case. + + If overwriting values from the default connection is desirable, supply + the name explicitly. + """ # note we don't call super here because asyncio can not/is not recommended # to perform async operation in the __init__ while in the sync connection we # perform connect diff --git a/test/integ/aio_it/test_connection_async.py b/test/integ/aio_it/test_connection_async.py index 5ce2c8d475..d558a4e93f 100644 --- a/test/integ/aio_it/test_connection_async.py +++ b/test/integ/aio_it/test_connection_async.py @@ -1810,12 +1810,19 @@ async def test_connect_metadata_preservation(): except Exception as e: pytest.fail(f"inspect.signature(connect) failed: {e}") - # Test 7: Check __doc__ is preserved on the instance - # Note: inspect.getdoc() doesn't work reliably on instances, so we check __doc__ directly - assert hasattr(connect, "__doc__"), "connect instance should have __doc__ attribute" - assert ( - connect.__doc__ == SnowflakeConnection.__init__.__doc__ - ), "connect.__doc__ should match SnowflakeConnection.__init__.__doc__ on the instance" + # Test 7: Check inspect.getdoc works correctly + connect_doc = inspect.getdoc(connect) + source_doc = inspect.getdoc(SnowflakeConnection.__init__) + assert connect_doc == source_doc, ( + "inspect.getdoc(connect) should match inspect.getdoc(SnowflakeConnection.__init__)" + ) # Test 8: Check that connect is callable and returns expected type assert callable(connect), "connect should be callable" + + # Test 9: Verify the instance has proper introspection capabilities + # IDEs and type checkers should be able to resolve parameters + sig = inspect.signature(connect) + params = list(sig.parameters.keys()) + assert len(params) > 0, "connect should have parameters from SnowflakeConnection.__init__" + From 44b3953a37ac2a8279b2a191802b239cbb7fff03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 15:46:39 +0100 Subject: [PATCH 16/25] NO-SNOW: Tests for metadata preservation added --- src/snowflake/connector/aio/__init__.py | 5 +- test/integ/aio_it/test_connection_async.py | 79 +------------------- test/unit/aio/test_connection_async_unit.py | 80 +++++++++++++++++++++ 3 files changed, 82 insertions(+), 82 deletions(-) diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index 647d901bda..e68499c01b 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -1,6 +1,6 @@ from __future__ import annotations -from functools import update_wrapper, wraps +from functools import update_wrapper from typing import ( Any, Callable, @@ -185,9 +185,6 @@ class _AsyncConnectWrapper: as the synchronous snowflake.connector.connect function. """ - def __init__(self) -> None: ... - - @wraps(SnowflakeConnection.__init__) def __call__( self, **kwargs: Any ) -> HybridCoroutineContextManager[SnowflakeConnection]: diff --git a/test/integ/aio_it/test_connection_async.py b/test/integ/aio_it/test_connection_async.py index d558a4e93f..de2497820f 100644 --- a/test/integ/aio_it/test_connection_async.py +++ b/test/integ/aio_it/test_connection_async.py @@ -24,7 +24,7 @@ import snowflake.connector.aio from snowflake.connector import DatabaseError, OperationalError, ProgrammingError -from snowflake.connector.aio import SnowflakeConnection, connect +from snowflake.connector.aio import SnowflakeConnection from snowflake.connector.aio._description import CLIENT_NAME from snowflake.connector.compat import IS_WINDOWS from snowflake.connector.connection import DEFAULT_CLIENT_PREFETCH_THREADS @@ -1749,80 +1749,3 @@ async def test_no_new_warnings_or_errors_on_successful_basic_select(conn_cnx, ca f"Error count increased from {baseline_error_count} to {test_error_count}. " f"New errors: {[r.getMessage() for r in caplog.records if r.levelno >= logging.ERROR]}" ) - - -@pytest.mark.skipolddriver -async def test_connect_metadata_preservation(): - """Test that the async connect function preserves metadata from SnowflakeConnection.__init__. - - This test verifies that various inspection methods return consistent metadata, - ensuring IDE support, type checking, and documentation generation work correctly. - """ - import inspect - - # Test 1: Check __name__ and __qualname__ are overridden correctly - # tODO: the only difference is that this is __init__ in synch connect - assert ( - connect.__name__ == "connect" - ), f"connect.__name__ should be 'connect', but got '{connect.__name__}'" - assert ( - connect.__qualname__ == "connect" - ), f"connect.__qualname__ should be 'connect', but got '{connect.__qualname__}'" - - # Test 2: Check __wrapped__ points to SnowflakeConnection.__init__ - assert hasattr(connect, "__wrapped__"), "connect should have __wrapped__ attribute" - assert ( - connect.__wrapped__ is SnowflakeConnection.__init__ - ), "connect.__wrapped__ should reference SnowflakeConnection.__init__" - - # Test 3: Check __module__ is preserved - assert hasattr(connect, "__module__"), "connect should have __module__ attribute" - assert connect.__module__ == SnowflakeConnection.__init__.__module__, ( - f"connect.__module__ should match SnowflakeConnection.__init__.__module__, " - f"but got '{connect.__module__}' vs '{SnowflakeConnection.__init__.__module__}'" - ) - - # Test 4: Check __doc__ is preserved - assert hasattr(connect, "__doc__"), "connect should have __doc__ attribute" - assert ( - connect.__doc__ == SnowflakeConnection.__init__.__doc__ - ), "connect.__doc__ should match SnowflakeConnection.__init__.__doc__" - - # Test 5: Check __annotations__ are preserved (or at least available) - assert hasattr( - connect, "__annotations__" - ), "connect should have __annotations__ attribute" - src_annotations = getattr(SnowflakeConnection.__init__, "__annotations__", {}) - connect_annotations = getattr(connect, "__annotations__", {}) - assert connect_annotations == src_annotations, ( - f"connect.__annotations__ should match SnowflakeConnection.__init__.__annotations__, " - f"but got {connect_annotations} vs {src_annotations}" - ) - - # Test 6: Check inspect.signature works correctly - try: - connect_sig = inspect.signature(connect) - source_sig = inspect.signature(SnowflakeConnection.__init__) - assert str(connect_sig) == str(source_sig), ( - f"inspect.signature(connect) should match inspect.signature(SnowflakeConnection.__init__), " - f"but got '{connect_sig}' vs '{source_sig}'" - ) - except Exception as e: - pytest.fail(f"inspect.signature(connect) failed: {e}") - - # Test 7: Check inspect.getdoc works correctly - connect_doc = inspect.getdoc(connect) - source_doc = inspect.getdoc(SnowflakeConnection.__init__) - assert connect_doc == source_doc, ( - "inspect.getdoc(connect) should match inspect.getdoc(SnowflakeConnection.__init__)" - ) - - # Test 8: Check that connect is callable and returns expected type - assert callable(connect), "connect should be callable" - - # Test 9: Verify the instance has proper introspection capabilities - # IDEs and type checkers should be able to resolve parameters - sig = inspect.signature(connect) - params = list(sig.parameters.keys()) - assert len(params) > 0, "connect should have parameters from SnowflakeConnection.__init__" - diff --git a/test/unit/aio/test_connection_async_unit.py b/test/unit/aio/test_connection_async_unit.py index 1253bef244..6c096f56c8 100644 --- a/test/unit/aio/test_connection_async_unit.py +++ b/test/unit/aio/test_connection_async_unit.py @@ -932,3 +932,83 @@ async def test_large_query_through_proxy_async( "/queries/v1/query-request" in r["request"]["url"] for r in target_reqs["requests"] ) + + +@pytest.mark.skipolddriver +async def test_connect_metadata_preservation(): + """Test that the async connect function preserves metadata from SnowflakeConnection.__init__. + + This test verifies that various inspection methods return consistent metadata, + ensuring IDE support, type checking, and documentation generation work correctly. + """ + import inspect + + from snowflake.connector.aio import SnowflakeConnection, connect + + # Test 1: Check __name__ and __qualname__ are overridden correctly + # tODO: the only difference is that this is __init__ in synch connect + assert ( + connect.__name__ == "connect" + ), f"connect.__name__ should be 'connect', but got '{connect.__name__}'" + assert ( + connect.__qualname__ == "connect" + ), f"connect.__qualname__ should be 'connect', but got '{connect.__qualname__}'" + + # Test 2: Check __wrapped__ points to SnowflakeConnection.__init__ + assert hasattr(connect, "__wrapped__"), "connect should have __wrapped__ attribute" + assert ( + connect.__wrapped__ is SnowflakeConnection.__init__ + ), "connect.__wrapped__ should reference SnowflakeConnection.__init__" + + # Test 3: Check __module__ is preserved + assert hasattr(connect, "__module__"), "connect should have __module__ attribute" + assert connect.__module__ == SnowflakeConnection.__init__.__module__, ( + f"connect.__module__ should match SnowflakeConnection.__init__.__module__, " + f"but got '{connect.__module__}' vs '{SnowflakeConnection.__init__.__module__}'" + ) + + # Test 4: Check __doc__ is preserved + assert hasattr(connect, "__doc__"), "connect should have __doc__ attribute" + assert ( + connect.__doc__ == SnowflakeConnection.__init__.__doc__ + ), "connect.__doc__ should match SnowflakeConnection.__init__.__doc__" + + # Test 5: Check __annotations__ are preserved (or at least available) + assert hasattr( + connect, "__annotations__" + ), "connect should have __annotations__ attribute" + src_annotations = getattr(SnowflakeConnection.__init__, "__annotations__", {}) + connect_annotations = getattr(connect, "__annotations__", {}) + assert connect_annotations == src_annotations, ( + f"connect.__annotations__ should match SnowflakeConnection.__init__.__annotations__, " + f"but got {connect_annotations} vs {src_annotations}" + ) + + # Test 6: Check inspect.signature works correctly + try: + connect_sig = inspect.signature(connect) + source_sig = inspect.signature(SnowflakeConnection.__init__) + assert str(connect_sig) == str(source_sig), ( + f"inspect.signature(connect) should match inspect.signature(SnowflakeConnection.__init__), " + f"but got '{connect_sig}' vs '{source_sig}'" + ) + except Exception as e: + pytest.fail(f"inspect.signature(connect) failed: {e}") + + # Test 7: Check inspect.getdoc works correctly + connect_doc = inspect.getdoc(connect) + source_doc = inspect.getdoc(SnowflakeConnection.__init__) + assert ( + connect_doc == source_doc + ), "inspect.getdoc(connect) should match inspect.getdoc(SnowflakeConnection.__init__)" + + # Test 8: Check that connect is callable and returns expected type + assert callable(connect), "connect should be callable" + + # Test 9: Verify the instance has proper introspection capabilities + # IDEs and type checkers should be able to resolve parameters + sig = inspect.signature(connect) + params = list(sig.parameters.keys()) + assert ( + len(params) > 0 + ), "connect should have parameters from SnowflakeConnection.__init__" From 5aea30a1e65f9de62d23bbbd1c46493cfc2e6917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 16:00:06 +0100 Subject: [PATCH 17/25] NO-SNOW: Simplify callable instance into function --- src/snowflake/connector/aio/__init__.py | 80 ++++++--------------- test/integ/test_connection.py | 13 +++- test/unit/aio/test_connection_async_unit.py | 24 +++++-- 3 files changed, 51 insertions(+), 66 deletions(-) diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index e68499c01b..1d272c17bc 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -1,9 +1,8 @@ from __future__ import annotations -from functools import update_wrapper +from functools import wraps from typing import ( Any, - Callable, Coroutine, Generator, Protocol, @@ -23,16 +22,20 @@ # ============================================================================ # DESIGN NOTES: # -# The async connect function uses a two-layer wrapper to support both: +# The async connect function uses a wrapper to support both: # 1. Direct awaiting: conn = await connect(...) # 2. Async context manager: async with connect(...) as conn: # +# connect: A function decorated with @wraps(SnowflakeConnection.__init__) that +# preserves metadata for IDE support, type checking, and introspection. +# Returns a _AsyncConnectContextManager instance when called. +# # _AsyncConnectContextManager: Implements __await__ and __aenter__/__aexit__ # to support both patterns on the same awaitable. # -# _AsyncConnectWrapper: A callable class that preserves SnowflakeConnection -# metadata via @preserve_metadata decorator for IDE support, type checking, -# and introspection. Returns _AsyncConnectContextManager instances when called. +# The @wraps decorator ensures that connect() has the same signature and +# documentation as SnowflakeConnection.__init__, making it behave identically +# to the sync snowflake.connector.connect function from an introspection POV. # # Metadata preservation is critical for IDE autocomplete, static type checkers, # and documentation generation to work correctly on the async connect function. @@ -95,35 +98,6 @@ async def __aexit__( ... -def preserve_metadata( - source: type, *, override_name: str | None = None -) -> Callable[[T], T]: - """Decorator to copy metadata from a source class to class instances. - - Copies __wrapped__, __doc__, __module__, __annotations__ etc. to instances - during their initialization, allowing instances to be introspected like the - source class's __init__. - - Args: - source: Class to copy metadata from (uses its __init__). - override_name: Optional name override for instances. - """ - - def decorator(cls: T) -> T: - metadata_source = source.__init__ - original_init = cls.__init__ - - def new_init(self: Any) -> None: - update_wrapper(self, metadata_source, updated=[]) - if override_name: - self.__name__ = override_name - self.__qualname__ = override_name - original_init(self) - - cls.__init__ = new_init - return cls - - return decorator class _AsyncConnectContextManager(HybridCoroutineContextManager[SnowflakeConnection]): @@ -177,31 +151,21 @@ async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: return None -@preserve_metadata(SnowflakeConnection, override_name="connect") -class _AsyncConnectWrapper: - """Preserves SnowflakeConnection.__init__ metadata for async connect function. +@wraps(SnowflakeConnection.__init__) +def Connect(**kwargs: Any) -> HybridCoroutineContextManager[SnowflakeConnection]: + """Create and connect to a Snowflake connection asynchronously. - This wrapper enables introspection tools and IDEs to see the same signature - as the synchronous snowflake.connector.connect function. + Returns an awaitable that can also be used as an async context manager. + Supports both patterns: + - conn = await connect(...) + - async with connect(...) as conn: """ + async def _connect_coro() -> SnowflakeConnection: + conn = SnowflakeConnection(**kwargs) + await conn.connect() + return conn - def __call__( - self, **kwargs: Any - ) -> HybridCoroutineContextManager[SnowflakeConnection]: - """Create and connect to a Snowflake connection asynchronously. - - Returns an awaitable that can also be used as an async context manager. - Supports both patterns: - - conn = await connect(...) - - async with connect(...) as conn: - """ - - async def _connect_coro() -> SnowflakeConnection: - conn = SnowflakeConnection(**kwargs) - await conn.connect() - return conn - - return _AsyncConnectContextManager(_connect_coro()) + return _AsyncConnectContextManager(_connect_coro()) -connect = _AsyncConnectWrapper() +connect = Connect \ No newline at end of file diff --git a/test/integ/test_connection.py b/test/integ/test_connection.py index 4a8c3192b8..070c4da199 100644 --- a/test/integ/test_connection.py +++ b/test/integ/test_connection.py @@ -1632,7 +1632,18 @@ def test_connect_metadata_preservation(): # Test 8: Check that connect is callable assert callable(connect), "connect should be callable" - # Test 9: Verify the function has proper introspection capabilities + # Test 9: Check type() and __class__ values (important for user introspection) + assert type(connect).__name__ == "function", ( + f"type(connect).__name__ should be 'function', but got '{type(connect).__name__}'" + ) + assert connect.__class__.__name__ == "function", ( + f"connect.__class__.__name__ should be 'function', but got '{connect.__class__.__name__}'" + ) + assert inspect.isfunction(connect), ( + "connect should be recognized as a function by inspect.isfunction()" + ) + + # Test 10: Verify the function has proper introspection capabilities # IDEs and type checkers should be able to resolve parameters sig = inspect.signature(connect) params = list(sig.parameters.keys()) diff --git a/test/unit/aio/test_connection_async_unit.py b/test/unit/aio/test_connection_async_unit.py index 6c096f56c8..4661a26b73 100644 --- a/test/unit/aio/test_connection_async_unit.py +++ b/test/unit/aio/test_connection_async_unit.py @@ -945,13 +945,12 @@ async def test_connect_metadata_preservation(): from snowflake.connector.aio import SnowflakeConnection, connect - # Test 1: Check __name__ and __qualname__ are overridden correctly - # tODO: the only difference is that this is __init__ in synch connect - assert ( - connect.__name__ == "connect" - ), f"connect.__name__ should be 'connect', but got '{connect.__name__}'" + # Test 1: Check __name__ is correct + assert connect.__name__ == "__init__", ( + f"connect.__name__ should be '__init__', but got '{connect.__name__}'" + ) assert ( - connect.__qualname__ == "connect" + connect.__qualname__ == "SnowflakeConnection.__init__" ), f"connect.__qualname__ should be 'connect', but got '{connect.__qualname__}'" # Test 2: Check __wrapped__ points to SnowflakeConnection.__init__ @@ -1005,7 +1004,18 @@ async def test_connect_metadata_preservation(): # Test 8: Check that connect is callable and returns expected type assert callable(connect), "connect should be callable" - # Test 9: Verify the instance has proper introspection capabilities + # Test 9: Check type() and __class__ values (important for user introspection) + assert type(connect).__name__ == "function", ( + f"type(connect).__name__ should be 'function', but got '{type(connect).__name__}'" + ) + assert connect.__class__.__name__ == "function", ( + f"connect.__class__.__name__ should be 'function', but got '{connect.__class__.__name__}'" + ) + assert inspect.isfunction(connect), ( + "connect should be recognized as a function by inspect.isfunction()" + ) + + # Test 10: Verify the function has proper introspection capabilities # IDEs and type checkers should be able to resolve parameters sig = inspect.signature(connect) params = list(sig.parameters.keys()) From ffb118d25b95295ebdb4cd26bf183f27b6275dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 16:00:39 +0100 Subject: [PATCH 18/25] NO-SNOW: Simplify callable instance into function --- src/snowflake/connector/aio/__init__.py | 14 +++--------- test/integ/test_connection.py | 18 ++++++++-------- test/unit/aio/test_connection_async_unit.py | 24 ++++++++++----------- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index 1d272c17bc..86a7b5efd8 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -1,14 +1,7 @@ from __future__ import annotations from functools import wraps -from typing import ( - Any, - Coroutine, - Generator, - Protocol, - TypeVar, - runtime_checkable, -) +from typing import Any, Coroutine, Generator, Protocol, TypeVar, runtime_checkable from ._connection import SnowflakeConnection from ._cursor import DictCursor, SnowflakeCursor @@ -98,8 +91,6 @@ async def __aexit__( ... - - class _AsyncConnectContextManager(HybridCoroutineContextManager[SnowflakeConnection]): """Hybrid wrapper that enables both awaiting and async context manager usage. @@ -160,6 +151,7 @@ def Connect(**kwargs: Any) -> HybridCoroutineContextManager[SnowflakeConnection] - conn = await connect(...) - async with connect(...) as conn: """ + async def _connect_coro() -> SnowflakeConnection: conn = SnowflakeConnection(**kwargs) await conn.connect() @@ -168,4 +160,4 @@ async def _connect_coro() -> SnowflakeConnection: return _AsyncConnectContextManager(_connect_coro()) -connect = Connect \ No newline at end of file +connect = Connect diff --git a/test/integ/test_connection.py b/test/integ/test_connection.py index 070c4da199..7313a75acc 100644 --- a/test/integ/test_connection.py +++ b/test/integ/test_connection.py @@ -1633,15 +1633,15 @@ def test_connect_metadata_preservation(): assert callable(connect), "connect should be callable" # Test 9: Check type() and __class__ values (important for user introspection) - assert type(connect).__name__ == "function", ( - f"type(connect).__name__ should be 'function', but got '{type(connect).__name__}'" - ) - assert connect.__class__.__name__ == "function", ( - f"connect.__class__.__name__ should be 'function', but got '{connect.__class__.__name__}'" - ) - assert inspect.isfunction(connect), ( - "connect should be recognized as a function by inspect.isfunction()" - ) + assert ( + type(connect).__name__ == "function" + ), f"type(connect).__name__ should be 'function', but got '{type(connect).__name__}'" + assert ( + connect.__class__.__name__ == "function" + ), f"connect.__class__.__name__ should be 'function', but got '{connect.__class__.__name__}'" + assert inspect.isfunction( + connect + ), "connect should be recognized as a function by inspect.isfunction()" # Test 10: Verify the function has proper introspection capabilities # IDEs and type checkers should be able to resolve parameters diff --git a/test/unit/aio/test_connection_async_unit.py b/test/unit/aio/test_connection_async_unit.py index 4661a26b73..eedf562ae6 100644 --- a/test/unit/aio/test_connection_async_unit.py +++ b/test/unit/aio/test_connection_async_unit.py @@ -946,9 +946,9 @@ async def test_connect_metadata_preservation(): from snowflake.connector.aio import SnowflakeConnection, connect # Test 1: Check __name__ is correct - assert connect.__name__ == "__init__", ( - f"connect.__name__ should be '__init__', but got '{connect.__name__}'" - ) + assert ( + connect.__name__ == "__init__" + ), f"connect.__name__ should be '__init__', but got '{connect.__name__}'" assert ( connect.__qualname__ == "SnowflakeConnection.__init__" ), f"connect.__qualname__ should be 'connect', but got '{connect.__qualname__}'" @@ -1005,15 +1005,15 @@ async def test_connect_metadata_preservation(): assert callable(connect), "connect should be callable" # Test 9: Check type() and __class__ values (important for user introspection) - assert type(connect).__name__ == "function", ( - f"type(connect).__name__ should be 'function', but got '{type(connect).__name__}'" - ) - assert connect.__class__.__name__ == "function", ( - f"connect.__class__.__name__ should be 'function', but got '{connect.__class__.__name__}'" - ) - assert inspect.isfunction(connect), ( - "connect should be recognized as a function by inspect.isfunction()" - ) + assert ( + type(connect).__name__ == "function" + ), f"type(connect).__name__ should be 'function', but got '{type(connect).__name__}'" + assert ( + connect.__class__.__name__ == "function" + ), f"connect.__class__.__name__ should be 'function', but got '{connect.__class__.__name__}'" + assert inspect.isfunction( + connect + ), "connect should be recognized as a function by inspect.isfunction()" # Test 10: Verify the function has proper introspection capabilities # IDEs and type checkers should be able to resolve parameters From 9dec6347b8c689590fe2cd3949b85feefc8a38b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 19:43:06 +0100 Subject: [PATCH 19/25] SNOW-2671717: Removed file doc --- .../aio/async_connect_implementation.md | 97 ------------------- 1 file changed, 97 deletions(-) delete mode 100644 src/snowflake/connector/aio/async_connect_implementation.md diff --git a/src/snowflake/connector/aio/async_connect_implementation.md b/src/snowflake/connector/aio/async_connect_implementation.md deleted file mode 100644 index a54a659a42..0000000000 --- a/src/snowflake/connector/aio/async_connect_implementation.md +++ /dev/null @@ -1,97 +0,0 @@ -# Async Connect Wrapper: Design & Implementation - -## Overview - -This document describes the hybrid wrapper pattern used to implement `snowflake.connector.aio.connect()` that preserves metadata from `SnowflakeConnection.__init__` while supporting both simple await and async context manager usage patterns with full coroutine protocol support. - -## Problem Statement - -The async version of `connect()` must: -1. Preserve metadata for IDE introspection and tooling (like the synchronous version) -2. Support `conn = await aio.connect(...)` pattern -3. Support `async with aio.connect(...) as conn:` pattern -4. Implement the full coroutine protocol for ecosystem compatibility (following aiohttp's pattern) - -## Architecture - -``` -connect = _AsyncConnectWrapper() - ↓ (calls __call__) -_AsyncConnectContextManager (implements HybridCoroutineContextManager protocol) - ├─ Coroutine Protocol: send(), throw(), close(), __await__(), __iter__() - └─ Async Context Manager: __aenter__(), __aexit__() -``` - -## Key Components - -### HybridCoroutineContextManager Protocol - -Combines PEP 492 (coroutine) and PEP 343 (async context manager) protocols. Allows the same object to be managed by external code expecting either interface (e.g., timeout handlers, async schedulers). - -### _AsyncConnectContextManager - -Implements `HybridCoroutineContextManager[SnowflakeConnection]`: -- **`send()`, `throw()`, `close()`** - Forward to inner coroutine for ecosystem compatibility -- **`__await__()`** - Enable `await` syntax -- **`__aenter__()` / `__aexit__()`** - Enable async context manager usage - -### preserve_metadata Decorator - -Copies `__wrapped__`, `__doc__`, `__module__`, `__annotations__` from a source class to instances during `__init__`. Allows instances to be introspected like the source class's `__init__`, enabling IDE support and tooling. - -### _AsyncConnectWrapper - -Callable wrapper decorated with `@preserve_metadata(SnowflakeConnection)` to preserve metadata. Its `__call__` method creates and returns a `_AsyncConnectContextManager` instance. - -## Design Rationale - -### Why Full Coroutine Protocol? - -External async code (timeout handlers, async schedulers, introspection tools) expects `send()`, `throw()`, and `close()` methods. Without these, our wrapper breaks compatibility and may fail at runtime with code like: -```python -result = await asyncio.wait_for(aio.connect(...), timeout=5.0) # May call throw() -``` - -### Why Preserve Metadata? - -- IDEs show correct function signature when hovering over `connect` -- `help(connect)` displays proper docstring -- `inspect.signature(connect)` works correctly -- Static type checkers recognize parameters - -### Why Both Await and Context Manager? - -- **Await pattern:** Simple, lightweight for one-off connections -- **Context manager pattern:** Ensures cleanup via `__aexit__`, safer for resource management - -## Usage Patterns - -```python -# Pattern 1: Simple await -conn = await aio.connect(account="myaccount", user="user", password="pass") -result = await conn.cursor().execute("SELECT 1") -await conn.close() - -# Pattern 2: Async context manager (recommended) -async with aio.connect(account="myaccount", user="user", password="pass") as conn: - result = await conn.cursor().execute("SELECT 1") - # Auto-closes on exit -``` - -## Implementation Details - -### __slots__ = ("_coro", "_conn") -Memory efficient, especially when many connections are created. - -### Inner Coroutine Function -```python -async def _connect_coro() -> SnowflakeConnection: - conn = SnowflakeConnection(**kwargs) - await conn.connect() - return conn -``` -Defers connection creation and establishment until await time, not at `connect()` call time. - -## File Location - -`src/snowflake/connector/aio/__init__.py` From 0b24d0db02a8b697cf2189256e60ec54b2ada194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 20:14:31 +0100 Subject: [PATCH 20/25] NO-SNOW: remove comment --- src/snowflake/connector/aio/_connection.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/snowflake/connector/aio/_connection.py b/src/snowflake/connector/aio/_connection.py index f78ffe8a08..3b85f4df41 100644 --- a/src/snowflake/connector/aio/_connection.py +++ b/src/snowflake/connector/aio/_connection.py @@ -149,7 +149,6 @@ def __init__( if "platform_detection_timeout_seconds" not in kwargs: self._platform_detection_timeout_seconds = 0.0 - # TODO: why we have it here if never changed self._connected = False self.expired = False # check SNOW-1218851 for long term improvement plan to refactor ocsp code From e1cccfb106a113a04a7f6b85a6c9ecc213648da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 20:23:48 +0100 Subject: [PATCH 21/25] NO-SNOW: Docs improved --- src/snowflake/connector/aio/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index 86a7b5efd8..c96fad39e6 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -15,6 +15,9 @@ # ============================================================================ # DESIGN NOTES: # +# Pattern similar to aiohttp.ClientSession.request() which similarly returns +# an object that can be both awaited and used as an async context manager. +# # The async connect function uses a wrapper to support both: # 1. Direct awaiting: conn = await connect(...) # 2. Async context manager: async with connect(...) as conn: From 5234ea9cb840ee3766d9f248f7c10234b43591e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 20:36:29 +0100 Subject: [PATCH 22/25] NO-SNOW: Removed synch changes --- test/integ/test_connection.py | 94 +---------------------------------- 1 file changed, 1 insertion(+), 93 deletions(-) diff --git a/test/integ/test_connection.py b/test/integ/test_connection.py index 7313a75acc..31a3bc32af 100644 --- a/test/integ/test_connection.py +++ b/test/integ/test_connection.py @@ -17,13 +17,7 @@ import pytest import snowflake.connector -from snowflake.connector import ( - DatabaseError, - OperationalError, - ProgrammingError, - SnowflakeConnection, - connect, -) +from snowflake.connector import DatabaseError, OperationalError, ProgrammingError from snowflake.connector.compat import IS_WINDOWS from snowflake.connector.connection import DEFAULT_CLIENT_PREFETCH_THREADS from snowflake.connector.description import CLIENT_NAME @@ -1567,92 +1561,6 @@ def test_no_new_warnings_or_errors_on_successful_basic_select(conn_cnx, caplog): ) -@pytest.mark.skipolddriver -def test_connect_metadata_preservation(): - """Test that the sync connect function preserves metadata from SnowflakeConnection.__init__. - - This test verifies that various inspection methods return consistent metadata, - ensuring IDE support, type checking, and documentation generation work correctly. - """ - import inspect - - # Test 1: Check __name__ is correct - assert ( - connect.__name__ == "__init__" - ), f"connect.__name__ should be 'connect', but got '{connect.__name__}'" - - # Test 2: Check __wrapped__ points to SnowflakeConnection.__init__ - assert hasattr(connect, "__wrapped__"), "connect should have __wrapped__ attribute" - assert ( - connect.__wrapped__ is SnowflakeConnection.__init__ - ), "connect.__wrapped__ should reference SnowflakeConnection.__init__" - - # Test 3: Check __module__ is preserved - assert hasattr(connect, "__module__"), "connect should have __module__ attribute" - assert connect.__module__ == SnowflakeConnection.__init__.__module__, ( - f"connect.__module__ should match SnowflakeConnection.__init__.__module__, " - f"but got '{connect.__module__}' vs '{SnowflakeConnection.__init__.__module__}'" - ) - - # Test 4: Check __doc__ is preserved - assert hasattr(connect, "__doc__"), "connect should have __doc__ attribute" - assert ( - connect.__doc__ == SnowflakeConnection.__init__.__doc__ - ), "connect.__doc__ should match SnowflakeConnection.__init__.__doc__" - - # Test 5: Check __annotations__ are preserved (or at least available) - assert hasattr( - connect, "__annotations__" - ), "connect should have __annotations__ attribute" - src_annotations = getattr(SnowflakeConnection.__init__, "__annotations__", {}) - connect_annotations = getattr(connect, "__annotations__", {}) - assert connect_annotations == src_annotations, ( - f"connect.__annotations__ should match SnowflakeConnection.__init__.__annotations__, " - f"but got {connect_annotations} vs {src_annotations}" - ) - - # Test 6: Check inspect.signature works correctly - try: - connect_sig = inspect.signature(connect) - source_sig = inspect.signature(SnowflakeConnection.__init__) - assert str(connect_sig) == str(source_sig), ( - f"inspect.signature(connect) should match inspect.signature(SnowflakeConnection.__init__), " - f"but got '{connect_sig}' vs '{source_sig}'" - ) - except Exception as e: - pytest.fail(f"inspect.signature(connect) failed: {e}") - - # Test 7: Check inspect.getdoc works correctly - connect_doc = inspect.getdoc(connect) - source_doc = inspect.getdoc(SnowflakeConnection.__init__) - assert ( - connect_doc == source_doc - ), "inspect.getdoc(connect) should match inspect.getdoc(SnowflakeConnection.__init__)" - - # Test 8: Check that connect is callable - assert callable(connect), "connect should be callable" - - # Test 9: Check type() and __class__ values (important for user introspection) - assert ( - type(connect).__name__ == "function" - ), f"type(connect).__name__ should be 'function', but got '{type(connect).__name__}'" - assert ( - connect.__class__.__name__ == "function" - ), f"connect.__class__.__name__ should be 'function', but got '{connect.__class__.__name__}'" - assert inspect.isfunction( - connect - ), "connect should be recognized as a function by inspect.isfunction()" - - # Test 10: Verify the function has proper introspection capabilities - # IDEs and type checkers should be able to resolve parameters - sig = inspect.signature(connect) - params = list(sig.parameters.keys()) - assert ( - len(params) > 0 - ), "connect should have parameters from SnowflakeConnection.__init__" - # Should have parameters like account, user, password, etc. - - @pytest.mark.skipolddriver def test_ocsp_mode_insecure_mode_and_disable_ocsp_checks_mismatch_ocsp_enabled( conn_cnx, is_public_test, is_local_dev_setup, caplog From ef0e4fd7701e83318e6d76a11d3edfcc2f06bd59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Tue, 4 Nov 2025 20:41:20 +0100 Subject: [PATCH 23/25] NO-SNOW: CLean up --- test/integ/test_connection.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/integ/test_connection.py b/test/integ/test_connection.py index 31a3bc32af..966c8f7d10 100644 --- a/test/integ/test_connection.py +++ b/test/integ/test_connection.py @@ -19,7 +19,10 @@ import snowflake.connector from snowflake.connector import DatabaseError, OperationalError, ProgrammingError from snowflake.connector.compat import IS_WINDOWS -from snowflake.connector.connection import DEFAULT_CLIENT_PREFETCH_THREADS +from snowflake.connector.connection import ( + DEFAULT_CLIENT_PREFETCH_THREADS, + SnowflakeConnection, +) from snowflake.connector.description import CLIENT_NAME from snowflake.connector.errorcode import ( ER_CONNECTION_IS_CLOSED, From 48123e736cfbc6b2d4e172815d4146136ecbec3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Wed, 5 Nov 2025 08:35:16 +0100 Subject: [PATCH 24/25] NO-SNOW: final api --- src/snowflake/connector/aio/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/snowflake/connector/aio/__init__.py b/src/snowflake/connector/aio/__init__.py index c96fad39e6..15a0fe4699 100644 --- a/src/snowflake/connector/aio/__init__.py +++ b/src/snowflake/connector/aio/__init__.py @@ -146,7 +146,7 @@ async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None: @wraps(SnowflakeConnection.__init__) -def Connect(**kwargs: Any) -> HybridCoroutineContextManager[SnowflakeConnection]: +def connect(**kwargs: Any) -> HybridCoroutineContextManager[SnowflakeConnection]: """Create and connect to a Snowflake connection asynchronously. Returns an awaitable that can also be used as an async context manager. @@ -161,6 +161,3 @@ async def _connect_coro() -> SnowflakeConnection: return conn return _AsyncConnectContextManager(_connect_coro()) - - -connect = Connect From 7b196c336fb6e0b5d21fcb1e162b6547745c82b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Paw=C5=82owski?= Date: Wed, 12 Nov 2025 14:34:46 +0100 Subject: [PATCH 25/25] SNOW-1675422: fixed broken jobs - duplicated leftover --- test/integ/aio_it/conftest.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/integ/aio_it/conftest.py b/test/integ/aio_it/conftest.py index dba36fba0c..86df4efc80 100644 --- a/test/integ/aio_it/conftest.py +++ b/test/integ/aio_it/conftest.py @@ -101,10 +101,7 @@ async def create_connection(connection_name: str, **kwargs) -> SnowflakeConnecti from parameters.py. """ ret = fill_conn_kwargs_for_tests(connection_name, **kwargs) - connection = SnowflakeConnection(**ret) - conn = await async_connect(**ret) - await connection.connect() - return conn + return await async_connect(**ret) @asynccontextmanager