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 analyticsinit 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 devFrom 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-passwordA 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:
- Where to create the project (a new
analyticsfolder when the current one isn't empty). - 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;initprints the.envlines 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. - Which tables, only when the database has more than ten.
- The tenant column, only when one looks like a tenant id (
tenant_id,organization_id,org_id,account_idorcustomer_id): use it, pick another column, or skip tenant isolation. - 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 yourpyproject.tomlorrequirements.txtnames.
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
| Option | Description |
|---|---|
[directory] or --path <dir> | Destination; defaults to the current directory |
--tables a,b | Only inspect these tables (default: all) |
--exclude-tables a,b | Skip these tables |
--all-tables | Inspect every table (the default) |
--tenant-column <column> | Set tenant_key on tables that have this column, as generate datasets does |
--framework standalone|fastapi|django|flask | The application to write beside datasets.py (default standalone); see below |
--skip-connection | Write the offline orders example and seed.sql instead of inspecting a database |
--no-interactive | Never ask questions, even in a terminal |
Applications
--framework | Writes | Run with |
|---|---|---|
standalone (default) | app.py: an API from create_api, served as plain ASGI with no web framework | hypequery dev |
fastapi | A FastAPI app.py that mounts the API, and a route of your own | uvicorn app:app --reload |
django | A small Django project (settings.py, urls.py, manage.py) and analytics.py | python manage.py runserver |
flask | A Flask app.py | flask --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/analyticsbehind the framework's own login with the same-origin check, for@hypequery/reacton pages the app serves, and at/api/agentsbehind a bearer token, for@hypequery/mcpand 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/agentsand adds/analytics/<dataset>, a route of your own that queries as whoever itscurrent_principaldependency returns. It refuses everyone until you wire that to your login. The project depends onfastapidirectly; 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.
| Option | Description |
|---|---|
--output <file> | Output file (default datasets.py) |
--path <dir> | Output directory containing datasets.py |
--tables, --exclude-tables | Table selection, as for init. Without them, the selection init was given (recorded in schema.json) is reused |
--all-tables | Inspect every table, ignoring a recorded selection |
--tenant-column <column> | Set tenant_key on tables that have this column; reports tables that do not |
--check | Exit 1 if the file would change, without writing |
--diff | Show the drift without writing; exit 1 if any |
--force | Replace 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.
| Option | Description |
|---|---|
[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.
| Option | Description |
|---|---|
--factory module:factory | Zero-argument factory returning the definition-only metadata (required) |
--output <file> | Output file, default client-contract.json; - for stdout |
--stdout | Write canonical JSON only to stdout |
--check | Exit 1 if the artifact would change, without writing |
--force | Replace an existing artifact |
See React and MCP clients for the end-to-end workflow.