Skip to main content

aiochlite

Python GitHub License PyPI - Version PyPI - Downloads GitHub Actions Workflow Status codecov GitHub last commit

Lightweight asynchronous ClickHouse client for Python built on aiohttp.

Table of Contents

Features

  • Lightweight - minimal dependencies, only aiohttp required
  • Streaming support - efficient processing of large datasets with .stream()
  • Export formats - raw Parquet / CSV / TSV / JSON / Arrow / ORC payloads via .fetch_format() and .stream_format()
  • External tables - advanced temporary data support
  • Type conversion - automatic conversion between Python and ClickHouse types
  • Type-safe - full type hints coverage
  • Flexible - custom sessions, compression, query settings

Why aiochlite?

  • Real asyncio I/O: built on aiohttp without wrapping blocking code in a thread pool.
  • Fast decoding: uses RowBinaryWithNamesAndTypes and lets you choose between Row wrappers (fetch()) and raw tuples (fetch_rows()).
  • Small surface area: minimal dependencies and a focused API for ClickHouse HTTP.

Installation

pip install aiochlite

Quick Start

Basic Connection

from aiochlite import AsyncChClient

# Using context manager (recommended)
async with AsyncChClient(
    url="http://localhost:8123",
    user="default",
    password="",
    database="default"
) as client:
    result = await client.fetch("SELECT 1")

# Or manual connection management
client = AsyncChClient("http://localhost:8123")
try:
    assert await client.ping()
    result = await client.fetch("SELECT 1")
finally:
    await client.close()

Execute Query

await client.execute("""
    CREATE TABLE IF NOT EXISTS users (
        id UInt32,
        name String,
        email String
    ) ENGINE = MergeTree() ORDER BY id
""")

Insert Data

# Insert dictionaries
data = [
    {"id": 1, "name": "Alice", "email": "alice@example.com"},
    {"id": 2, "name": "Bob", "email": "bob@example.com"},
]
await client.insert("users", data)

# Insert tuples
data = [
    (3, "Charlie", "charlie@example.com"),
    (4, "Diana", "diana@example.com"),
]
await client.insert("users", data, column_names=["id", "name", "email"])

# Insert with settings
await client.insert(
    "users",
    [{"id": 5, "name": "Eve", "email": "eve@example.com"}],
    settings={"max_insert_block_size": 100000}
)

Fetch Results

# Fetch all rows
rows = await client.fetch("SELECT * FROM users")
for row in rows:
    print(f"ID: {row.id}, Name: {row.name}, Email: {row.email}")

# Fetch one row
row = await client.fetchone("SELECT * FROM users WHERE id = 1")
if row:
    print(row.name)  # Attribute access
    print(row["name"])  # Dictionary-style access
    print(row.first())  # Get first column value

# Fetch single value
count = await client.fetchval("SELECT count() FROM users")
print(f"Total users: {count}")

# Iterate over results (for large datasets)
async for row in client.stream("SELECT * FROM users"):
    print(row.name)

Export Formats

Get the raw server payload in any ClickHouse output format — Parquet, CSV, TSV, JSON, Arrow, ORC and more. Both methods return raw bytes exactly as produced by the server (decode text formats yourself).

# Whole result at once
parquet = await client.fetch_format("SELECT * FROM users", "Parquet")
csv = (await client.fetch_format("SELECT * FROM users", "CSVWithNames")).decode()

# Chunked streaming for large result sets
with open("users.parquet", "wb") as f:
    async for chunk in client.stream_format("SELECT * FROM users", "Parquet"):
        f.write(chunk)

# Query parameters, settings and external tables work as usual
ndjson = await client.fetch_format(
    "SELECT * FROM users WHERE id > {id:UInt32}",
    "JSONEachRow",
    params={"id": 10},
)

Supported formats (ExportFormat):

Group Formats
Columnar / binary Parquet, Arrow, ArrowStream, ORC, Avro, Native, RowBinary, RowBinaryWithNames, RowBinaryWithNamesAndTypes
Separated values CSV, CSVWithNames, CSVWithNamesAndTypes, TSV, TSVWithNames, TSVWithNamesAndTypes, TabSeparated, TabSeparatedWithNames, TabSeparatedWithNamesAndTypes, TSKV, Values
JSON JSON, JSONStrings, JSONCompact, JSONColumns, JSONEachRow, JSONStringsEachRow, JSONObjectEachRow, JSONCompactEachRow, JSONCompactEachRowWithNames, JSONCompactEachRowWithNamesAndTypes
Human-readable XML, Markdown, Vertical, Pretty, PrettyCompact

