Skip to main content

py_app_runner

Async Python service framework: Tornado HTTP/WS bridge, PyBridge service loader, Redis WebSocket connection manager.

Install

pip install py_app_runner                # the bridge, migrations, queue, audit, throttle
pip install 'py_app_runner[crypto,cron]'  # field encryption and the scheduler need their extras

Every push to develop publishes a pre-release, X.Y.<commit count>.dev0. pip hides those unless you ask for them:

pip install --pre py_app_runner        # newest, including dev builds
pip install py_app_runner==0.4.50.dev0 # a specific dev build

Development

docker-compose up develop

Migrations

Built-in service that applies tracked SQL files to one or more configured databases. Add migrations to SERVICES to enable it.

python3 src/app.py migrations status   [--check] [--target NAME]
python3 src/app.py migrations apply    [--dry-run] [--to PREFIX] [--target NAME]
python3 src/app.py migrations baseline [--to PREFIX] [--yes] [--target NAME]
python3 src/app.py migrations new      <name> [--target NAME]
python3 src/app.py migrations repair   <filename> [--target NAME]

By default there is a single target, main, against config["db"]["main"]: files live in config["migrations"]["dir"] (default data/migrations) and are tracked in config["migrations"]["table"] (default migrations). No project needs to change anything to keep this working - an absent migrations key, an empty one, and this flat shape all resolve to that same single target.

To migrate more than one database, opt in with config["migrations"]["targets"]:

"migrations": {
    "targets": {
        "main": {"db": "main", "dir": "data/migrations", "table": "migrations"},
        "gis": {"db": "gis", "dir": "data/migrations_gis", "table": "gis_migrations"},
    }
}

Each target's db names a key under config["db"] and defaults to the target's own name. dir and table default as above. status and apply with no --target run every target in declared order (apply stops at the first one that fails; status --check reports each target rather than stopping at the first with pending work, and exits 1 if any is pending or blocked). new, repair and baseline require --target once more than one target is configured, and an unknown --target exits 1 - both name the configured targets. Output gets a [name] prefix only when more than one target is processed, so single-target output is unchanged.

Targets may share a database, but not a database and a tracking table - each would then report the other's migrations as missing, so that config is refused up front, naming both targets. table defaults to migrations for every target, so two targets on one database need an explicit table on at least one of them.

  • Migrations no longer need to be idempotent - each file runs in its own transaction with its tracking row written inside it, so what already ran is always known.
  • -- migrations:no-transaction on line 1 runs a file outside a transaction (for CREATE INDEX CONCURRENTLY and friends). Such a file must contain exactly one statement: Postgres wraps a multi-statement send in an implicit transaction, which would defeat the directive, so apply refuses it up front. A no-transaction file that fails cannot roll back and is not recorded - apply says so and tells you to inspect the database before re-running.
  • Editing an applied file is detected as drift, and a tracked file that has since been deleted shows as missing; both block apply until resolved. repair fixes drift only; a missing file is fixed by restoring it, or by deleting its tracking row by hand (apply prints the exact DELETE when it blocks).
  • Files must not contain psql meta-commands (\restrict / \unrestrict, as emitted by pg_dump) - psycopg cannot execute them, and apply refuses such a file up front.
  • baseline adopts an existing database into the system: it writes tracking rows without executing anything.
  • Every subcommand exits non-zero on failure, including a misconfigured targets block. status --check exits 1 if anything is pending, drifted or missing, so a deploy script can halt before restarting services against a half-migrated database.

Scheduled jobs

Built-in service that runs app.py subcommands on a cron schedule declared in config, the way Laravel's scheduler does: the system crontab calls cron run once a minute and it starts whatever is due. Needs the cron extra; add cron to SERVICES to enable it.

python3 src/app.py cron list                       # every job, its schedule and next run
python3 src/app.py cron run  [--job NAME] [--dry-run]
python3 src/app.py cron work                       # the same, looping, for a container without a crontab
"cron": {
    "timezone": "Europe/Riga",
    "jobs": {
        "lad-sync": {"schedule": "0 4 * * 0", "command": "parcel lad sync"},
        "cleanup":  {"schedule": "15 4 * * *", "command": "cron cleanup", "timeout": 1800},
    },
}
* * * * * app cd /srv/app && python3 src/app.py cron run 2>&1 | logger -t cron

Each job runs as its own app.py process, so a wedged or leaking job takes only itself down and timeout can kill it. A Postgres advisory lock per job stops a tick from starting a second copy of one still running. There is no run history and no catch-up: a job is due when its expression matches the current minute, and a missed minute is missed.

API keys

With api_key_use_db=True, keys live in the api_keys table and carry abilities (service:action, service:*, *), an optional IP allow-list, an optional expiry and an optional user the key acts as. A key with no abilities can call nothing. Add api_keys to SERVICES for the CLI:

python3 src/app.py api_keys install                 # writes the schema into data/migrations
python3 src/app.py api_keys create --name reports-bot --ability sudat_reports:report \
    --allowed-ip 10.0.0.0/8 --expires 2027-12-31 --user-id 5
python3 src/app.py api_keys list
python3 src/app.py api_keys update <id|prefix> --add-ability sudat_reports:export
python3 src/app.py api_keys revoke <id|prefix>

create prints the key once; only a peppered hash (config["api_key_pepper"]) is stored. Clients send it as X-API-Key. A call outside the key's abilities gets 403 with code 1403. Behind nginx, list the proxy in config["trusted_proxies"] (or TRUSTED_PROXIES), or the IP allow-list sees the proxy's address rather than the client's.

Inside a service, bridge_handler.current_api_key is the key's record and bridge_handler.key_can("service:thing") checks a finer ability.

Release files for py-app-runner 0.6.57

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for py-app-runner 0.6.57
File Size Uploaded
py_app_runner-0.6.57.tar.gz 178.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for py-app-runner 0.6.57
File Interpreter ABI Platform
py_app_runner-0.6.57-py3-none-any.whl Python 3 none any Details

Total release size: 332.2 kB

Release files / py_app_runner-0.6.57.tar.gz

Download URL py_app_runner-0.6.57.tar.gz
Size 178.1 kB
Tags Source
SHA-256 checksum
How to use checksums
fe2e69460b0af794ddc2cb985fc3c7dd9fd63ec2c4bb3273273b550c92367943
BLAKE2b-256 checksum
How to use checksums
99912997d19f276e4893344c3cd80ba845c55c7155a9acab1e9849ac75ddfb2a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / py_app_runner-0.6.57-py3-none-any.whl

Download URL py_app_runner-0.6.57-py3-none-any.whl
Size 154.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e037cbafd41aad9e655e31c3e2eccf703e99b0df58a692687a5d60440e861cc4
BLAKE2b-256 checksum
How to use checksums
1c89bdcd9989c4fa4f5d6d23372c65e64c427ca051c6fdea01b81bf7ee9d8a56
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page