Skip to content

v0.4.0

Latest

Choose a tag to compare

@long2ice long2ice released this 14 Aug 15:40
· 1 commit to dev since this release

Major internal refactor: the protocol hot path is now compiled with Cython, modeled on the sister project asyncmy. The public API (Connection, Cursor, DictCursor, Pool, DSNs) is unchanged.

Upgrading: Python 3.11 or newer is required, and asynch now ships platform-specific binary wheels rather than a pure-Python one. Everything else is backwards compatible — pip install -U asynch is enough.

Performance

Measured against clickhouse-driver, the synchronous C-extension driver (500k rows, best of 3; reproduce with make benchmark):

Scenario asynch clickhouse-driver
Export 500k rows, wide events table 438 ms 728 ms
100 concurrent queries (pool of 10) 2103 q/s 1310 q/s
DateTime decode 13.8M rows/s 2.3M rows/s
String decode 21.7M rows/s 15.2M rows/s

Small filtered queries, aggregations and batch inserts are server-bound and unchanged — both drivers sit at the wire limit.

New

  • JSON type support (ClickHouse 24.8+): reads as nested dicts, accepts dicts or JSON text on insert. Object('json') no longer exists server-side, so JSON was previously unusable. (#142)
  • Query cancellation: Connection.cancel() / Cursor.cancel() stop a running query from another task, leaving the connection usable. (#104)
  • Connection.last_query exposes per-query statistics (elapsed, rows/bytes, profile info). (#85)
  • Pool(idle_timeout=...) reaps idle connections down to minsize; pool checkouts also cost one liveness ping instead of two. (#137)
  • PEP 249 module surface: apilevel, threadsafety, paramstyle, connect() and the exception hierarchy are importable from asynch. (#159)

Fixes

  • Compressed inserts corrupted the stream and the server dropped the connection — every compressed INSERT was affected. (#149, #153)
  • DateTime64 was decoded as unsigned, silently corrupting pre-1970 timestamps.
  • A cancelled query wedged the connection permanently; any asyncio.timeout around a query, or a web framework cancelling a request task, triggered it. (#93)
  • alt_hosts never failed over — the first host's failure aborted the connect. (#144, #136)
  • Dead pooled connections were handed back to callers: the "reconnect" was a no-op. (#145)
  • connect_timeout / send_receive_timeout / sync_request_timeout were accepted but never used. (#114)
  • Secure connections without an explicit port used 9000 instead of 9440. (#162, #40, #22)
  • Server errors disconnected the connection, costing a pooled connection per failed query. (#150)
  • Streaming was impossible for readonly=1 users — max_block_size was forced. (#67)
  • getpass.getuser() raises OSError on Python 3.13+, breaking the handshake in containers. (#157, #156)
  • None in a non-Nullable column now reports the column and expected type instead of a bare TypeError. (#80, #146)

Packaging

  • Python 3.11+; binary wheels for Linux (x86_64/arm64), Windows and macOS (Intel/ARM)
  • Dependency management moved from Poetry to uv; PyPI publishing via trusted publishing (OIDC)
  • pytz and leb128 dropped; lz4/zstd moved into the compression extra (stdlib zstd on 3.14+)
  • Type information ships as generated .pyi stubs validated by stubtest
  • CI across Python 3.11–3.14 and ClickHouse latest + LTS lines

Full details in CHANGELOG.md.

Thanks to @nils-borrmann-tacto, @vlad-zverev, @shsailaubay, @stankudrow, @turquoisehealth, @vizor-games, @baconfield, @objecthuman, @itssimon, @medikos and everyone who filed issues.