Add async SQLAlchemy dialect - #1022
Open
joe-clickhouse wants to merge 32 commits into
Open
Conversation
# Conflicts: # .github/workflows/on_push.yml # CHANGELOG.md # clickhouse_connect/driver/asyncclient.py # clickhouse_connect/driver/streaming.py # clickhouse_connect/driver/transform.py # tests/unit_tests/test_backend_orchestration.py # tests/unit_tests/test_streaming_source.py
|
Docs drift check: resolved, docs now match this PR's changes. |
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
It introduces a new async SQLAlchemy dialect and makes extensive, correctness-critical changes across async transport/session lifecycle, streaming cleanup, and exception semantics that warrant final human maintainer review.
Pull request overview
This PR adds a production-ready async SQLAlchemy dialect (clickhousedb+async://) backed by AsyncClient, and hardens several async driver lifecycle and streaming behaviors needed to make pooled async usage safe (cancellation-safe cleanup, event-loop ownership checks, and improved error metadata).
Changes:
- Add an async SQLAlchemy dialect implementation with SQLAlchemy 2.0.44+ guards, async DB-API adaptation, pooling/termination behavior, and Alembic async environment support.
- Improve async client/backend lifecycle correctness (session rotation/close semantics, cancellation-safe cleanup) and streaming cleanup to avoid leaked responses/tasks and executor stalls.
- Expand error and option-handling contracts (DB-API exception re-exports,
StreamFailureError->OperationalError, ClickHouse error code/name extraction, and DSN/generic option coercion).
File summaries
| File | Description |
|---|---|
| tests/unit_tests/test_sqlalchemy/test_dbapi_exceptions.py | Unit coverage for DB-API exception exports and SQLAlchemy wrapping behavior. |
| tests/unit_tests/test_sqlalchemy/test_async_dialect_version.py | Version/greenlet import guards for async dialect module import. |
| tests/unit_tests/test_sqlalchemy/test_async_alembic.py | Unit coverage for checked-in async Alembic env and offline/online config hooks. |
| tests/unit_tests/test_sqlalchemy/test_alembic.py | Coverage for integration-tag injection via dialect get_driver_connection. |
| tests/unit_tests/test_helpers.py | Unit coverage for new run_in_new_loop helper semantics. |
| tests/unit_tests/test_driver/test_token_provider.py | Align tests with async session-lease loop ownership tracking. |
| tests/unit_tests/test_driver/test_rustcodec.py | Coverage for insert error-handler propagation and error detail policies. |
| tests/unit_tests/test_driver/test_httpclient.py | Update insert-transform mock signature for optional error handler. |
| tests/unit_tests/test_driver/test_cursor.py | Ensure StreamFailureError during metadata probe is not swallowed. |
| tests/unit_tests/test_driver/test_chdb.py | Validate stream failure metadata extraction for chDB streaming wrappers. |
| tests/unit_tests/test_driver/test_buffer.py | Validate stream failure metadata extraction in Native transform buffering paths. |
| tests/unit_tests/test_driver/test_backend_http.py | Coverage for fractional timeout behavior and async session loop ownership errors. |
| tests/unit_tests/test_driver/test_async_client_query_cleanup.py | Extensive async query cleanup and cancellation-safety coverage. |
| tests/unit_tests/test_driver/test_async_client_insert_cleanup.py | Extensive async insert cleanup, serializer error precedence, cancellation coverage. |
| tests/unit_tests/test_driver/test_async_client_initialization.py | Coverage for async initialization serialization and failure cleanup paths. |
| tests/unit_tests/test_driver/test_async_client_factory.py | Coverage for DSN/generic option coercion and connector option precedence. |
| tests/unit_tests/test_driver/test_async_client_close_cleanup.py | Coverage for close/rotation/force-close semantics under cancellation. |
| tests/unit_tests/test_backend_orchestration.py | Use run_in_new_loop for async orchestration parity testing. |
| tests/unit_tests/test_asyncqueue.py | Use run_in_new_loop to avoid asyncio.run side effects in threaded tests. |
| tests/type_check/installed_sqlalchemy_smoke.py | Installed-wheel entry point validation for sync+async dialects. |
| tests/type_check/consumer_smoke.py | Consumer typing smoke additions for async client + async SQLAlchemy dialect surface. |
| tests/test_requirements.txt | Switch test dependency to sqlalchemy[asyncio] for async dialect coverage. |
| tests/integration_tests/test_sqlalchemy/test_basics.py | Integration coverage for SQLAlchemy exception hierarchy wrapping. |
| tests/integration_tests/test_sqlalchemy/test_async_alembic.py | Integration coverage for Alembic migrations via async engine + run_sync. |
| tests/integration_tests/test_rust_codec.py | Ensure SQLAlchemy wrapping preserves orig for NotSupportedError. |
| tests/integration_tests/test_mid_stream_exception.py | Assert stream failure code/name metadata in sync + async streaming tests. |
| tests/integration_tests/test_client.py | Assert stream failure metadata under show_clickhouse_errors modes. |
| tests/helpers.py | Add run_in_new_loop utility with robust loop shutdown/cleanup. |
| setup.py | Add sqlalchemy-async extra and async dialect entry point registration. |
| README.md | Document async SQLAlchemy dialect usage and disposal guidance. |
| examples/alembic_async/env.py | Add runnable async Alembic environment example. |
| docs/sqlalchemy.mdx | Full async dialect documentation: pooling, buffering boundaries, disposal, error handling, Alembic usage. |
| docs/index.mdx | Update docs index to reflect sync + async dialects and install extras. |
| docs/driver-api.mdx | Document async timeout types, connector option coercion/precedence, and DB-API exception exports. |
| docs/advanced-usage.mdx | Document async client loop ownership transfer/cleanup guidance. |
| clickhouse_connect/driver/transform.py | Add stream failure code/name extraction; add insert error-handler hook. |
| clickhouse_connect/driver/streaming.py | Strengthen lease release semantics and async cleanup (cancellation-safe) for streaming/read-ahead/insert sources. |
| clickhouse_connect/driver/rustcodec.py | Add stream failure code/name extraction; propagate insert error-handler to Python fallback. |
| clickhouse_connect/driver/query.py | Add async close path for results to support async stream context cleanup. |
| clickhouse_connect/driver/npquery.py | Add async close path for NumPy results mirroring QueryResult cleanup. |
| clickhouse_connect/driver/exceptions.py | Add error code extraction helper; make StreamFailureError an OperationalError. |
| clickhouse_connect/driver/common.py | Add _close_async helper for uniform close/awaitable-close behavior. |
| clickhouse_connect/driver/asyncclient.py | Serialize initialization; add cancellation-safe cleanup; improve insert/query cleanup and error precedence. |
| clickhouse_connect/driver/_backend/httpcommon.py | Allow float timeouts for async; ensure progress interval is an integer string. |
| clickhouse_connect/driver/_backend/http_sync.py | Normalize trailing slash for proxy-path ping URL. |
| clickhouse_connect/driver/_backend/http_async.py | Add session loop ownership checks, cancellation-safe session close/rotation, force-close connector fallback, and ping URL normalization. |
| clickhouse_connect/driver/_backend/contracts.py | Extend async backend protocol with force_close. |
| clickhouse_connect/driver/_backend/chdb_backend.py | Reuse shared error code extraction for chDB error wrapping/stream failures. |
| clickhouse_connect/driver/init.py | Add DSN/generic option coercion and async connector option precedence rules. |
| clickhouse_connect/dbapi/cursor.py | Ensure StreamFailureError propagates through DB-API metadata probe. |
| clickhouse_connect/dbapi/init.py | Re-export PEP 249 exception hierarchy from driver exceptions. |
| clickhouse_connect/cc_sqlalchemy/asyncio.py | New async dialect implementation (guards, async DB-API adapter, pooling, terminate/close behavior). |
| clickhouse_connect/cc_sqlalchemy/alembic/WORKED_EXAMPLE.md | Update worked example to avoid dialect_name and use default schema from dialect. |
| clickhouse_connect/cc_sqlalchemy/alembic/impl.py | Make Alembic integration tagging work with async dialect via get_driver_connection. |
| clickhouse_connect/cc_sqlalchemy/init.py | Register async dialect names with SQLAlchemy registry. |
| CHANGELOG.md | Document async dialect addition and related driver lifecycle fixes. |
| .github/workflows/on_push.yml | Add installed-wheel dialect entry point smoke coverage; adjust type ratchet threshold. |
| .github/workflows/binding_ci.yml | Expand binding CI path filters to include new/updated async+SQLAlchemy-related surfaces and tests. |
Review details
- Files reviewed: 61/61 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a production async SQLAlchemy dialect backed by
AsyncClient.clickhousedb+async://dialect entry point for SQLAlchemy 2.0.44 and later.sqlalchemy-asyncinstallation extra with SQLAlchemy, greenlet, and aiohttp dependencies.AsyncClient, aiohttp session, and connector per pooled SQLAlchemy connection.Standard SQLAlchemy result APIs remain buffered. Native streaming and bulk inserts are available through the underlying
AsyncClient. SQLAlchemy executemany currently performs one request per parameter set, which is documented and covered as an explicit compatibility boundary.Closes #576
Checklist