CLI Commands¶
The hyper command-line tool for project management, database operations, and development. All commands are run via uv run hyper.
Commands Overview¶
| Command | Description |
|---|---|
hyper new |
Create a new project |
hyper run |
Start the development server (foreground) |
hyper start |
Start server in background (daemon, PID file) |
hyper stop |
Graceful shutdown (SIGTERM → drain → exit) |
hyper restart |
Stop + start |
hyper status |
Check if server is running |
hyper setup |
Create tables from models (topological FK ordering) |
hyper routes |
List all registered routes |
hyper makemigrations |
Generate migration files from model changes |
hyper migrate |
Apply or rollback migrations |
hyper createsuperuser |
Create an admin user |
hyper collectstatic |
Collect static files for production |
hyper shell |
Interactive Python shell with app context |
hyper dbshell |
PostgreSQL interactive shell |
hyper inspectdb |
Generate model code from existing tables |
hyper dumpdata |
Export database rows to JSON |
hyper loaddata |
Import JSON fixtures into database |
hyper doctor |
Diagnose build, config, database, and perf issues |
hyper systemd install |
Generate and install systemd service unit |
hyper systemd uninstall |
Remove systemd service |
hyper systemd status |
Show systemd service status |
hyper benchmark |
Run EXPLAIN ANALYZE performance regression tests |
hyper new¶
Create a new project with scaffolded files and configuration.
uv run hyper new myproject # minimal project
uv run hyper new myproject --with-db # + database configuration
uv run hyper new myproject --with-auth # + authentication
uv run hyper new myproject --with-admin # + admin interface
uv run hyper new myproject --full # all of the above
Options¶
| Flag | Description |
|---|---|
--with-db |
Add database configuration, models.py, and migration setup |
--with-auth |
Add authentication (login/logout views, User model, session config) |
--with-admin |
Add HyperAdmin interface with auto-CRUD |
--full |
Enable all flags: --with-db --with-auth --with-admin |
Generated file structure¶
Minimal (hyper new myproject):
myproject/
├── app.py # Application entry point with example route
├── .gitignore # Git ignore rules (*.pyc, __pycache__, .env, etc.)
├── README.md # Project README with quick start instructions
├── pyproject.toml # Python project config with hyperdjango dependency
├── templates/ # HTML templates
│ └── base.html # Base template with <!DOCTYPE html> skeleton
└── static/ # Static files (CSS, JS, images)
With database (--with-db):
myproject/
├── app.py # App with db.configure() call
├── models.py # Example model with TimestampMixin
├── migrations/ # Migration directory (empty initially)
├── .gitignore
├── README.md
├── pyproject.toml
├── templates/
│ └── base.html
└── static/
With auth (--with-auth):
myproject/
├── app.py # App with auth middleware registered
├── models.py # User model + additional models
├── migrations/
├── .gitignore
├── README.md
├── pyproject.toml
├── templates/
│ ├── base.html
│ └── login.html # Login form template
└── static/
Full (--full):
myproject/
├── app.py # Full app with db, auth, admin configured
├── models.py # User + example models
├── migrations/
├── .gitignore
├── README.md
├── pyproject.toml
├── templates/
│ ├── base.html
│ └── login.html
└── static/
Generated app.py (--full)¶
The generated app.py includes:
from hyperdjango import HyperApp, Response
from hyperdjango.auth import SessionAuth
from hyperdjango.admin import HyperAdmin
import models
app = HyperApp()
# Database
app.configure_db(
host="localhost",
port=5432,
database="myproject",
user="postgres",
password="postgres",
)
# Auth
app.use(SessionAuth(secret="change-me-in-production"))
# Admin
admin = HyperAdmin(app)
admin.register(models.User)
@app.get("/")
async def index(request):
return Response.html("<h1>Welcome to myproject</h1>")
hyper run¶
Start the development server using the native Zig HTTP server.
uv run hyper run # default: 0.0.0.0:8000
uv run hyper run --port 3000 # custom port
uv run hyper run --host 127.0.0.1 # localhost only
uv run hyper run --prod # production mode
Options¶
| Flag | Default | Description |
|---|---|---|
--host |
0.0.0.0 |
Bind address. Use 127.0.0.1 for localhost-only access |
--port |
8000 |
Port number |
--prod |
off | Production mode: validates config, disables debug, requires SECRET_KEY |
Development mode (default)¶
- Hot reload enabled: file changes trigger automatic restart via native kqueue/inotify watcher
- Debug error pages with tracebacks
- Verbose logging to stdout
Production mode (--prod)¶
Production mode performs configuration validation before starting:
- SECRET_KEY required: Refuses to start without a configured secret key
- Debug disabled: No tracebacks in error responses
- Hot reload disabled: No file watching overhead
- Optimized logging: Structured JSON logs instead of human-readable
hyper start¶
Start the server as a background daemon. Writes a PID file and redirects output to a log file.
Options¶
| Flag | Default | Description |
|---|---|---|
--host |
127.0.0.1 |
Bind address |
--port |
8000 |
Port number |
--app |
app:app |
App import path |
--prod |
off | Production mode |
Creates .hyper.<port>.pid (PID file) and .hyper.<port>.log (output log).
hyper stop¶
Gracefully stop a running server. Sends SIGTERM, waits for the drain timeout, then SIGKILL as fallback.
Options¶
| Flag | Default | Description |
|---|---|---|
--port |
8000 |
Port of the server to stop |
--timeout |
30 |
Seconds to wait before SIGKILL |
hyper restart¶
Stop and restart the server.
Inherits all options from hyper start plus --timeout from hyper stop.
hyper status¶
Check if a server is running on the given port.
Reads the PID file and verifies the process is alive. Cleans up stale PID files.
hyper systemd¶
Manage systemd service integration for production deployments.
hyper systemd install¶
Generate and install a systemd unit file with production-grade defaults.
# As root: install and optionally enable
sudo hyper systemd install --app app:app --port 8000 --enable
# Without root: generates unit file in current directory
hyper systemd install --app app:app --port 8000
| Flag | Default | Description |
|---|---|---|
--host |
0.0.0.0 |
Bind address |
--port |
8000 |
Port number |
--app |
app:app |
App import path |
--name |
dir name | Service name |
--user |
current user | Run-as user |
--enable |
off | Enable and start immediately |
hyper systemd uninstall¶
Stop, disable, and remove the systemd unit file.
hyper systemd status¶
Show the current service status via systemctl status.
hyper setup¶
Create database tables from app models. Introspects all Model subclasses loaded by the app, generates CREATE TABLE IF NOT EXISTS SQL for each, and executes it. Tables are created in topological order (Kahn's algorithm) so foreign key dependencies are satisfied.
uv run hyper setup --app myapp.app:app # create tables
uv run hyper setup --app myapp.app:app --seed seed:run # create + seed
uv run hyper setup --app myapp.app:app --drop # drop and recreate (DESTRUCTIVE)
uv run hyper setup --app examples.rest_api.app:app # dotted module paths work
Options¶
| Flag | Description |
|---|---|
--app |
App import path (module:attribute). Required. |
--database |
Override database URL (default: reads from app's database_url) |
--seed |
Seed function import path (module:function). Runs after tables. |
--drop |
Drop all tables before recreating. DESTRUCTIVE. |
How it works¶
- Imports the app module — this registers all
Modelsubclasses in the model registry - Reads the database URL from the app or
--databaseflag - Sorts models by FK dependencies using topological sort (Kahn's algorithm)
- Generates SQL for each model: column types, PRIMARY KEY, UNIQUE, DEFAULT, FOREIGN KEY REFERENCES
- Handles special Meta options:
Meta.unlogged = TruegeneratesCREATE UNLOGGED TABLE, composite primary keys generate table-levelPRIMARY KEY (col_a, col_b)constraints - Creates vector indexes with optional
WITH (...)tuning parameters fromVectorField(index_params=...) - Executes DDL in dependency order
Field type mapping¶
| Python type | PostgreSQL type |
|---|---|
int |
INTEGER (or SERIAL if auto) |
str |
TEXT (or VARCHAR(n)) |
float |
DOUBLE PRECISION |
bool |
BOOLEAN |
datetime |
TIMESTAMPTZ |
bytes |
BYTEA |
Default values¶
Field(default=...) values are translated to SQL DEFAULT clauses:
| Python default | SQL DEFAULT |
|---|---|
True / False |
DEFAULT TRUE/FALSE |
0, 42 |
DEFAULT 0, 42 |
"hello" |
DEFAULT 'hello' |
"now()" |
DEFAULT NOW() |
"" |
DEFAULT '' |
Example¶
# models.py
from datetime import datetime
from hyperdjango.models import Field, Model
class User(Model):
class Meta:
table = "users"
id: int = Field(primary_key=True, auto=True)
username: str = Field(unique=True)
email: str = Field()
is_active: bool = Field(default=True)
created_at: datetime = Field(default="now()")
$ uv run hyper setup --app myapp:app
Found 1 model(s): User
users ✓
1 table(s) created.
Setup complete!
Generated SQL:
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE,
email VARCHAR(255),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
hyper routes¶
List all registered routes with their HTTP methods and handler names.
Output format¶
METHOD PATH HANDLER
GET / index
GET /articles/ article_list
GET /articles/{id:int}/ article_detail
POST /articles/ article_create
PUT /articles/{id:int}/ article_update
DELETE /articles/{id:int}/ article_delete
GET /admin/ admin_index
GET /health health_check
WS /ws/chat chat_handler
The output includes:
- METHOD: HTTP method (GET, POST, PUT, DELETE, PATCH) or WS for WebSocket
- PATH: URL pattern with parameter types shown (e.g.,
{id:int},{slug:str}) - HANDLER: Python function name of the view
Routes are listed in registration order. Admin routes (if HyperAdmin is registered) appear at the end.
hyper makemigrations¶
Detect model changes by comparing the current model definitions against the last migration state, and generate migration files.
uv run hyper makemigrations
uv run hyper makemigrations --name "add_user_email" # custom migration name
uv run hyper makemigrations --dry-run # preview without writing
Options¶
| Flag | Description |
|---|---|
--name |
Custom name for the migration file (used as suffix) |
--dry-run |
Show what migration would be generated without writing any files |
How it works¶
- Introspects all registered Model classes and their fields
- Compares against the state stored in the latest migration file
- Detects: added fields, removed fields, changed field types, renamed tables, new models, deleted models
- Generates a numbered migration file in the
migrations/directory
Generated migration file¶
# migrations/0002_add_user_email.py
from hyperdjango.migrations import Migration, AddField, Field
class Migration_0002(Migration):
dependencies = ["0001_initial"]
operations = [
AddField(
table="users",
name="email",
field=Field(type="varchar(254)", nullable=False, unique=True),
),
]
def rollback_operations(self):
return [
DropField(table="users", name="email"),
]
Dry run output¶
Detected changes:
users:
+ Add field email (varchar(254), NOT NULL, UNIQUE)
~ Change field name max_length: 100 → 200
Would generate: migrations/0002_auto.py
(dry-run mode, no files written)
hyper migrate¶
Apply pending migrations to the database or rollback to a previous state.
uv run hyper migrate # apply all pending
uv run hyper migrate --rollback # rollback last migration
uv run hyper migrate --target 0003 # migrate to specific version
uv run hyper migrate --fake # mark as applied without running SQL
Options¶
| Flag | Description |
|---|---|
--rollback |
Rollback the most recently applied migration |
--target VERSION |
Migrate forward or backward to a specific migration number |
--fake |
Record the migration as applied without executing its SQL |
Apply all pending¶
Applying migrations:
0001_initial ... OK (12ms)
0002_add_user_email ... OK (8ms)
0003_add_articles ... OK (15ms)
All migrations applied.
Rollback¶
Target a specific version¶
Rolling back:
0005_add_tags ... OK (4ms)
0004_add_categories ... OK (5ms)
Migrated to 0003_add_articles.
Fake migration¶
Useful when you've manually applied schema changes and need to update the migration tracking table:
Faking migrations:
0004_add_categories ... FAKED
0005_add_tags ... FAKED
All migrations marked as applied (no SQL executed).
hyper createsuperuser¶
Create an admin user interactively. Prompts for username, email, and password.
Interactive prompts¶
Username: admin
Email: admin@example.com
Password: ********
Password (confirm): ********
Superuser 'admin' created successfully.
Validation¶
- Username: Required, must be unique
- Email: Required, must be a valid email format, must be unique
- Password: Required, minimum 8 characters, checked against password validators (no common passwords, not too similar to username)
- Confirm password: Must match
If validation fails, the prompt repeats with an error message:
Username: admin
Email: admin@example.com
Password: ********
Password (confirm): ********
Error: This password is too common. Please choose a different password.
Password: ********
Password (confirm): ********
Superuser 'admin' created successfully.
The password is hashed with argon2id before storage. The user is created with is_superuser=True and is_staff=True.
hyper collectstatic¶
Collect static files from all registered static directories into a single STATIC_ROOT directory for production serving.
uv run hyper collectstatic # collect to STATIC_ROOT
uv run hyper collectstatic --dry-run # preview without copying
uv run hyper collectstatic --clear # clear destination first
Options¶
| Flag | Description |
|---|---|
--dry-run |
Show which files would be collected without copying |
--clear |
Delete everything in STATIC_ROOT before collecting |
Content-hash manifesting¶
Collected files are renamed with a content hash for cache busting:
static/
├── style.css → staticfiles/style.abc123def.css
├── app.js → staticfiles/app.789xyz456.js
└── images/logo.png → staticfiles/images/logo.fa3b2c1d.png
A staticfiles.json manifest is generated that maps original names to hashed names. The get_static_url() function uses this manifest in templates:
<link rel="stylesheet" href="{{ get_static_url('style.css') }}" />
<!-- Renders: <link rel="stylesheet" href="/static/style.abc123def.css"> -->
CSS url() rewriting¶
CSS files have their url() references rewritten to point to the hashed filenames:
/* Before */
background: url(../images/logo.png);
/* After */
background: url(../images/logo.fa3b2c1d.png);
Dry run output¶
Would collect:
static/style.css → staticfiles/style.abc123def.css
static/app.js → staticfiles/app.789xyz456.js
static/images/logo.png → staticfiles/images/logo.fa3b2c1d.png
3 files would be collected (dry-run mode, no files copied).
hyper shell¶
Interactive Python shell with the app context pre-loaded. Uses the standard Python REPL.
Pre-loaded namespace¶
When the shell starts, these are already imported and available:
| Name | Description |
|---|---|
app |
The HyperApp instance |
db |
Database connection object |
| All models | Every registered Model class (e.g., User, Article, Order) |
asyncio |
The asyncio module |
asyncio.run |
For running async operations |
Example session¶
>>> User
<class 'models.User'>
>>> asyncio.run(User.objects.count())
42
>>> asyncio.run(User.objects.filter(active=True).all())
[User(id=1, name='Alice'), User(id=2, name='Bob'), ...]
>>> user = asyncio.run(User.objects.get(id=1))
>>> user.name
'Alice'
>>> user.name = "Alice Smith"
>>> asyncio.run(user.save())
>>> asyncio.run(db.query("SELECT version()"))
[('PostgreSQL 16.13 ...',)]
Running async code¶
Since the shell is a standard synchronous REPL, use asyncio.run() to execute async operations:
>>> import asyncio
>>> asyncio.run(Article.objects.filter(title__contains="Python").all())
[Article(id=5, title='Python Tips'), Article(id=12, title='Advanced Python')]
hyper dbshell¶
Open an interactive PostgreSQL shell (psql) connected to the project's database.
The command reads database connection settings from the app configuration and launches psql with the correct host, port, database, and user.
Passing psql arguments¶
Any additional arguments are forwarded directly to psql:
uv run hyper dbshell -- -c "SELECT COUNT(*) FROM users"
uv run hyper dbshell -- -f schema.sql
uv run hyper dbshell -- --tuples-only -c "SELECT name FROM users"
Example session¶
psql (16.13)
Type "help" for help.
myproject=> \dt
List of relations
Schema | Name | Type | Owner
--------+----------------+-------+-------
public | articles | table | postgres
public | users | table | postgres
public | migrations | table | postgres
(3 rows)
myproject=> SELECT id, name, email FROM users LIMIT 5;
id | name | email
----+--------+-----------------
1 | Alice | alice@example.com
2 | Bob | bob@example.com
(2 rows)
hyper inspectdb¶
Generate HyperDjango model code from existing database tables. Useful for integrating with legacy databases or bootstrapping models from an existing schema.
Options¶
| Flag | Description |
|---|---|
--table NAME |
Generate model for a specific table only |
Output¶
The command prints Python model code to stdout. Redirect to a file to save:
Example output¶
class User(Model):
class Meta:
table = "users"
id: int = Field(primary_key=True, auto=True)
name: str = Field()
email: str = Field(unique=True)
is_active: bool = Field(default=True)
created_at: datetime | None = Field(default=None)
PostgreSQL type mapping¶
The command maps PostgreSQL column types to Python types:
| PostgreSQL Type | Python Type | Field Options |
|---|---|---|
integer, serial |
int |
auto=True for serial |
bigint, bigserial |
int |
auto=True for bigserial |
smallint |
int |
|
text |
str |
|
varchar(N) |
str |
max_length=N |
char(N) |
str |
max_length=N |
boolean |
bool |
|
real, double precision |
float |
|
numeric(P,S) |
Decimal |
max_digits=P, decimal_places=S |
timestamp, timestamptz |
datetime |
|
date |
date |
|
time, timetz |
time |
|
uuid |
UUID |
|
jsonb, json |
dict[str, object] |
|
bytea |
bytes |
|
inet, cidr |
str |
Introspection capabilities¶
The inspectdb command detects:
- Primary keys: Columns in the primary key constraint get
primary_key=True - Auto-increment: Serial/bigserial columns get
auto=True - Foreign keys: Detected and annotated as comments (e.g.,
# FK -> users.id) - Unique constraints: Single-column unique constraints get
unique=True - Nullable: Nullable columns use
Type | Noneanddefault=None - Default values: Server defaults are mapped to Python values where possible
- Table naming: Table name
user_profilesgenerates class nameUserProfile
Foreign key detection¶
class Order(Model):
class Meta:
table = "orders"
id: int = Field(primary_key=True, auto=True)
user_id: int = Field() # FK -> users.id
product_id: int = Field() # FK -> products.id
quantity: int = Field()
total: int = Field()
created_at: datetime | None = Field(default=None)
hyper benchmark¶
Run EXPLAIN ANALYZE performance benchmarks against a seeded database. Detects query plan regressions (timing, new sequential scans) by comparing against a saved baseline.
# Run benchmarks
hyper benchmark
# Save current results as baseline
hyper benchmark --save-baseline
# JSON output for CI pipelines
hyper benchmark --json
# Custom database and threshold
hyper benchmark --database postgres://localhost/mydb --threshold 1.5
# Smaller dataset for faster runs
hyper benchmark --posts 10000
Options¶
| Option | Default | Description |
|---|---|---|
--database |
$DATABASE_URL |
PostgreSQL connection URL |
--save-baseline |
false | Save results to .hyper.benchmark.json |
--json |
false | Output as JSON |
--threshold |
2.0 | Regression detection multiplier |
--posts |
50000 | Number of posts to seed |
What It Tests¶
14 benchmark queries covering critical access patterns:
- Front page sorts: hot, new, top, controversial, rising (all must use indexes)
- Single record: post by PK, vote check (index lookup)
- Pagination: keyset pagination with composite ordering
- Aggregation: author score summaries, post counts
- Search: ILIKE title search
- Joins: post + author join with ordering
Regression Detection¶
When a .hyper.benchmark.json baseline exists, the tool compares:
- Timing regression: query > baseline * threshold → FAIL
- New sequential scan: index scan → seq scan → FAIL
- Improvements: query < baseline * 0.5 → reported
CI Integration¶
# GitHub Actions
- name: Performance regression check
run: |
hyper benchmark --save-baseline # first run
# ... make changes ...
hyper benchmark --threshold 2.0 # detect regressions
Exit code 0 = pass, 1 = regression detected.
Custom Management Commands¶
You can add custom commands to the hyper CLI by registering them with the app:
from hyperdjango import HyperApp
app = HyperApp()
@app.command("seed")
async def seed_command(args):
"""Seed the database with test data."""
from models import User, Article
for i in range(10):
user = User(name=f"User {i}", email=f"user{i}@example.com")
await user.save()
for i in range(50):
article = Article(
title=f"Article {i}",
body=f"Content for article {i}",
)
await article.save()
print(f"Seeded 10 users and 50 articles.")
Run the custom command:
Command with arguments¶
@app.command("import-csv")
async def import_csv_command(args):
"""Import data from a CSV file."""
if len(args) < 1:
print("Usage: hyper import-csv <filename>")
return
filename = args[0]
count = 0
with open(filename) as f:
reader = csv.DictReader(f)
for row in reader:
user = User(name=row["name"], email=row["email"])
await user.save()
count += 1
print(f"Imported {count} users from {filename}.")
Command with flags¶
@app.command("cleanup")
async def cleanup_command(args):
"""Remove old data."""
dry_run = "--dry-run" in args
days = 90
for arg in args:
if arg.startswith("--days="):
days = int(arg.split("=")[1])
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
old_sessions = await Session.objects.filter(expires_at__lt=cutoff).all()
if dry_run:
print(f"Would delete {len(old_sessions)} expired sessions.")
else:
for session in old_sessions:
await session.hard_delete()
print(f"Deleted {len(old_sessions)} expired sessions.")