Python SDK

Execution

Validate, inspect SQL, and execute Python dataset queries.

create_dataset_client is the entry point for running semantic queries. It plans each query, hands the compiled statement to an executor, and returns rows keyed by column name.

from hypequery.datasets import create_dataset_client
from hypequery.execution import ClickHouseConnection, create_clickhouse_executor

# Reads CLICKHOUSE_URL, or CLICKHOUSE_HOST, _PORT, _DATABASE, _USERNAME, _PASSWORD and
# _SECURE; or pass host=..., username=..., password=..., database=... explicitly.
executor = create_clickhouse_executor(ClickHouseConnection.from_env())
analytics = create_dataset_client(executor=executor)

The client does not own the executor, so call executor.close() when your application shuts down. The factory connects as soon as it is called, so create the executor in your app's startup rather than at import time if the app must start while ClickHouse is down. In a FastAPI app, close it from the app's lifespan. For async code, use create_async_dataset_client with an async executor and await analytics.execute(...).

In Django and Flask, let the Django or Flask integration own the client: those frameworks have no shutdown hook, and the integrations create, close and tenant-bind it for you.

Connecting

ClickHouseConnection(host="ch.internal", database="analytics", username="reader", password=password)
ClickHouseConnection.from_url("clickhouses://reader:pw@ch.example.com/analytics")
ClickHouseConnection.from_env()
URLConnects with
clickhouse://, http://Plain HTTP (port 8123 by default)
clickhouses://, https://TLS (port 8443 by default)
clickhousedb://, clickhouse+http://SQLAlchemy URLs for the HTTP interface; ?secure=true or ?protocol=https for TLS

The path is the database (default when omitted), and the user and password are percent-decoded. Native-protocol URLs such as clickhouse+native:// are refused: hypequery sends values as server-side parameters, which only the HTTP interface carries. No error message repeats the URL.

from_env() reads CLICKHOUSE_URL when it is set, and CLICKHOUSE_HOST, _PORT, _DATABASE, _USERNAME, _PASSWORD and _SECURE otherwise. Setting both is an error. To reuse a SQLAlchemy engine's server, see SQLAlchemy.

Client methods

MethodUse it for
analytics.validate(target, query, context=...)Check a query without executing it
analytics.to_sql(target, query, context=...)Inspect a redacted debug statement
analytics.execute(target, query, context=...)Plan, execute, and return rows
analytics.for_tenant(scope)Get a client bound to one tenant

A target is a Dataset or the name of a registered dataset. A query is a DatasetQuery or a plain mapping, validated strictly with unknown keys rejected. A mapping uses the Python field names, so write order_by, not the HTTP body's orderBy.

DatasetQuery(timezone="Europe/Berlin") sets the zone for time buckets and offset-free time-key bounds for one query; create_dataset_client(timezone=...) sets the default, UTC unless given.

Validate

from hypequery.datasets import DatasetQuery, filter, order

validation = analytics.validate(
    orders,
    DatasetQuery(dimensions=("country",), filters=(filter.eq("status", "completed"),)),
)

if not validation.valid:
    print(validation.errors)

Inspect SQL

sql = analytics.to_sql(
    orders,
    DatasetQuery(
        dimensions=("country",),
        measures=("revenue",),
        filters=(filter.eq("status", "completed"),),
        order_by=(order.desc("revenue"),),
        limit=10,
    ),
)

The statement has placeholders instead of values and cannot be executed.

Results

execute returns a result with data (rows keyed by column name), columns, and meta with query_id, row_count, timing_ms, and cache.

meta.pagination is None unless you pass paginate=True:

page = analytics.execute(orders, DatasetQuery(dimensions=("country",), limit=50, offset=100), paginate=True)
page.meta.pagination  # Pagination(limit=50, offset=100, has_more=True)

The client fetches one extra row to compute has_more and drops it from data. There is no total row count.

Value types differ from the HTTP wire, where every measure is a string:

  • integer counts come back as int;
  • floating-point results come back as float;
  • Decimal values come back as str, to keep their precision;
  • timestamps come back as ISO 8601 strings.

For analysis, result.to_pandas(), to_polars() and to_arrow() restore real types from the dataset definition. See pandas, Polars and Arrow.

On this page

Get early access to hypequery cloud.

Cloud hosts the same model you run in your backend, with a managed API and MCP server, versions, and rollback. We're letting teams in a few at a time. Join the waitlist and we'll reach out when your project can go live.

How do you want to build your analytics layer? (Optional)
What features do you most need? (Optional)

Pick as many as you like.

Only your email is required. All other questions are optional.