Flask
Requires Flask 3.1.3+, Python 3.10+. See the compatibility matrix for the full pin string.
Install + wire
Section titled “Install + wire”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 Flaskfrom 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 appOr the one-shot form, with config already on app.config or in env:
Z4J(app)Configuration sources
Section titled “Configuration sources”The extension accepts three equivalent shapes; pick one.
| Source | Example |
|---|---|
| Flat Flask keys | app.config["Z4J_BRAIN_URL"] = "...", app.config["Z4J_TOKEN"] = "...", app.config["Z4J_HMAC_SECRET"] = "..." |
| Nested Flask dict | app.config["Z4J"] = {"brain_url": "...", "token": "...", "hmac_secret": "...", "project_id": "billing-prod"} |
| Environment variables | Z4J_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.
Lifecycle
Section titled “Lifecycle”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):
from myapp import create_appapp = create_app() # Z4J.init_app already ran inside; the agent is up.# ... RQ worker loop ...Multi-process
Section titled “Multi-process”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, socketapp.config["Z4J_AGENT_NAME"] = f"web-{socket.gethostname()}-{os.getpid()}"Blueprints
Section titled “Blueprints”z4j does not register any Flask blueprints. It attaches as pure background behavior; your URL space is untouched.
CLI (flask ...)
Section titled “CLI (flask ...)”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.
Verify with doctor
Section titled “Verify with doctor”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.
# 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 --jsonExit 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.
Troubleshooting
Section titled “Troubleshooting”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/.z4jin service log — the agent auto-relocates the buffer to$TMPDIR/z4j-{uid}. See service-user deployments.