Python SDK

Python CLI

Scaffold a Python dataset project from your ClickHouse schema, regenerate definitions, serve locally, and export client contracts.

The hypequery command ships with the Python package and needs no Node tooling. python -m hypequery runs the same CLI. Install the serving and ClickHouse extras for the full onboarding path, then run init in a terminal:

pip install "hypequery[server,clickhouse]==0.1.0b2"
hypequery init analytics

init asks how to reach ClickHouse and which app to scaffold, then writes datasets for your tables into analytics/. Start the API it created:

cd analytics
hypequery dev

From another shell, query one of your datasets (the README init writes shows the call for your own tables):

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: every request queries as one developer, with no token. Before deploying, replace its auth with real authentication (see Authentication).

hypequery --help, hypequery <command> --help, hypequery help <command> and hypequery --version (or -V) work without the extras. Exit codes are 0 for success, 1 for a runtime failure and 2 for invalid usage.

Connection settings and .env

The CLI reads CLICKHOUSE_URL, or CLICKHOUSE_HOST, _PORT, _DATABASE, _USERNAME, _PASSWORD and _SECURE. Like the TypeScript CLI, it also loads .env.local and then .env from the folder you run it in, so the settings can live in a gitignored file:

# .env
CLICKHOUSE_HOST=localhost
CLICKHOUSE_PORT=8123
CLICKHOUSE_DATABASE=analytics
CLICKHOUSE_USERNAME=default
CLICKHOUSE_PASSWORD=your-password

A variable already set in your shell wins over both files. Only the CLI reads them (init, generate datasets and dev): the library and the apps init generates read the real environment, so in production set these as environment variables or secrets. To apply the same rules in a script of your own, call hypequery.cli.load_env_files(Path.cwd()) before ClickHouseConnection.from_env().

hypequery init

Creates a project from your live schema: datasets.py with one dataset per table, a schema.json snapshot, the application you choose, a pyproject.toml, a README.md and a .env.example. Existing files are never overwritten. The .env.example and README repeat how discovery connected: CLICKHOUSE_URL if it was set, otherwise the host, port and TLS setting that worked, with credentials left as placeholders.

Guided setup

