NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #2766 most downloaded on PyPI
An asyncio driver for ClickHouse with native TCP support
Last release 1 months ago
14 Aug 2026
Release timing varies
gaps range from 2 weeks to 1.3 years
Nearly every release is documented
notes for 19 of 19 stable releases
Nothing withdrawn
no release was ever pulled
6 years old
19 releases · first in 2020
One column per quarter.
Major internal refactor: the protocol hot path is now compiled with Cython, modeled on the sister project asyncmy . The public API ( Connection , Curs
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.
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.
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)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)apilevel, threadsafety, paramstyle, connect() and the exception hierarchy are importable from asynch. (#159)DateTime64 was decoded as unsigned, silently corrupting pre-1970 timestamps.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)connect_timeout / send_receive_timeout / sync_request_timeout were accepted but never used. (#114)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)pytz and leb128 dropped; lz4/zstd moved into the compression extra (stdlib zstd on 3.14+).pyi stubs validated by stubtestFull 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.
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.
Measured against clickhouse-driver, the synchronous C-extension driver
(500k rows, best of 3; reproduce with make benchmark):
DateTime 13.8M vs 2.3M rows/s, String 21.7M vs
15.2M, Decimal +44%, UUID +56%, Array +15%; Int64/Float64 on parbenchmark/: a realistic wide-table workload,
per-column-type micro-benchmarks, concurrency and pool overheaduv sync --all-groups --all-extras (or make deps).pyi stubs validated by stubtestJSON type (ClickHouse 24.8+). Reading composes a
Dynamic/Variant reader per path with the shared-data sub-column and
returns nested dicts; writing accepts dicts or JSON text and emits the V2
object framing. Dynamic is readable as a column type in its own right.
The pre-24.8 Object('json') spelling keeps its own reader, since modern
servers reject the type outright (#142)Connection.cancel() / Cursor.cancel() stop a running query from another
task. Only the cancel packet is sent - the task awaiting the query owns the
read side and drains the stream, so the connection stays usable (#104)Connection.last_query exposes the statistics the server reported for the
most recent query (elapsed, rows/bytes progress, profile info); previously
reachable only as conn._connection.last_query (#85)Pool(idle_timeout=...) reaps connections that have been idle for longer
than the timeout, down to minsize. Without it the pool grows to its
high-water mark and keeps every connection for the process lifetime, which
is still the default (#137)liveness_grace
seconds (1.0 by default)lz4 and zstd moved into the compression extra. They were mandatory
even though compression cannot work without the optional
clickhouse-cityhash, so every install carried two unusable codec
libraries. On Python 3.14+ the stdlib compression.zstd is used and no
third-party zstd package is needed at all (#143)apilevel, threadsafety, paramstyle,
connect() and the exception hierarchy are now importable from asynch;
Cursor.arraysize is a read/write attribute. Additive only - no existing
behaviour changes. Based on #159 by @turquoisehealthleb128 (hand-rolled unsigned LEB128; also fixes non-canonical
varint encodings the signed encoder produced) and pytz (stdlib zoneinfo;
tzdata is pulled in on Windows only)asyncio.CancelledError
is a BaseException, so the teardown branch was skipped and
is_query_executing stayed set - which any asyncio.timeout around a
query, or a web framework cancelling a request task, would trigger (#93)connect_timeout and sync_request_timeout were accepted but never used:
a black-holed host hung on the OS connect timeout (taking alt_hosts
failover with it), and a ping() against an accepted-but-silent server
could stall a pool checkout indefinitely (#114)send_receive_timeout is now enforced on socket reads and writes; a server
that accepts a connection and then goes silent no longer hangs the client
indefinitely (#114)UnknownCompressionMethod: Unknown compression method: 'lz4', which reads as a typo in a valid method name;
it now says to install asynch[compression]max_block_size, so a readonly=1 user could not stream
at all, and the default buffer of 0 was rejected by the server outright.
The setting is only sent when a buffer size was requested (#67)clickhouses://host/db and Connection(secure=True) both went to the
plaintext port. Based on #162 by @stankudrowNone into a non-Nullable column raised whatever the serializer
happened to fail on (TypeError: cannot convert 'NoneType' object to bytes, AttributeError: 'NoneType' object has no attribute 'year');
it now reports the column and expected type. input_format_null_as_default
keeps working. Based on #146 by @vizor-gamesalt_hosts never worked as a fallback: the first host's failure aborted
the whole connect. Unreachable hosts are now skipped (with the socket torn
down between attempts) and NetworkError is raised only if all fail;
errors from a server that answers, such as bad credentials, propagate
unchanged. Based on #136 by @vlad-zverev_refresh "reconnected" them, but connect() returns early on an
apparently-open connection, so the reconnect was a no-op. Dead connections
are now discarded and replaced. Based on #145 by @nils-borrmann-tactoping() no longer lets OperationalError escape when the peer closes the
connection mid-read, which broke transparent pool reconnects. Based on #148
by @nils-borrmann-tactogetpass.getuser() raises OSError instead of KeyError since Python
3.13, breaking the handshake where no user name can be determined (e.g. in
containers). Based on #156 by @shsailaubayDateTime64 was decoded as an unsigned integer: pre-1970 values were
silently corrupted on read and failed on write; the wire value is a signed
Int64 tick countstr()/f"{...}" of status/scheme enums returned e.g.
ConnectionStatus.opened instead of opened on Python 3.11+Cursor.fetchone in streaming mode no longer swallows server errors
arriving mid-streamuse_numpy setting now emits a DeprecationWarningproto/utils/compat.py) removedFix params substitution for select queries by @dmkulazhenko in #141
Full Changelog: v0.3.0...v0.3.1
Remove warnings on deprecated API by @stankudrow in #125
Pool API cleansed by @stankudrow in #120lint group dependency by @stankudrow in #128Connection and Pool classes APIs -> preparing v0.3.0 release by @stankudrow in #130Full Changelog: v0.2.5...v0.3.0
Connection and Pool classes API. By @stankudrow in #130:
connected property from the Connection classCursor class as incoming parameter for the connection cursor methodclose async method more consistentasynch/connection.py::connect functionasyncio.AbstractServer for the Pool class (mypy is satisfied)asynch/pool.py::create_pool functionmypy dependency. By @stankudrow in #128.cursor.py module. By @stankudrow in #125.Pool API. By @stankudrow in #120.Pool object without raising AsynchPoolError("no free connections"). The issue #121 by @itssimon. By @stankudrow in #124.Reconsider the Connection , Cursor and Pool classes in terms of unification and marking some public API deprecated. by @stankudrow in #111
Pool dangling connections by @stankudrow in #109async for row in cursor: infinite loop error by @stankudrow in #112Connection, Cursor and Pool classes in terms of unification and marking some public API deprecated. by @stankudrow in #111strict option from the parse_dsn function and make the function stricter on URL validation by @stankudrow in #113Enums classes with non-simultaneously-coexisting-values by @stankudrow in #119Full Changelog: v0.2.4...v0.2.5
Add more validation rules in the parse_dsn function. By @stankudrow in #113
Reconsider the API of the Connection, Cursor and Pool classes and deprecate outdated methods or properties. Define the DB-API v2.0 compliant exception hierarchy. Update project dependencies and metadata. By @stankudrow in #111.
Fix infinite iteration case when a cursor object is put in the async for loop (the discussion #100 by @KuzenkovAG). By @stankudrow in #112.
Fix pool connection management (the discussion #108 by @DFilyushin) by @stankudrow in #109:
Pool class with the pool "startup()" as __aenter__ and "shutdown()" as __aexit__ methods;Pool class with the "connection()" method returning an asynchronous context manager responsible for acquiring connections from a pool object and releasing them back into the pool;Connection and Pool classes.Add the asynchronous context manager support to the Connection class. By @stankudrow in #107.
Make Python3.9 the minimum supported version. Update the project dependencies, metadata, tests. By @stankudrow in #106.
Connection failure leaves connection in an unusable state by @KPull in #82
bytearray index out of range error while reading a string. by @pufit in #90Full Changelog: v0.2.3...v0.2.4
IndexError cases from the BufferedReader class methods when reading from an empty buffer. By @stankudrow in #94.ExecuteContext manager class. By @KPull in #82.acquire method. By @lxneng in #81.Fix connection with secure=True and verify=False .
secure=True and verify=False.Cannot set verify_mode to CERT_NONE when check_hostname is enabled.Add Int128Column , Int256Column , UInt128Column , UInt256Column , Decimal256Column .
Int128Column, Int256Column, UInt128Column, UInt256Column, Decimal256Column. (#57)NestedColumn. (#54)IPv6Column. (#52)SimpleAggregateFunction for nested. (#41)Fix incorrect merge dev
Fix compression not working. ( #36 ) Add BoolColumn .
Fix LowCardinalityColumn keys column exception.
Fix bug in protocol for FixedString
- Fix bug with FixedString
FixedString- Fix syntax error
- Fix syntax error - Fix BufferReader.read_bytes
BufferReader.read_bytes- Fix bugs with TupleColumn
TupleColumnFix bugs with ArrayColumn and LowCardinalityColumn.
ArrayColumn and LowCardinalityColumn.Fix exception and read data bugs.
- Add connect pool.
- Release first version.
Your coding agent can read these notes before it upgrades. Set up the MCP server →