The hare command¶
The hare command (installed as a console script) reads its config from -c/--config — either
module.VARIABLE (a dotted path to a config value, e.g. settings.HARE_ORM) or the path of a
.json/.yml/.yaml file — and without it from the HARE_ORM environment variable or
[tool.hare].hare_orm in pyproject.toml, which hold the same type of value. It is what
HareConfig.load() takes. hare -V/--version
prints hare's version.
| Command | Purpose |
|---|---|
hare init [app_labels...] |
Create a migrations package for the given (or all) apps. |
hare shell |
IPython shell (pip install hare-orm[ipython]) with top-level await, and Hare, hare (the HareContext), apps and every model already in scope — each model also as <app_label>_<ModelName>, which tells apart two apps' models of one name. |
hare makemigrations [app_labels...] [--empty] [--merge] [--check] [--dry-run] [-n/--name NAME] [--rename OLD=NEW]... [--no-input] |
Generates migrations from the changes to the models — see hare makemigrations. |
hare squashmigrations APP_LABEL [START] END [--squashed-name NAME] |
Squashes a run of an app's migrations into one that replaces them — see hare squashmigrations. |
hare migrate [app_label] [migration] [--fake] [--dry-run] [--lock-timeout SECONDS] |
Applies or unapplies migrations up to a given one — see hare migrate. |
hare history [app_labels...] |
List migrations already applied (from the database), grouped by connection/app. |
hare heads [app_labels...] |
List head migrations on disk (not from the database). |
hare inspectdb [tables...] [--connection ALIAS] [--schema SCHEMA] |
Writes the models of an existing schema — see Models from an existing database. |
hare dbshell [--connection ALIAS] |
Runs the database's own interactive client on a connection — see hare dbshell. |
hare checkmigrations [app_labels...] |
Checks the migrations migrate would apply for risks to a database in use — see hare checkmigrations. |
hare sqlmigrate APP_LABEL MIGRATION_NAME [--backward] |
Print a migration's SQL without running it; --backward prints the rollback. An atomic migration on a database that rolls back DDL (PostgreSQL, SQLite) is wrapped in BEGIN;/COMMIT;. Any migration on disk is printed, a squashed one and those it replaces alike, each on the state of its dependencies. |
hare drift [app_labels...] [--connection ALIAS] [--schema SCHEMA] [--journal-table NAME ...] |
Compares a connection's live database with the models — see hare drift. |
hare distributed-recover --coordinator ALIAS [--finish] [--older-than SECONDS] |
Reports or finishes Transactions.distributed() prepared transactions that never resolved — see hare distributed-recover. |
hare stubs [--output DIRECTORY] [--relation-depth N] [--check] |
Writes the stubs pyright and Pylance check the models with — see hare stubs. |
hare makemigrations¶
Generate migrations by diffing model state; an app label given twice counts once. --empty needs an app
label and creates a blank migration depending on the current heads. --merge needs an app label and
writes one migration joining the app's forked history (two heads); without it a forked history is an
error. --dry-run prints what would be written without writing; --check does the same and exits 1
when there is anything to write (neither combines with --empty). -n/--name renames every migration
generated in the run, dependencies between them included. A removed field or model declared exactly like
an added one is asked about in the terminal — Was field 'title' of shop.Item renamed to 'name'? [y/N];
--rename answers beforehand (repeatable: --rename shop.Item.title=name, --rename shop.Item=Product,
--rename blog.Tag=catalog.Tag for a model moved keeping its table — see
Renames); --no-input, or input not from a terminal, asks nothing — only the
--rename answers rename anything. An answer that renames nothing — a misspelt name, a model renamed while
its fields change — fails the command, and nothing is written: the field or model it meant would be removed
and added, its rows dropped. Without an explicit migrations module, an app
whose models live in a top-level models.py (no package) gets a top-level migrations package next to
it. Two apps that would end up with the same migrations package (two such single-file apps in one
directory, or the same explicit migrations value) are refused with an error by every migration command
— set a distinct migrations module for each (e.g. "migrations_billing").
hare squashmigrations¶
Squash an app's migrations from START (its first without it) to END — each a name or a unique prefix
— into one that replaces them: a database that applied none or all of them runs it in their place, one
that applied only some runs the rest of them first. See
Squashing migrations.
hare migrate¶
Apply migrations forward or backward, whichever the current database state calls for: a migration after
the applied ones applies up to it, one before them unapplies back to it, and zero unapplies every
migration of the app (hare migrate models zero). migration may be a unique prefix of the name (0002
for 0002_add_email), as for sqlmigrate; a name of no migration, or one several begin with, exits with
code 2 here, in sqlmigrate and in squashmigrations, as an unknown app_label does. Without
migration the app goes to its latest; without app_label every app does. --fake records history
without running SQL; --dry-run prints the plan only and writes nothing to the database, not even the
migrations table. --lock-timeout fails a migration whose statement waits longer for a lock another
session holds (see Lock timeout); a value outside the range a transaction's
lock_timeout takes exits with code 2.
hare dbshell¶
Run the database's own interactive client on a connection (default: the first one configured): psql for
PostgreSQL, sqlite3 for a SQLite file, clickhouse-client for ClickHouse. The password goes to the
client through its environment (PGPASSWORD, CLICKHOUSE_PASSWORD), never among the arguments; a
connection with a password_provider passes the provider's password. PostgreSQL's TLS settings, schema
and application_name go along too (PGSSLMODE, PGSSLROOTCERT, PGOPTIONS, PGAPPNAME). Exits with
the client's exit code. An in-memory SQLite database, a dialect that names no client, or a client not on
PATH is refused with an error.
hare checkmigrations¶
Check the migrations migrate would apply against the database: operations that lock or rewrite a large
table, or break the code still running during a deployment (renamed and dropped columns and tables, raw
SQL), each printed with how to make the change safely. Exits 1 while a risk isn't listed in its
migration's safety_exemptions. A table is large from the config's migrations.safety.large_table_rows
rows. makemigrations prints the same warnings for the migrations it writes, without reading the
database. See Checking migrations.
hare drift¶
Compare the live database of a connection (default: the first one configured) against current model state
of the given apps (default: every app) that use it — tables/columns present in the database but not in
any migration, and operations that would be needed to reconcile the rest. Exits 1 if anything is found.
Meta.managed = False models are skipped entirely, same as makemigrations. --schema picks the schema
swept for untracked tables — an attached database on SQLite, a database of the server on ClickHouse
(default: the connection's current schema). hare's own bookkeeping tables
(hare_migrations, Transactions.distributed()'s hare_distributed_decisions) are never reported as
untracked, nor is a journal named by --journal-table (a MigrationRecorder(table_name=...)'s), nor are
the tables of a PostgreSQL extension (PostGIS's spatial_ref_sys) and SpatiaLite's spatial metadata. See
Detecting drift for what is compared and how.
hare distributed-recover¶
Report (or, with --finish, actually finish) Transactions.distributed() prepared transactions that
never resolved. --coordinator (required) is the connection alias holding the
hare_distributed_decisions decision log. --finish issues COMMIT PREPARED/ROLLBACK PREPARED
instead of only reporting. --older-than ignores anything younger than that many seconds, since it may
still be mid-flight (default: 300); 0 ignores nothing. Exits 1 if any stale prepared transaction was
found (report mode) or left unresolved after --finish.
hare stubs¶
Write the stubs pyright and Pylance type-check the modules of models with, into typings/ by default.
--relation-depth (0 to 5, default 2) is how many relations a filter key crosses at most; --check
writes nothing and exits 1 when a stub is missing or outdated. See
pyright and Pylance.
hare -c settings.HARE_ORM makemigrations models -n add_book_isbn
hare -c settings.HARE_ORM migrate models 0003_add_index --dry-run
hare -c settings.HARE_ORM migrate models 0002_add_book_isbn # back to 0002: unapplies 0003
hare -c settings.HARE_ORM migrate models zero # unapplies every migration of the app
hare -c settings.HARE_ORM sqlmigrate models 0003_add_index
hare -c settings.HARE_ORM inspectdb --connection default --schema public books authors
Exit codes: 0 success, 2 usage error (CLIUsageError, printed to stderr), 1 any other failure
(CLIError), 130 stopped by Ctrl+C (hare: interrupted printed to stderr; the running command is
cancelled, a migration running in a transaction rolled back).
Adding your own commands¶
Installed packages and your project can add subcommands to hare without touching hare. A command is a
CLICommand subclass:
# library/cli.py
from hare.cli.plugins import CLICommand, CLIError, CommandContext
class ExportCommand(CLICommand):
name = "export" # `hare export`
help = "Export loans to CSV." # shown in `hare --help`
def add_arguments(self, parser):
parser.add_argument("--since")
async def run(self, cli_context, args):
config = CommandContext.load_config(cli_context) # the same -c/--config config
async with CommandContext.hare_cli_context(config):
... # models and connections are ready here
return 0 # exit code; None also means 0
cli_context is a CLIContext (hare.cli.plugins) holding the global option config — the
-c/--config value (module.VARIABLE or a file path), None when not given: then
CommandContext.load_config(cli_context) locates the config through the environment or pyproject.toml,
like every built-in command.
run() raising CLIUsageError exits with code 2, CLIError with code 1, the message printed to stderr
— the same as built-in commands. Any other hare exception (ConfigurationError, DBConnectionError,
...) also exits with code 1 and prints its type and message instead of a traceback; so does a built-in
command whose configuration can't be loaded (an unknown engine, a bad port, ...). An exception of another
type (a RuntimeError, a KeyError — a bug of the command) exits with code 1 and prints its full
traceback, to find the bug by. The module defining a command is imported on every hare run, so keep it
light and import heavy dependencies inside run().
Two ways to register a command:
- An installed package declares an entry point in the
hare.cligroup of its ownpyproject.toml; the command appears once the package is installed:
- The project itself lists commands in its config, next to
connectionsandapps(Hare.init()ignores this section — only the CLI reads it):
HARE_ORM = {
"connections": {...},
"apps": {...},
"cli": {"commands": ["library.cli:ExportCommand"]},
}
A command whose name is already taken — by a built-in command or an earlier plugin — is skipped with a warning naming both sources; built-in commands always win. A plugin that fails to import or to declare its arguments is skipped with a warning too, and every other command keeps working.