Metadata-Version: 2.4
Name: py_app_runner
Version: 0.5.50
Summary: Async service framework: Tornado HTTP/WS bridge, service loader, Redis WS connection manager
Author-email: Gints Murans <gm@gm.lv>
License-Expression: MIT
Project-URL: Homepage, https://github.com/4Apps/py_app_runner
Project-URL: Documentation, https://github.com/4Apps/py_app_runner/blob/master/AGENTS.md
Project-URL: Code, https://github.com/4Apps/py_app_runner
Project-URL: Issue Tracker, https://github.com/4Apps/py_app_runner/issues
Project-URL: Download, https://pypi.org/project/py-app-runner/
Keywords: async,tornado,websocket,framework,service,redis,postgresql,migrations,queue
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: sentry-sdk
Requires-Dist: python-dotenv
Requires-Dist: uvloop
Requires-Dist: msgspec
Requires-Dist: msgpack
Requires-Dist: tornado
Requires-Dist: database_wrapper[pgsql,redis]==0.3.146
Requires-Dist: psycopg>=3.2.0
Requires-Dist: psycopg[pool]>=3.2.0
Requires-Dist: redis
Requires-Dist: pyjwt
Requires-Dist: cryptography>=44
Requires-Dist: bcrypt
Provides-Extra: dev
Requires-Dist: pyrefly; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Dynamic: license-file

# py_app_runner

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

## Install

```bash
pip install py_app_runner
```

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

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

## Development

```bash
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.

```bash
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"]`:

```python
"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.
