Skip to content

Commit 9e76506

Browse files
NO-SNOW: Simplify callable instance into function
1 parent 8443333 commit 9e76506

File tree

3 files changed

+51
-66
lines changed

3 files changed

+51
-66
lines changed

src/snowflake/connector/aio/__init__.py

Lines changed: 22 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
from __future__ import annotations
22

3-
from functools import update_wrapper
3+
from functools import wraps
44
from typing import (
55
Any,
6-
Callable,
76
Coroutine,
87
Generator,
98
Protocol,
@@ -23,16 +22,20 @@
2322
# ============================================================================
2423
# DESIGN NOTES:
2524
#
26-
# The async connect function uses a two-layer wrapper to support both:
25+
# The async connect function uses a wrapper to support both:
2726
# 1. Direct awaiting: conn = await connect(...)
2827
# 2. Async context manager: async with connect(...) as conn:
2928
#
29+
# connect: A function decorated with @wraps(SnowflakeConnection.__init__) that
30+
# preserves metadata for IDE support, type checking, and introspection.
31+
# Returns a _AsyncConnectContextManager instance when called.
32+
#
3033
# _AsyncConnectContextManager: Implements __await__ and __aenter__/__aexit__
3134
# to support both patterns on the same awaitable.
3235
#
33-
# _AsyncConnectWrapper: A callable class that preserves SnowflakeConnection
34-
# metadata via @preserve_metadata decorator for IDE support, type checking,
35-
# and introspection. Returns _AsyncConnectContextManager instances when called.
36+
# The @wraps decorator ensures that connect() has the same signature and
37+
# documentation as SnowflakeConnection.__init__, making it behave identically
38+
# to the sync snowflake.connector.connect function from an introspection POV.
3639
#
3740
# Metadata preservation is critical for IDE autocomplete, static type checkers,
3841
# and documentation generation to work correctly on the async connect function.
@@ -95,35 +98,6 @@ async def __aexit__(
9598
...
9699

97100

98-
def preserve_metadata(
99-
source: type, *, override_name: str | None = None
100-
) -> Callable[[T], T]:
101-
"""Decorator to copy metadata from a source class to class instances.
102-
103-
Copies __wrapped__, __doc__, __module__, __annotations__ etc. to instances
104-
during their initialization, allowing instances to be introspected like the
105-
source class's __init__.
106-
107-
Args:
108-
source: Class to copy metadata from (uses its __init__).
109-
override_name: Optional name override for instances.
110-
"""
111-
112-
def decorator(cls: T) -> T:
113-
metadata_source = source.__init__
114-
original_init = cls.__init__
115-
116-
def new_init(self: Any) -> None:
117-
update_wrapper(self, metadata_source, updated=[])
118-
if override_name:
119-
self.__name__ = override_name
120-
self.__qualname__ = override_name
121-
original_init(self)
122-
123-
cls.__init__ = new_init
124-
return cls
125-
126-
return decorator
127101

128102

129103
class _AsyncConnectContextManager(HybridCoroutineContextManager[SnowflakeConnection]):
@@ -177,31 +151,21 @@ async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
177151
return None
178152

179153

180-
@preserve_metadata(SnowflakeConnection, override_name="connect")
181-
class _AsyncConnectWrapper:
182-
"""Preserves SnowflakeConnection.__init__ metadata for async connect function.
154+
@wraps(SnowflakeConnection.__init__)
155+
def Connect(**kwargs: Any) -> HybridCoroutineContextManager[SnowflakeConnection]:
156+
"""Create and connect to a Snowflake connection asynchronously.
183157
184-
This wrapper enables introspection tools and IDEs to see the same signature
185-
as the synchronous snowflake.connector.connect function.
158+
Returns an awaitable that can also be used as an async context manager.
159+
Supports both patterns:
160+
- conn = await connect(...)
161+
- async with connect(...) as conn:
186162
"""
163+
async def _connect_coro() -> SnowflakeConnection:
164+
conn = SnowflakeConnection(**kwargs)
165+
await conn.connect()
166+
return conn
187167

188-
def __call__(
189-
self, **kwargs: Any
190-
) -> HybridCoroutineContextManager[SnowflakeConnection]:
191-
"""Create and connect to a Snowflake connection asynchronously.
192-
193-
Returns an awaitable that can also be used as an async context manager.
194-
Supports both patterns:
195-
- conn = await connect(...)
196-
- async with connect(...) as conn:
197-
"""
198-
199-
async def _connect_coro() -> SnowflakeConnection:
200-
conn = SnowflakeConnection(**kwargs)
201-
await conn.connect()
202-
return conn
203-
204-
return _AsyncConnectContextManager(_connect_coro())
168+
return _AsyncConnectContextManager(_connect_coro())
205169

206170

207-
connect = _AsyncConnectWrapper()
171+
connect = Connect

test/integ/test_connection.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1628,7 +1628,18 @@ def test_connect_metadata_preservation():
16281628
# Test 8: Check that connect is callable
16291629
assert callable(connect), "connect should be callable"
16301630

1631-
# Test 9: Verify the function has proper introspection capabilities
1631+
# Test 9: Check type() and __class__ values (important for user introspection)
1632+
assert type(connect).__name__ == "function", (
1633+
f"type(connect).__name__ should be 'function', but got '{type(connect).__name__}'"
1634+
)
1635+
assert connect.__class__.__name__ == "function", (
1636+
f"connect.__class__.__name__ should be 'function', but got '{connect.__class__.__name__}'"
1637+
)
1638+
assert inspect.isfunction(connect), (
1639+
"connect should be recognized as a function by inspect.isfunction()"
1640+
)
1641+
1642+
# Test 10: Verify the function has proper introspection capabilities
16321643
# IDEs and type checkers should be able to resolve parameters
16331644
sig = inspect.signature(connect)
16341645
params = list(sig.parameters.keys())

test/unit/aio/test_connection_async_unit.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -876,13 +876,12 @@ async def test_connect_metadata_preservation():
876876

877877
from snowflake.connector.aio import SnowflakeConnection, connect
878878

879-
# Test 1: Check __name__ and __qualname__ are overridden correctly
880-
# tODO: the only difference is that this is __init__ in synch connect
881-
assert (
882-
connect.__name__ == "connect"
883-
), f"connect.__name__ should be 'connect', but got '{connect.__name__}'"
879+
# Test 1: Check __name__ is correct
880+
assert connect.__name__ == "__init__", (
881+
f"connect.__name__ should be '__init__', but got '{connect.__name__}'"
882+
)
884883
assert (
885-
connect.__qualname__ == "connect"
884+
connect.__qualname__ == "SnowflakeConnection.__init__"
886885
), f"connect.__qualname__ should be 'connect', but got '{connect.__qualname__}'"
887886

888887
# Test 2: Check __wrapped__ points to SnowflakeConnection.__init__
@@ -936,7 +935,18 @@ async def test_connect_metadata_preservation():
936935
# Test 8: Check that connect is callable and returns expected type
937936
assert callable(connect), "connect should be callable"
938937

939-
# Test 9: Verify the instance has proper introspection capabilities
938+
# Test 9: Check type() and __class__ values (important for user introspection)
939+
assert type(connect).__name__ == "function", (
940+
f"type(connect).__name__ should be 'function', but got '{type(connect).__name__}'"
941+
)
942+
assert connect.__class__.__name__ == "function", (
943+
f"connect.__class__.__name__ should be 'function', but got '{connect.__class__.__name__}'"
944+
)
945+
assert inspect.isfunction(connect), (
946+
"connect should be recognized as a function by inspect.isfunction()"
947+
)
948+
949+
# Test 10: Verify the function has proper introspection capabilities
940950
# IDEs and type checkers should be able to resolve parameters
941951
sig = inspect.signature(connect)
942952
params = list(sig.parameters.keys())

0 commit comments

Comments
 (0)