In a terminal, init asks for whatever its options leave unset, in this order:

  1. Where to create the project (a new analytics folder when the current one isn't empty).
  2. How to reach ClickHouse: the connection already in your environment or .env, or a URL, username, password (hidden as you type) and database. Typed-in details are used only to read the schema and are never written to a file; init prints the .env lines to save, with the password left for you. If the connection fails, you can try again, change the details, or create the offline example instead.
  3. Which tables, only when the database has more than ten.
  4. The tenant column, only when one looks like a tenant id (tenant_id, organization_id, org_id, account_id or customer_id): use it, pick another column, or skip tenant isolation.
  5. Which app to scaffold: standalone, FastAPI, Django or Flask. The default follows your project: Django when there is a manage.py, otherwise whichever of the three your pyproject.toml or requirements.txt names.

It then shows a summary and the same choices as one command, for example hypequery init analytics --no-interactive --framework fastapi --tenant-column org_id, and asks before writing anything. Every option answers its question. --no-interactive, a CI environment variable, or input that isn't a terminal skip the questions entirely, and unset options take their defaults.

Options

OptionDescription
[directory] or --path <dir>Destination; defaults to the current directory
--tables a,bOnly inspect these tables (default: all)
--exclude-tables a,bSkip these tables
--all-tablesInspect every table (the default)
--tenant-column <column>Set tenant_key on tables that have this column, as generate datasets does
--framework standalone|fastapi|django|flaskThe application to write beside datasets.py (default standalone); see below
--skip-connectionWrite the offline orders example and seed.sql instead of inspecting a database
--no-interactiveNever ask questions, even in a terminal

Applications

--frameworkWritesRun with
standalone (default)app.py: an API from create_api, served as plain ASGI with no web frameworkhypequery dev
fastapiA FastAPI app.py that mounts the API, and a route of your ownuvicorn app:app --reload
djangoA small Django project (settings.py, urls.py, manage.py) and analytics.pypython manage.py runserver
flaskA Flask app.pyflask --app app run --port 5001

The standalone API is open for local development, as above. The framework apps authenticate:

  • Django and Flask serve hypequery's endpoints natively (urls(api), blueprint(api)) twice: at /api/analytics behind the framework's own login with the same-origin check, for @hypequery/react on pages the app serves, and at /api/agents behind a bearer token, for @hypequery/mcp and scripts. Until you wire them to your login, Flask's session API refuses everyone, and tenant-scoped datasets refuse signed-in Django users.
  • FastAPI has no login of its own, so its app mounts the bearer-token API at /api/agents and adds /analytics/<dataset>, a route of your own that queries as whoever its current_principal dependency returns. It refuses everyone until you wire that to your login. The project depends on fastapi directly; hypequery has no FastAPI extra.

The bearer token is HYPEQUERY_DEV_TOKEN, a local development token you set in the shell that runs the app. Their servers don't load .env for you (Flask does only with python-dotenv installed), so export the connection settings there too. The generated README walks through each app. Every choice also works with --skip-connection.

Schema discovery reads only system.tables and system.columns for the current database, so the user needs SHOW and SELECT on it. A read-only user works, whether its profile sets readonly = 1 or 2. If discovery fails, the message names what to check: credentials, host and port, the database, or grants.

Tenant-scoped datasets

With --tenant-column, each dataset gets a tenant_key, and every query must run for one tenant. The standalone app's developer principal has no tenant, so queries answer 403 FORBIDDEN ("requires runtime tenant scoping") until you give it one. For local testing, return one from developer in app.py:

return Principal(subject="developer", tenant_id="org_123")

In the FastAPI, Django and Flask apps, the development token reads the tenant in HYPEQUERY_DEV_TENANT instead: export it in the app's shell before starting it. Before real users, replace the open API or the token check with your own authentication so the tenant comes from the signed-in user. See Multi-tenancy.

Physical names map to the same camelCase semantic names as the TypeScript CLI. Numeric value columns get suggested sum and avg measures; ids and coordinates stay dimensions. Unsupported columns and likely tenant columns are reported, and nothing is written to the database. Review the generated semantics and configure tenant isolation explicitly before serving real data.

hypequery generate datasets

Regenerates datasets.py from the current schema. Also spelled generate:datasets, as in the TypeScript CLI.

OptionDescription
--output <file>Output file (default datasets.py)
--path <dir>Output directory containing datasets.py
--tables, --exclude-tablesTable selection, as for init. Without them, the selection init was given (recorded in schema.json) is reused
--all-tablesInspect every table, ignoring a recorded selection
--tenant-column <column>Set tenant_key on tables that have this column; reports tables that do not
--checkExit 1 if the file would change, without writing
--diffShow the drift without writing; exit 1 if any
--forceReplace the existing file; warns when it removes a configured tenant_key

Replacement is atomic and preserves the file's permissions. Use --check in CI to catch schema drift.

hypequery dev

Serves a local app with uvicorn. Requires the server extra.

OptionDescription
[app]Import string, default app:app, resolved from the working directory. It names an Api from create_api or its to_asgi_app application
--hostname, --host <addr>Bind address, default 127.0.0.1. Binding beyond loopback prints a warning
-p, --port <port>Port, default 8000
--reload / --no-watch (--no-reload)Reload on file changes; on by default

The dev runner is not hardened for a network. APIs created with a ProductionProfile are refused here; run those with start_server behind a reverse proxy instead. Startup failures are reported without their traceback so credentials in an exception never reach the terminal.

hypequery export client-contract

Writes the canonical client contract that generated React hooks and the MCP bridge consume, without starting a server or connecting to a database.

OptionDescription
--factory module:factoryZero-argument factory returning the definition-only metadata (required)
--output <file>Output file, default client-contract.json; - for stdout
--stdoutWrite canonical JSON only to stdout
--checkExit 1 if the artifact would change, without writing
--forceReplace an existing artifact

See React and MCP clients for the end-to-end workflow.

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.