Any other output format the server accepts can still be passed at runtime (type checkers will flag it).

[!WARNING] fetch_parquet() and stream_parquet() are deprecated and will be removed in a future release. Use fetch_format(query, "Parquet") and stream_format(query, "Parquet") instead.

Query Parameters

# Basic types
result = await client.fetch(
    "SELECT * FROM users WHERE id = {id:UInt32}",
    params={"id": 1}
)

# Lists and tuples (arrays)
result = await client.fetch(
    "SELECT * FROM users WHERE id IN {ids:Array(UInt32)}",
    params={"ids": [1, 2, 3]}  # or tuple: (1, 2, 3)
)

# Datetime and date
from datetime import datetime, date

result = await client.fetch(
    "SELECT * FROM events WHERE created_at > {dt:DateTime} AND date = {d:Date}",
    params={
        "dt": datetime(2025, 12, 14, 15, 30, 45),
        "d": date(2025, 12, 14)
    }
)

# UUID
from uuid import UUID

result = await client.fetch(
    "SELECT * FROM users WHERE uuid = {uid:UUID}",
    params={"uid": UUID("550e8400-e29b-41d4-a716-446655440000")}
)

# Decimal
from decimal import Decimal

result = await client.fetch(
    "SELECT * FROM products WHERE price > {price:Decimal(10, 2)}",
    params={"price": Decimal("99.99")}
)

# Nested arrays and maps
result = await client.fetch(
    "SELECT {matrix:Array(Array(Int32))} AS matrix, {data:Map(String, Int32)} AS data",
    params={
        "matrix": [[1, 2], [3, 4]],
        "data": {"a": 1, "b": 2}
    }
)

Supported parameter types:

  • Basic: int, float, str, bool, None
  • Collections: list, tuple, dict
  • Date/Time: datetime, date, timedelta
  • Special: UUID, Decimal, bytes

See Type Conversion for full type mapping details.

Query Settings

rows = await client.fetch(
    "SELECT * FROM users",
    settings={
        "max_execution_time": 60,
        "max_block_size": 10000
    }
)

External Tables

from aiochlite import ExternalTable

external_data = {
    "temp_data": ExternalTable(
        structure=[("id", "UInt32"), ("value", "String")],
        data=[
            {"id": 1, "value": "foo"},
            {"id": 2, "value": "bar"},
        ]
    )
}

result = await client.fetch(
    """
    SELECT t1.id, t1.name, t2.value
    FROM users t1
    JOIN temp_data t2 ON t1.id = t2.id
    """,
    external_tables=external_data
)

JSON Type

[!NOTE] For ClickHouse versions where JSON is still considered experimental, set allow_experimental_json_type=1 via client settings.

await client.execute("DROP TABLE IF EXISTS json_demo")
await client.execute("CREATE TABLE json_demo (id UInt32, doc JSON) ENGINE = Memory")

await client.insert(
    "json_demo",
    [{"id": 1, "doc": {"a": 1, "b": [True, None, {"c": "x"}]}}],
)

row = await client.fetchone("SELECT id, doc FROM json_demo WHERE id = 1")
print(row["doc"])  # Output: {"a": 1, "b": [True, None, {"c": "x"}]}

Error Handling

from aiochlite import ChClientError

try:
    await client.execute("SELECT * FROM non_existent_table")
except ChClientError as e:
    print(f"Query failed: {e}")

Custom Session

from aiohttp import ClientSession, ClientTimeout

timeout = ClientTimeout(total=30)
async with ClientSession(timeout=timeout) as session:
    async with AsyncChClient(url="http://localhost:8123", session=session) as client:
        result = await client.fetch("SELECT 1")

Enable Compression

async with AsyncChClient(url="http://localhost:8123", enable_compression=True) as client:
    result = await client.fetch("SELECT * FROM users")

Type Conversion

aiochlite uses ClickHouse’s RowBinaryWithNamesAndTypes for result decoding:

  • fetch, fetchone, fetchval, stream automatically append FORMAT RowBinaryWithNamesAndTypes and decode rows into Python values.
  • Queries passed to these methods must not contain a FORMAT ... clause.
  • Use execute() for statements that don’t return rows.

Automatic type conversion from ClickHouse:

