Running BlackBull¶
BlackBull has three entry points, in increasing order of how
"production-shaped" they are. All three resolve to the same
underlying serve() function — pick whichever shape fits the
deployment surface.
1. app.run(...) — in-Python, single command¶
# Single-worker, plain HTTP/1.1 on :8000
app.run(port=8000)
# HTTPS + HTTP/2 via ALPN on :8443
app.run(port=8443, certfile='cert.pem', keyfile='key.pem')
# Multi-worker (pre-forks N workers; master holds the listening sockets)
app.run(port=8443, certfile='cert.pem', keyfile='key.pem', workers=4)
# Development: hot-reload when *.py files change
# Requires the [reload] extra: pip install 'blackbull[reload]'
app.run(port=8000, reload=True)
app.run() is synchronous — it internalises asyncio.run
so callers write app.run(port=8000) rather than
asyncio.run(app.run(...)). reload=True and workers > 1
both trigger the master-process path (long-lived supervisor
around the worker pool).
Full signature:
def run(port=0, certfile=None, keyfile=None,
workers=None, unix_path=None, inherited_fd=None,
max_connections=None, stream_queue_depth=None,
ws_queue_depth=None, reload=False, reload_paths=None):
...
2. blackbull CLI — useful for systemd, Docker, PaaS¶
# Same flags as app.run kwargs
blackbull myapp:app \
--bind 0.0.0.0:8443 \
--certfile cert.pem --keyfile key.pem \
--workers 4
# Auto-reload via the CLI
blackbull myapp:app --bind 127.0.0.1:8000 --reload
The CLI accepts any module:attribute import path; the
attribute may be a BlackBull instance or a plain ASGI
callable. See blackbull --help for the full flag list and
Configuration for how flags compose
with environment variables and TOML config files.
3. Any external ASGI 3.0 server¶
BlackBull instances are valid ASGI 3.0 callables, so they work under any ASGI server:
uvicorn myapp:app --port 8000
hypercorn myapp:app --bind 0.0.0.0:8000
granian --interface asgi myapp:app
You lose BlackBull's own HTTP/2 frame layer and WebSocket codec (the surrounding server's protocol stack handles framing instead), but the application code is portable as-is.
Embedded use under an existing event loop¶
To run BlackBull inside an existing event loop, or to pre-bind a
socket before forking a test subprocess, instantiate
ASGIServer directly:
import asyncio
from blackbull.server import ASGIServer
server = ASGIServer(app, certfile='cert.pem', keyfile='key.pem')
server.open_socket(8443)
await server.run()
open_socket(port=0) binds to a kernel-assigned port and
exposes it as server.port — the same pattern the
testing fixture
uses.
Picking the deployment shape¶
| Shape | When |
|---|---|
python myapp.py (calls app.run()) |
Development, one-shot scripts, tutorial-style apps |
blackbull myapp:app … |
systemd, Docker, PaaS — anywhere the deployment surface is a CLI |
| External ASGI server | When the surrounding deployment dictates uvicorn / granian / hypercorn for tooling reasons |
ASGIServer direct |
Embedded inside another asyncio program, or when fine-grained socket / TLS control is needed (e.g. mTLS — see TLS) |
Next¶
- Workers — pre-fork multiprocessing, uvloop, abuse defences, production checklist.
- TLS — HTTPS, HTTP/2 via ALPN, self-signed certs, mTLS.
- Behind nginx — terminating TLS upstream, trusted-proxy headers.
- Unix and fd inheritance —
AF_UNIX, systemd socket activation. - Hot reload —
--reloadfor development.