Running and deploying

Every app is started the same way:

if __name__ == "__main__":
    app.run()

Development

$ python app.py --dev
  • Hot reload — a small supervisor restarts the app when a *.py file changes (watchfiles if installed, otherwise polling). It works the same on Windows, macOS and Linux.

  • Live browser reload — open pages reload themselves after each restart.

  • Readable errors — a failing page shows the exception, the line of your code with context, a hint for common mistakes and the full traceback. Errors in event handlers appear as an in-page overlay.

  • Binds to 127.0.0.1. Never expose dev mode: it shows source code.

--dev is the default when no mode is given, unless RATIONAL_ENV=production.

Production

$ python app.py --prod
$ PORT=8080 python app.py --prod --workers 1

One origin serves everything:

/…

your pages

/api/…

@remote services, OpenAPI at /api/docs

/_rational/…

runtime script, theme CSS (immutable, cache forever), events

Production mode disables debug output, binds to $HOST (default 0.0.0.0) and $PORT (default 8000), trusts X-Forwarded-* from $FORWARDED_ALLOW_IPS (default 127.0.0.1) and hides the server header. Use --root-path /prefix when a proxy serves the app under a sub-path.

Important

Page views (component state) live in process memory. Run one worker per container, or use sticky sessions when you run several (--workers N prints a reminder).

Static export

A site made of static pages does not need a running server. Export it as plain HTML files and host them anywhere (GitHub Pages, Netlify, S3, a CDN):

$ python app.py --export site/
$ python app.py --export site/ --root-path /my-repo     # served under a sub-path

This writes site/index.html, site/<path>/index.html, site/_rational/theme.css, a 404.html and a copy of every directory mounted with app.static. Only pages that work without a server are exported: those with no path parameters, no auth="required" and no interactivity (any page that loads the browser runtime, such as one with event handlers). Everything else is listed as skipped with the reason, and the command exits non-zero if nothing could be exported. The same export is available from code as rational.export.export_site().

Request logging and tracing

Every HTTP response includes an X-Request-ID header. A valid ID supplied by the caller is preserved; otherwise Rational generates one. The rational standard-library logger emits structured LogRecord fields for request completion and event dispatch, including the request ID and elapsed milliseconds. Slow page renders are logged at WARNING; the default threshold is 100 ms.

app.logging.configure(
    request_id_header="X-Request-ID",
    slow_render_threshold_ms=75,
)

To emit OpenTelemetry spans for requests, page renders and event dispatch, install rational[telemetry], configure an OpenTelemetry SDK provider/exporter in the host application, then enable tracing:

app.logging.configure(opentelemetry=True)

The framework does not install or configure an exporter, so the host application retains control over where telemetry is sent.

Other deployment strategies

Any ASGI server

App is an ASGI application:

uvicorn app:app --host 0.0.0.0 --port 8000 --proxy-headers
gunicorn app:app -k uvicorn.workers.UvicornWorker -w 1
The module entry point

Run an app without a __main__ block, or with an import string:

python -m rational path/to/app.py --prod
python -m rational mypackage.web:app --prod --workers 2
Inside an existing FastAPI application
api = FastAPI()
api.mount("/app", rational_app)          # or add routes to rational_app.fastapi
Behind a reverse proxy / CDN

Terminate TLS at the proxy, forward X-Forwarded-Proto so cookies get Secure, and cache /_rational/*.js|css aggressively (their URLs are content-hashed).

Containers
FROM python:3.13-slim
WORKDIR /srv
COPY . .
RUN pip install --no-cache-dir "rational[db]" .
ENV RATIONAL_ENV=production
CMD ["python", "app.py", "--prod"]
Content Security Policy

App(csp=True) sends a strict policy with a per-response nonce. No inline style= attributes or inline scripts are generated.