Skip to content

Add async SQLAlchemy dialect - #1022

Open
joe-clickhouse wants to merge 32 commits into
mainfrom
joe/async-sqlalchemy-1.8-integration
Open

Add async SQLAlchemy dialect#1022
joe-clickhouse wants to merge 32 commits into
mainfrom
joe/async-sqlalchemy-1.8-integration

Conversation

@joe-clickhouse

@joe-clickhouse joe-clickhouse commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a production async SQLAlchemy dialect backed by AsyncClient.

  • Adds the clickhousedb+async:// dialect entry point for SQLAlchemy 2.0.44 and later.
  • Adds the sqlalchemy-async installation extra with SQLAlchemy, greenlet, and aiohttp dependencies.
  • Uses one AsyncClient, aiohttp session, and connector per pooled SQLAlchemy connection.
  • Supports SQLAlchemy Core, basic ORM use, reflection, DDL, server-side parameters, pre-ping, invalidation, recycle, disposal, and Alembic migrations.
  • Maps driver failures through the DB-API exception hierarchy so SQLAlchemy can classify and wrap them correctly.
  • Makes async initialization, queries, inserts, connection rotation, and transport teardown cancellation-safe.
  • Documents buffering, pooling, event-loop ownership, sessions, native streaming and insert access, error handling, and current executemany behavior.
  • Includes a runnable async Alembic environment and worked example.
  • Preserves SQLAlchemy 1.x compatibility for the existing synchronous dialect.

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

  • Unit and integration tests covering the common scenarios were added
  • A human-readable description of the changes was provided in CHANGELOG

# 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
@workflow-authentication-public workflow-authentication-public Bot added the needs-docs PR has user-visible changes without matching docs updates label Sep 3, 2026
@workflow-authentication-public

workflow-authentication-public Bot commented Sep 3, 2026

Copy link
Copy Markdown

Docs drift check: resolved, docs now match this PR's changes.

@workflow-authentication-public workflow-authentication-public Bot removed the needs-docs PR has user-visible changes without matching docs updates label Sep 3, 2026
@joe-clickhouse
joe-clickhouse requested a lite review from Copilot September 3, 2026 23:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Async SQLAlchemy support

2 participants