Quick start
Generate datasets from your ClickHouse schema with the Python CLI, or define them in your own code, then query and serve them.
The Python SDK is in beta
0.1.0b2 implements the same datasets and Serve protocol as TypeScript, and the same React hooks and MCP server work
against it. Its APIs may still change before 1.0, so pin the version you deploy.
Choose your route
This page has two paths:
- Route 1: Scaffold a project if you want a working API over your tables as quickly as possible
- Route 2: Datasets in your code if you want to add a semantic layer to an existing Python codebase
Both routes can be exposed over HTTP with Serve when you need named endpoints, validation, docs, or OpenAPI.
Route 1: Scaffold a project
Install the package
pip install "hypequery[server,clickhouse]==0.1.0b2"The clickhouse extra adds the driver; server adds uvicorn, for hypequery dev.
Create the project
hypequery init analyticsIn a terminal, init asks how to reach ClickHouse: the settings in your environment or a .env file, or a URL and
credentials you type, which it uses only to read the schema and never writes to a file. Then it asks which tables to
include when there are more than ten, whether to scope datasets by a tenant column, and which app to scaffold:
standalone, FastAPI, Django or Flask.
It writes one dataset per table to analytics/datasets.py, with the app beside it, and a README for your tables.
Serve it and run a query
cd analytics
hypequery devFrom another shell, query one of your datasets. Swap in one of your own tables and its columns; the generated README shows the exact call.
curl -s -X POST http://127.0.0.1:8000/datasets/orders/query \
-H "Content-Type: application/json" \
-d '{"dimensions": ["status"], "measures": ["totalCount"]}'The standalone app's API is open for local development, with no token. Interactive docs are at
http://127.0.0.1:8000/docs.
If that is all you need, continue with the Python CLI for every option and the framework apps, or review the generated definitions with the Datasets overview.
Route 2: Datasets in your code
In this route you define a dataset, a semantic model over a ClickHouse table, once, then query it by selecting dimensions and measures. The same definition powers ad hoc rollups, calculated measures, and (optionally) governed HTTP endpoints.
Install the package
pip install "hypequery[clickhouse]==0.1.0b2"Configure your ClickHouse env vars
Create .env:
CLICKHOUSE_HOST=example.clickhouse.cloud
CLICKHOUSE_PORT=8443
CLICKHOUSE_SECURE=true
CLICKHOUSE_DATABASE=default
CLICKHOUSE_USERNAME=cli_user
CLICKHOUSE_PASSWORD=super-secretThe hypequery CLI reads .env.local and .env from the folder you run it in; a variable already set in your shell
wins. CLICKHOUSE_URL (https://user:password@host:8443/database) can replace all six, but not be combined with
them.
Generate dataset definitions
Use the CLI to introspect ClickHouse and write dataset definitions from your live schema:
hypequery generate datasetsThis writes datasets.py, with one dataset per table in a datasets mapping keyed by table name. Re-run it when your
schema changes (--diff shows the drift first), then curate the generated dimensions and measures so the semantic
names match your product language.
Create a dataset client
The client plans and validates semantic queries, and the executor runs the SQL it generates on ClickHouse.
# client.py
from pathlib import Path
from hypequery.cli import load_env_files
from hypequery.datasets import create_dataset_client
from hypequery.execution import ClickHouseConnection, create_clickhouse_executor
# Fill unset variables from .env.local and .env, as the hypequery CLI does.
load_env_files(Path.cwd())
executor = create_clickhouse_executor(ClickHouseConnection.from_env())
analytics = create_dataset_client(executor=executor)Your code reads only the real environment unless you call load_env_files; in production, set the variables as
environment variables or secrets instead.
Review or customize a dataset
The generated file gives you a starting point. A curated dataset maps a source table to typed dimensions and measures, plus optional tenant and time keys:
# datasets.py
from hypequery.datasets import dataset, dimension, measure
orders = dataset(
"orders",
source="orders",
tenant_key="tenant_id",
time_key="created_at",
dimensions={
"id": dimension.number(),
"status": dimension.string(),
"country": dimension.string(),
"createdAt": dimension.timestamp(column="created_at"),
},
measures={
"revenue": measure.sum("amount"),
"orderCount": measure.count("id"),
},
)
datasets = {"orders": orders}Query the dataset
Select any combination of dimensions and measures. execute validates the request, generates parameterized SQL,
runs it, and returns rows plus metadata. Because the dataset has a tenant_key, every query runs for one tenant,
and hypequery adds the tenant predicate for you.
from hypequery.datasets import DatasetQuery, ExecutionContext, filter, order, tenant
from client import analytics
from datasets import orders
by_country = analytics.execute(
orders,
DatasetQuery(
dimensions=("country",),
measures=("revenue", "orderCount"),
filters=(filter.eq("status", "completed"),),
order_by=(order.desc("revenue"),),
limit=10,
),
context=ExecutionContext(tenant=tenant("org_123")),
)
print(by_country.data)Add calculated measures
When a value is calculated from other measures, declare it as a derived measure on the dataset and select it by name like any other measure.
# datasets.py
from hypequery.datasets import dataset, dimension, formula, measure
orders = dataset(
"orders",
source="orders",
tenant_key="tenant_id",
time_key="created_at",
dimensions={
"id": dimension.number(),
"status": dimension.string(),
"country": dimension.string(),
"createdAt": dimension.timestamp(column="created_at"),
},
measures={
"revenue": measure.sum("amount"),
"orderCount": measure.count("id"),
"averageOrderValue": measure.derived(
formula.divide("revenue", formula.null_if_zero("orderCount")),
),
},
)
datasets = {"orders": orders}aov_by_country = analytics.execute(
orders,
DatasetQuery(
dimensions=("country",),
measures=("averageOrderValue",),
filters=(filter.eq("status", "completed"),),
order_by=(order.desc("averageOrderValue"),),
limit=10,
),
context=ExecutionContext(tenant=tenant("org_123")),
)If that is all you need, continue with the Datasets overview, or dig into Dimensions and Measures.
Optional: expose over HTTP with Serve
Both routes stay in-process until you need an HTTP boundary. hypequery.serve turns datasets into authenticated
endpoints with validation, docs and OpenAPI, as plain ASGI that any Python server or framework can host. Route 1's
project already has one in app.py. Add the server extra for hypequery dev:
pip install "hypequery[server,clickhouse]==0.1.0b2"Serve datasets
Create an API, install a query endpoint for each dataset, and expose it as an ASGI app:
# app.py
from hypequery.serve import (
HttpSecurity,
Principal,
ServeRequest,
create_api,
create_dataset_endpoint,
from_context,
to_asgi_app,
)
from client import analytics
from datasets import datasets
def developer(request: ServeRequest) -> Principal:
# Open for local development: every request queries as one developer, for one tenant.
# Replace auth with real authentication (jwt_strategy, or your app's login) before deploying.
return Principal(subject="developer", tenant_id="org_123")
api = create_api(
auth=from_context(developer, same_origin=False),
security=HttpSecurity(allowed_hosts=("127.0.0.1", "localhost")),
development_docs=True,
)
for name, definition in datasets.items():
create_dataset_endpoint(dataset=definition, client=analytics).install(
api, f"/datasets/{name}/query"
)
app = to_asgi_app(api)This generates POST /datasets/orders/query for flexible same-dataset rollups. Endpoints validate dimensions, measures,
filters, time grains, and ordering against the definition, and take the tenant from the principal, never from the
request. See Dataset endpoints for per-endpoint auth, caching, and limits, and
Authentication for real users.
To serve from an app you already have, mount the same API in FastAPI, or serve it natively from Django or Flask.
Consume from React and agents
The same React hooks and MCP server as TypeScript work against a Python API. Declare client endpoints, export their
contract with hypequery export client-contract, and generate a typed client from it; see
React and MCP clients.
Preview docs and routes
Start the dev server against app.py:
hypequery devWith development_docs=True, the API exposes:
- docs at
http://127.0.0.1:8000/docs - OpenAPI at
http://127.0.0.1:8000/openapi.json - your routes, such as
http://127.0.0.1:8000/datasets/orders/query