Guidance for working in this repository.
aiochclient — an async HTTP(S) ClickHouse client for Python. It talks to ClickHouse
over its HTTP interface (default http://localhost:8123/), converts types in both
directions, streams results, and decodes rows lazily. The public API is a single class,
ChClient, plus the Record row object and the ChClientError exception.
It has three interchangeable wire engines, selected on ChClient:
- TSV (default) — text
TSVWithNamesAndTypes, lazy per-row decoding. - RowBinary (
binary=True) — the row-oriented binary format; faster, best for streaming row-by-row viaiterate. - Native (
native=True) — ClickHouse's column-oriented binary format; fastest for bulk SELECT/INSERT, decoded whole-column-at-once without numpy.
Package is published to PyPI as aiochclient. Current version: 2.8.0 (see setup.py).
The package lives in aiochclient/:
client.py—ChClient, the public entry point. Holds connection params/headers, builds query strings, picks the right ClickHouseFORMAT, and orchestrates fetch/insert. Constructor flags:binary/native(engine),insert_block_size(Native INSERT block size; 0 = single atomic block). Public methods:execute,fetch,fetchrow,fetchval,iterate,insert_file,is_alive,close.fetchone/cursorare deprecated aliases.http_clients/— pluggable HTTP backend behindHttpClientABC(abc.py).choose_http_client()auto-detects whether the passed session is anaiohttp.ClientSessionorhttpx.AsyncClientand returns the matching impl (aiohttp.py/httpx.py). Both implementget/post_return_lines(streaming line generator, TSV/JSON) /post_return_bytes(streaming byte-chunk generator, RowBinary/Native) /post_no_return(also accepts an async-generator body for streamed Native INSERT) /close. Non-200 responses raiseChClientError.types.py— pure-Python type conversion (the TSV engine and the per-value binary read/write primitives).BaseTypesubclasses (one per ClickHouse type) implementconvert(CH bytes → Python),unconvert(Python → CH text), and the RowBinaryread/write.CH_TYPES_MAPPINGmaps CH type names → classes;PY_TYPES_MAPPINGmaps Python types → unconvert fns. Containers parse recursively viawhat_py_type. Also holds the binaryCursor(with bulk column reads) andread_column._types.pyx— Cython port oftypes.pyfor a speedup. Keep in sync withtypes.py— both must implement the same behavior. Also hosts the compiledRecord,build_records(column→row transpose),write_string_column, andrecord_new. Everything imports from_types(compiled) and falls back totypesif the C extension isn't built.binary.py— the RowBinary engine.BinaryReader(buffered O(n) reader over the byte-chunk stream),RowBinaryFabric/rows_from_binary(read),rows_to_binary(write),fetch_column_types/fetch_column_header(LIMIT 0 type probe for INSERTs). Imports theCursor,read_row,read_column,what_py_typefrom_types/types.native.py— the Native (columnar) engine. Read:decode_column(whole-column decode; bulk numerics via stdlibarray, strings/dates via the Cursor,_read_prefixfor LowCardinality state),blocks_from_native(yields aRecordlist per block, GC suppressed during the synchronous decode/build),rows_from_native. Write:encode_column(bulk column encoders),rows_to_native(one block) androws_to_native_stream(async generator of blocks for streamed INSERT)._wire_typesimplifies LowCardinality/Nested for the wire.records.py—Record(lazyMapping, access by name or index, decodes on first access). Re-exports the compiledRecord+record_from_decodedfrom_typeswhen built, else defines the pure version.RecordsFabric(TSV header rows),FromJsonFabric(JSON).sql.py— patchessqlparse's lexer/keywords so ClickHouse-specific syntax (FORMAT,DESCRIBE,SHOW,EXISTS) parses correctly. Imported for its side effects;client.pyusessqlparseto detect statement type and format.exceptions.py—ChClientError, plusNeedMoreData(internal, drives the binary reader's refill loop).
_execute() in client.py is the core. It substitutes params into the query string,
parses the statement (_parse_squery) to decide whether to fetch and which FORMAT to
append (Native / RowBinaryWithNamesAndTypes / TSVWithNamesAndTypes, or JSONEachRow
when json=True). Fetches: the columnar/binary engines stream byte chunks via
post_return_bytes and drive rows_from_native / rows_from_binary; TSV/JSON stream lines
via post_return_lines. fetch has a fast path that consumes Native results block-by-block.
INSERTs: Native encodes columnar blocks (streamed via rows_to_native_stream), RowBinary
encodes via rows_to_binary (both after a LIMIT 0 type probe), TSV via rows2ch, JSON via
json2ch. All public fetch methods delegate to _execute.
make format # isort + black
make check_format # verify formatting (black --target-version py310)
make build_cython # compile _types.pyx in place (python setup.py build_ext --inplace)
make test # installs dev reqs, starts a ClickHouse docker container, runs pytest
make docker-clickhouse # start ClickHouse on :8123 (container name "cs")
make html_types # cython annotated HTML for _types.pyx
Tests live in a single file, tests.py, and need a live ClickHouse at localhost:8123
(make docker-clickhouse provides one). Tests are async (pytest-asyncio, asyncio_mode = auto).
benchmarks.py benchmarks throughput.
The CI matrix (.github/workflows/tests.yml) runs Python 3.10–3.13 × four dependency sets
(plain / ciso8601 / cython / cython+ciso8601), defined in dev-requirements/. The minimum
supported Python is 3.10 (python_requires in setup.py).
- Formatting: black with
skip-string-normalization = true(single quotes are fine) and isort. Runmake formatbefore committing. - Optional native acceleration is the norm: any change to type conversion (or the compiled
helpers like
Cursor,read_column,read_row,Record,build_records,write_string_column) must be made in bothtypes.pyand_types.pyx, and any pure fallback inbinary.py/native.pykept in sync. Build withmake build_cython; run the suite both with the extension built and with it removed. - Speedup extras are optional installs:
ciso8601(fast datetime parsing),aiodns+faust-cchardet(aiohttp), and the compiled Cython extension. Code must work without any of them — always provide a pure-Python fallback path (see thetry/except ImportErrorblocks). - Install targets:
aiochclient[aiohttp],aiochclient[httpx], and their*-speedupsvariants.
- Query
paramsusestr.format, so literal{/}in SQL (e.g. cluster macros) must be escaped as{{/}}. - The TSV streaming backend asserts the response ends on a line boundary (
assert not buffer). - Native engine specifics:
decode=False(raw bytes) is TSV-only; the Native format carries no per-column timezone, so a tz-awareDateTime/DateTime64comes back as naive UTC. A multi-block native INSERT is not atomic on a client-side encoding error (blocks already streamed are committed) —insert_block_size=0forces a single atomic block. The GC is disabled inside the synchronous decode/build sections of a fetch (mass allocation otherwise trips the cyclic collector); it is always restored viatry/finally, and those sections contain noawait. - Both binary engines look up the target column types via a
LIMIT 0 RowBinaryWithNamesAndTypesprobe before an INSERT, since encoding is type-specific. perf_iterations/andperf_insert/are performance-experiment logs; they are pruned from the sdist/wheel (MANIFEST.in) and are not part of the package.