ClickHouse Type Python Type Notes
Numeric
UInt8, UInt16, UInt32, UInt64 int
Int8, Int16, Int32, Int64 int
Float32, Float64 float
Decimal(P, S) Decimal Precision preserved
Decimal32(S), Decimal64(S), Decimal128(S), Decimal256(S) Decimal Precision preserved
String
String str
FixedString(N) str Null padding stripped
Date/Time
Date date
Date32 date
DateTime datetime tzinfo only if the type includes a timezone
DateTime64(P) datetime tzinfo only if the type includes a timezone
Time timedelta Signed seconds; supports values beyond 24h
Time64(P) timedelta timedelta is microsecond-precision, so P > 6 is truncated
Special
UUID UUID
IPv4 ipaddress.IPv4Address
IPv6 ipaddress.IPv6Address
Enum8, Enum16 str Enum value name
Bool bool
Composite
Array(T) list Elements converted recursively
Tuple(T1, T2, ...) tuple Elements converted recursively
Map(K, V) dict Keys and values converted
Modifiers
Nullable(T) T | None Nulls become None
LowCardinality(T) T Transparent wrapper
Other
JSON Any json.loads() result

Python to ClickHouse conversion:

When sending data to ClickHouse (query parameters and inserts), Python types are automatically converted:

  • datetimeYYYY-MM-DD HH:MM:SS
  • dateYYYY-MM-DD
  • timedeltaHH:MM:SS[.ffffff] (signed; suitable for Time / Time64)
  • UUID / Decimal → string representation
  • list → array literal (e.g. [1,2,3])
  • tuple → tuple literal (e.g. (1,2,3))
  • dict → map literal (e.g. {'k':'v'})
  • bytes → UTF-8 decoded string
  • NoneNULL
  • bool1/0 for query parameters, true/false inside container literals

Benchmarks

Benchmark scripts live in benchmarks/.

[!NOTE] Benchmarks always depend on machine and environment (CPU, RAM, kernel, ClickHouse version/config, network, etc). These results were captured on a local machine with 8 CPU cores (16 threads) and 32 GB RAM, running ClickHouse 26.3 LTS. Each client uses the configuration recommended by its documentation, including the aiohttp-speedups extra for aiochclient.

Latest fetch-and-decode results for 100,000 rows (5 rounds, measured 2026-08-14):

Client Average Throughput Time per row
clickhouse-connect (async) 156.57 ms 638,704 rows/s 1.6 µs
aiochlite (tuples) 287.75 ms 347,524 rows/s 2.9 µs
aiochlite (Row, eager decoding) 333.63 ms 299,729 rows/s 3.3 µs
aiochclient 350.22 ms 285,532 rows/s 3.5 µs

Versions: aiochlite 1.3.0, clickhouse-connect 1.7.1, aiochclient 2.7.0, Python 3.14.5, and ClickHouse 26.3.17.110.

clickhouse-connect includes compiled C extensions. In contrast, aiochlite is pure Python and has a single dependency: aiohttp.

License

MIT License

Copyright (c) 2025 darkstussy

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiochlite-1.4.0.tar.gz (104.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aiochlite-1.4.0-py3-none-any.whl (28.0 kB view details)

Uploaded Python 3

File details

Details for the file aiochlite-1.4.0.tar.gz.

File metadata

  • Download URL: aiochlite-1.4.0.tar.gz
  • Upload date:
  • Size: 104.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for aiochlite-1.4.0.tar.gz
Algorithm Hash digest
SHA256 240527e72bbe2b3509c2bd75079f35aa0cfcbbedce24989e87cbc98a0e851052
MD5 f23b4d0e7f8563e7ee368140823c6ca4
BLAKE2b-256 be7321010d217e2044df24f629482478e0b4571893241807e88232653da7bc50

See more details on using hashes here.

Provenance

The following attestation bundles were made for aiochlite-1.4.0.tar.gz:

Publisher: release.yml on DarkStussy/aiochlite

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aiochlite-1.4.0-py3-none-any.whl.

File metadata

  • Download URL: aiochlite-1.4.0-py3-none-any.whl
  • Upload date:
  • Size: 28.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for aiochlite-1.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cfd84a9c65f5f348e30295fe7d586783c74042437886eb0fe59ed5c753c85241
MD5 ec8a37cc7fe770031534a7eb482bda21
BLAKE2b-256 b13c40d867de8b1d1d7d5b7e51e242cb20011ea32b0a38cf61c3e6fe2a1ec2d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for aiochlite-1.4.0-py3-none-any.whl:

Publisher: release.yml on DarkStussy/aiochlite

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.8.2

2 files

1.8.1

2 files

1.8.0

2 files

1.7.1

2 files

1.7.0

2 files

1.6.0

2 files

1.5.0

2 files

1.4.1

2 files

This release

1.4.0 This release

2 files

1.3.0

2 files

1.2.0

2 files

1.1.0

2 files

1.0.2

2 files

1.0.1

2 files

1.0.0

2 files

0.2.0

2 files

0.1.0

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page