You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
5
+
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.
6
6
7
7
## Problem Statement
8
8
9
-
The synchronous `snowflake.connector.connect()` uses:
10
-
```python
11
-
@wraps(SnowflakeConnection.__init__)
12
-
defConnect(**kwargs) -> SnowflakeConnection:
13
-
return SnowflakeConnection(**kwargs)
14
-
```
15
-
16
-
The async version cannot be decorated with `@wraps` on a raw async function. We needed a solution that:
17
-
1. Preserves metadata for IDE introspection and tooling
18
-
2. Supports `conn = await aio.connect(...)`
19
-
3. Supports `async with aio.connect(...) as conn:`
20
-
4. Implements the full coroutine protocol (following aiohttp's pattern)
9
+
The async version of `connect()` must:
10
+
1. Preserve metadata for IDE introspection and tooling (like the synchronous version)
11
+
2. Support `conn = await aio.connect(...)` pattern
12
+
3. Support `async with aio.connect(...) as conn:` pattern
13
+
4. Implement the full coroutine protocol for ecosystem compatibility (following aiohttp's pattern)
Makes a coroutine both awaitable and an async context manager, while implementing the full coroutine protocol.
27
+
### HybridCoroutineContextManager Protocol
38
28
39
-
```python
40
-
class_AsyncConnectContextManager:
41
-
"""Hybrid wrapper that enables both awaiting and async context manager usage.
29
+
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).
42
30
43
-
Implements the full coroutine protocol for maximum compatibility.
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.
55
41
56
-
defthrow(self, *args: Any, **kwargs: Any) -> Any:
57
-
"""Throw an exception into the wrapped coroutine."""
58
-
returnself._coro.throw(*args, **kwargs)
42
+
### _AsyncConnectWrapper
59
43
60
-
defclose(self) -> None:
61
-
"""Close the wrapped coroutine."""
62
-
returnself._coro.close()
44
+
Callable wrapper decorated with `@preserve_metadata(SnowflakeConnection)` to preserve metadata. Its `__call__` method creates and returns a `_AsyncConnectContextManager` instance.
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:
51
+
```python
52
+
result =await asyncio.wait_for(aio.connect(...), timeout=5.0) # May call throw()
81
53
```
82
54
83
-
#### Coroutine Protocol Methods
55
+
###Why Preserve Metadata?
84
56
85
-
-**`send(arg)`**: Send a value into the wrapped coroutine (used for manual coroutine driving)
86
-
-**`throw(*args, **kwargs)`**: Throw an exception into the wrapped coroutine
87
-
-**`close()`**: Gracefully close the wrapped coroutine
88
-
-**`__await__()`**: Return a generator to enable `await` syntax
89
-
-**`__iter__()`**: Make the wrapper iterable (some async utilities require this)
57
+
- IDEs show correct function signature when hovering over `connect`
58
+
-`help(connect)` displays proper docstring
59
+
-`inspect.signature(connect)` works correctly
60
+
- Static type checkers recognize parameters
90
61
91
-
### Class: _AsyncConnectWrapper
62
+
### Why Both Await and Context Manager?
92
63
93
-
Callable wrapper that preserves `SnowflakeConnection.__init__` metadata.
94
-
95
-
```python
96
-
class_AsyncConnectWrapper:
97
-
"""Preserves SnowflakeConnection.__init__ metadata for async connect function.
98
-
99
-
This wrapper enables introspection tools and IDEs to see the same signature
100
-
as the synchronous snowflake.connector.connect function.
0 commit comments