Skip to content

Commit 56ec7fa

Browse files
[async] Added docs
1 parent e1fcbae commit 56ec7fa

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed

ASYNC_CONNECT_IMPLEMENTATION.md

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# Async Connect Wrapper Implementation
2+
3+
## Overview
4+
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+
7+
## Problem Statement
8+
9+
The synchronous `snowflake.connector.connect()` uses:
10+
```python
11+
@wraps(SnowflakeConnection.__init__)
12+
def Connect(**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)
21+
22+
## Implementation
23+
24+
### Architecture
25+
26+
```
27+
connect = _AsyncConnectWrapper()
28+
↓ (calls __call__)
29+
_AsyncConnectContextManager (coroutine wrapper)
30+
├─ Coroutine Protocol: send(), throw(), close()
31+
├─ __await__(), __iter__()
32+
└─ __aenter__(), __aexit__() (async context manager)
33+
```
34+
35+
### Class: _AsyncConnectContextManager
36+
37+
Makes a coroutine both awaitable and an async context manager, while implementing the full coroutine protocol.
38+
39+
```python
40+
class _AsyncConnectContextManager:
41+
"""Hybrid wrapper that enables both awaiting and async context manager usage.
42+
43+
Implements the full coroutine protocol for maximum compatibility.
44+
"""
45+
46+
__slots__ = ("_coro", "_conn")
47+
48+
def __init__(self, coro: Coroutine[Any, Any, SnowflakeConnection]) -> None:
49+
self._coro = coro
50+
self._conn: SnowflakeConnection | None = None
51+
52+
def send(self, arg: Any) -> Any:
53+
"""Send a value into the wrapped coroutine."""
54+
return self._coro.send(arg)
55+
56+
def throw(self, *args: Any, **kwargs: Any) -> Any:
57+
"""Throw an exception into the wrapped coroutine."""
58+
return self._coro.throw(*args, **kwargs)
59+
60+
def close(self) -> None:
61+
"""Close the wrapped coroutine."""
62+
return self._coro.close()
63+
64+
def __await__(self) -> Generator[Any, None, SnowflakeConnection]:
65+
"""Enable: conn = await connect(...)"""
66+
return self._coro.__await__()
67+
68+
def __iter__(self) -> Generator[Any, None, SnowflakeConnection]:
69+
"""Make the wrapper iterable like a coroutine."""
70+
return self.__await__()
71+
72+
async def __aenter__(self) -> SnowflakeConnection:
73+
"""Enable: async with connect(...) as conn:"""
74+
self._conn = await self._coro
75+
return await self._conn.__aenter__()
76+
77+
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
78+
"""Exit async context manager."""
79+
if self._conn is not None:
80+
return await self._conn.__aexit__(exc_type, exc, tb)
81+
```
82+
83+
#### Coroutine Protocol Methods
84+
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)
90+
91+
### Class: _AsyncConnectWrapper
92+
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.
101+
"""
102+
103+
def __init__(self) -> None:
104+
self.__wrapped__ = SnowflakeConnection.__init__
105+
self.__name__ = "connect"
106+
self.__doc__ = SnowflakeConnection.__init__.__doc__
107+
self.__module__ = __name__
108+
self.__qualname__ = "connect"
109+
self.__annotations__ = getattr(SnowflakeConnection.__init__, "__annotations__", {})
110+
111+
@wraps(SnowflakeConnection.__init__)
112+
def __call__(self, **kwargs: Any) -> _AsyncConnectContextManager:
113+
"""Create and connect to a Snowflake connection asynchronously."""
114+
async def _connect_coro() -> SnowflakeConnection:
115+
conn = SnowflakeConnection(**kwargs)
116+
await conn.connect()
117+
return conn
118+
119+
return _AsyncConnectContextManager(_connect_coro())
120+
```
121+
122+
## Usage Patterns
123+
124+
### Pattern 1: Simple Await (No Context Manager)
125+
```python
126+
conn = await aio.connect(
127+
account="myaccount",
128+
user="myuser",
129+
password="mypassword"
130+
)
131+
result = await conn.cursor().execute("SELECT 1")
132+
await conn.close()
133+
```
134+
135+
**Flow:**
136+
1. `aio.connect(...)` returns `_AsyncConnectContextManager`
137+
2. `await` calls `__await__()`, returns inner coroutine result
138+
3. `conn` is a `SnowflakeConnection` object
139+
4. Wrapper is garbage collected
140+
141+
### Pattern 2: Async Context Manager
142+
```python
143+
async with aio.connect(
144+
account="myaccount",
145+
user="myuser",
146+
password="mypassword"
147+
) as conn:
148+
result = await conn.cursor().execute("SELECT 1")
149+
# Auto-closes on exit
150+
```
151+
152+
**Flow:**
153+
1. `aio.connect(...)` returns `_AsyncConnectContextManager`
154+
2. `async with` calls `__aenter__()`, awaits coroutine, returns connection
155+
3. Code block executes
156+
4. `async with` calls `__aexit__()` on exit
157+
158+
### Pattern 3: Manual Coroutine Driving (Advanced)
159+
```python
160+
# For advanced use cases with manual iteration
161+
coro_wrapper = aio.connect(account="myaccount", user="user", password="pass")
162+
# Can use send(), throw(), close() methods directly if needed
163+
```
164+
165+
## Key Design Decisions
166+
167+
### 1. Metadata Copying in `__init__`
168+
```python
169+
self.__wrapped__ = SnowflakeConnection.__init__
170+
self.__name__ = "connect"
171+
# ...
172+
```
173+
**Why:** Allows direct attribute access for introspection before calling `__call__`
174+
175+
### 2. `@wraps` Decorator on `__call__`
176+
```python
177+
@wraps(SnowflakeConnection.__init__)
178+
def __call__(self, **kwargs: Any) -> _AsyncConnectContextManager:
179+
```
180+
**Why:** IDEs and inspection tools that examine `__call__` see correct metadata
181+
182+
### 3. Inner Coroutine Function
183+
```python
184+
async def _connect_coro() -> SnowflakeConnection:
185+
conn = SnowflakeConnection(**kwargs)
186+
await conn.connect()
187+
return conn
188+
```
189+
**Why:** Defers connection creation and establishment until await time, not at `connect()` call time
190+
191+
### 4. `__slots__` on Context Manager
192+
```python
193+
__slots__ = ("_coro", "_conn")
194+
```
195+
**Why:** Memory efficient, especially when many connections are created
196+
197+
### 5. Full Coroutine Protocol
198+
Following aiohttp's `_RequestContextManager` pattern, we implement `send()`, `throw()`, and `close()` methods. This ensures:
199+
- Maximum compatibility with async utilities and libraries
200+
- Support for manual coroutine driving if needed
201+
- Proper cleanup and exception handling
202+
203+
## Comparison with aiohttp's _RequestContextManager
204+
205+
Our implementation follows the same proven pattern used by aiohttp:
206+
207+
| Feature | aiohttp | Our Implementation |
208+
|---------|---------|-------------------|
209+
| `send()` method |||
210+
| `throw()` method |||
211+
| `close()` method |||
212+
| `__await__()` method |||
213+
| `__iter__()` method |||
214+
| `__aenter__()` method |||
215+
| `__aexit__()` method |||
216+
| Metadata preservation | N/A ||
217+
218+
## Behavior Comparison
219+
220+
| Aspect | Pattern 1 | Pattern 2 | Pattern 3 |
221+
|--------|-----------|-----------|-----------|
222+
| Syntax | `conn = await aio.connect(...)` | `async with aio.connect(...) as conn:` | Manual iteration |
223+
| Connection Object | In local scope | In context scope | Via wrapper |
224+
| Cleanup | Manual (`await conn.close()`) | Automatic (`__aexit__`) | Manual |
225+
| Metadata Available | Yes | Yes | Yes |
226+
| Error Handling | Manual try/except | Automatic via context manager | Manual |
227+
| Use Case | Simple connections | Resource management | Advanced/testing |
228+
229+
## Verification
230+
231+
```python
232+
# Metadata preservation
233+
assert connect.__name__ == "connect"
234+
assert hasattr(connect, "__wrapped__")
235+
assert callable(connect)
236+
237+
# Return type validation
238+
result = connect(account="test")
239+
assert hasattr(result, "__await__") # Awaitable
240+
assert hasattr(result, "__aenter__") # Async context manager
241+
assert hasattr(result, "__aexit__") # Async context manager
242+
243+
# Full coroutine protocol
244+
assert hasattr(result, "send") # Coroutine protocol
245+
assert hasattr(result, "throw") # Exception injection
246+
assert hasattr(result, "close") # Cleanup
247+
assert hasattr(result, "__iter__") # Iteration support
248+
```
249+
250+
## File Location
251+
252+
`src/snowflake/connector/aio/__init__.py`
253+
254+
## Backwards Compatibility
255+
256+
- ✅ Existing code using `await aio.connect(...)` works unchanged
257+
- ✅ New code can use `async with aio.connect(...) as conn:`
258+
- ✅ Metadata available for IDE tooltips and introspection tools
259+
- ✅ Signature matches synchronous version in tooling
260+
- ✅ Full coroutine protocol support for advanced use cases
261+
262+
## Benefits
263+
264+
**Full Coroutine Protocol** - Compatible with all async utilities and libraries
265+
**Flexible Usage** - Simple await or async context manager patterns
266+
**Metadata Preservation** - IDE tooltips and introspection support
267+
**Transparent & Efficient** - Minimal overhead, garbage collected after use
268+
**Backwards Compatible** - No breaking changes to existing code
269+
**Battle-tested Pattern** - Follows aiohttp's proven design

0 commit comments

Comments
 (0)