Async support in Snowflake SQLAlchemy¶
Snowflake SQLAlchemy supports SQLAlchemy’s asyncio extension, letting you use AsyncEngine and AsyncSession for non-blocking database access.
Requirements¶
Async support is part of the standard install; you don’t need a separate package:
-
Python 3.11 or later. The 5.x connector dropped Python 3.10 support, so
pipcan’t resolve it on 3.10. -
snowflake-connector-python5.x (>=5.0.0), installed automatically as a base dependency. -
SQLAlchemy 2.0.44 or later, but earlier than 2.1.
-
greenlet, which SQLAlchemy’s asyncio extension relies on. SQLAlchemy installs it automatically only on some platforms, such asx86_64,aarch64, andamd64. On other platforms, notably Apple silicon macOS (arm64), install it explicitly. Without it,create_async_engine()raises anImportErrorwith install instructions when the engine is created. Keep the same SQLAlchemy range sopipdoesn’t upgrade you to 2.1:
Quick start¶
Async support uses the same snowflake:// URL as the sync dialect. There’s no separate +driver suffix, unlike, for example, postgresql+asyncpg://. SQLAlchemy detects and selects the async dialect automatically when you call create_async_engine():
If you need a case-sensitive schema name, the same edge case the sync create_snowflake_engine() helper handles, use its async counterpart instead of building the URL by hand:
How it works¶
- No new URL scheme.
create_async_engine()picks the async dialect through SQLAlchemy’sget_async_dialect_cls()hook, so the connection string is identical to sync. - DBAPI shim over
snowflake.connector.aio. Internally, the dialect wraps the connector’s native async driver behind a synchronous-looking PEP 249 facade, bridging blocking calls onto the running event loop with greenlets (await_only). This is the same strategy SQLAlchemy’s ownaiomysql,asyncmy, andaiosqlitedialects use.
Usage notes¶
These notes follow directly from SQLAlchemy’s own asyncio rules. They aren’t Snowflake-specific, but they’re worth restating here:
- No lazy loading. Accessing an unloaded ORM relationship or deferred column outside an active greenlet context raises an error. Use eager loading (
selectinload(),joinedload()), theAsyncAttrsmixin (await obj.awaitable_attrs.rel), or wrap sync-style logic inAsyncSession.run_sync(). - Set
expire_on_commit=Falseonasync_sessionmaker()to avoid attribute-expiry-triggered lazy loads after commit. This is a common setting for async sessions. - Dispose engines across event loops. Don’t reuse an
AsyncEngine(and its pool) across unrelated event loops without callingawait engine.dispose()first. ?async_fallback=trueon the URL switches the pool implementation toFallbackAsyncAdaptedQueuePoolinstead of the defaultAsyncAdaptedQueuePool. You only need this for the same edge cases SQLAlchemy documents for other async dialects, where a real running event loop isn’t always guaranteed at pool-checkout time.
Known limitations¶
- Test coverage for the async dialect currently covers PUT and GET file transfers and basic ORM operations. Other ORM and session behavior is inherited from SQLAlchemy’s generic asyncio machinery rather than independently verified.
- There’s no async-aware Alembic migration runner. Alembic migrations should continue to use a sync engine or connection.