Skip to content

Flask

Requires Flask 3.1.3+, Python 3.10+. See the compatibility matrix for the full pin string.

Z4J() and Z4J.init_app(app) read configuration from Flask’s app.config or from the environment; they do not take a config dict argument.

Factory pattern with deferred init:

from flask import Flask
from z4j_flask import Z4J
z4j = Z4J()
def create_app():
app = Flask(__name__)
app.config.from_object("myapp.config.Config")
# Required: Z4J_BRAIN_URL, Z4J_TOKEN, Z4J_HMAC_SECRET. Optional:
# Z4J_PROJECT_ID, Z4J_AGENT_NAME, Z4J_AGENT_ID.
z4j.init_app(app)
return app

Or the one-shot form, with config already on app.config or in env:

Z4J(app)

The extension accepts three equivalent shapes; pick one.

SourceExample
Flat Flask keysapp.config["Z4J_BRAIN_URL"] = "...", app.config["Z4J_TOKEN"] = "...", app.config["Z4J_HMAC_SECRET"] = "..."
Nested Flask dictapp.config["Z4J"] = {"brain_url": "...", "token": "...", "hmac_secret": "...", "project_id": "billing-prod"}
Environment variablesZ4J_BRAIN_URL, Z4J_TOKEN, Z4J_HMAC_SECRET, Z4J_PROJECT_ID, Z4J_AGENT_NAME

Z4J_HMAC_SECRET is required; the agent refuses to start without it. To skip the agent entirely (CI, local dev), set Z4J_DISABLED=1 (env) or app.config["Z4J_DISABLED"] = True.

Flask’s application context does not have a natural startup hook. z4j-flask starts the agent during init_app itself; the WebSocket connection lives in a background asyncio runtime that the extension manages. Request teardown hooks are registered automatically.

For long-running workers that don’t run under Flask’s request loop (RQ worker, Celery beat scheduler):

rq_worker.py
from myapp import create_app
app = create_app() # Z4J.init_app already ran inside; the agent is up.
# ... RQ worker loop ...

Each gunicorn / uwsgi worker process registers separately. z4j’s worker-first protocol identifies each one by (agent_id, worker_id), so a gunicorn -w 4 deployment shows up as four workers under one agent in the dashboard. Set Z4J_AGENT_NAME to keep multi-host deploys readable:

import os, socket
app.config["Z4J_AGENT_NAME"] = f"web-{socket.gethostname()}-{os.getpid()}"

z4j does not register any Flask blueprints. It attaches as pure background behavior; your URL space is untouched.

Management commands run under the same init_app path, so the agent attempts to start when they boot. Set Z4J_DISABLED=1 for one-off commands you do not want surfaced as agent connects.

python -m z4j_flask doctor runs the same probes the agent runtime does (buffer dir writable, brain DNS / TCP / TLS, WebSocket upgrade) using the Z4J_* env vars your service is configured with.

Terminal window
# Always run as the same user the service runs under.
sudo -u www-data /srv/app/venv/bin/python -m z4j_flask doctor
# Skip the WS round-trip when z4j is intentionally offline:
python -m z4j_flask doctor --no-websocket
# Machine-readable for scripting:
python -m z4j_flask doctor --json

Exit 0 on all-green, 1 on any failure. Catches the gunicorn-under-www-data silent startup failure, NAT / firewall / cert issues, and wrong-token / wrong-project problems with a specific failure reason. See service-user deployments.

First, run python -m z4j_flask doctor — it surfaces the most common failures with a specific reason.

  • Gunicorn preload mode — the agent’s asyncio loop is per-worker. Preload is fine; each worker opens its own WebSocket after fork.
  • PermissionError: ... /var/www/.z4j in service log — the agent auto-relocates the buffer to $TMPDIR/z4j-{uid}. See service-user deployments.