From-scratch async ASGI 3.0 framework with native HTTP/1.1, HTTP/2 and WebSocket implementations.
Project description
BlackBull
⚠ Early Alpha — API may break between MINOR versions. Conformance evidence:
docs/about/conformance.md. Things to know before adopting:KNOWN_LIMITATIONS.md.
From-scratch async ASGI 3.0 framework with native HTTP/1.1, HTTP/2,
and WebSocket implementations — no httptools, no uvicorn, no
hypercorn underneath. Pure-Python protocol stack, single
deployable, zero C-extension footprint outside the standard library.
Why BlackBull
- One package, one process — the framework is the server. No
separate ASGI runner;
app.run()opens the socket and serves. - HTTP/1.1 + HTTP/2 + WebSocket all implemented natively (RFC 9112 for H/1, RFC 9113 for H/2, RFC 6455 for WebSocket).
- Pure-Python identity — no
httptools, nouvloopdependency (uvloop available as an optional[speed]extra). - Conformance-tested against
h2spec, Autobahn, and a differential nginx fuzz corpus. - Modern Python — requires 3.11+, full type hints, PEP 561 typed distribution.
Install
pip install blackbull
pip install 'blackbull[compression]' # add brotli + zstandard codecs
pip install 'blackbull[speed]' # add uvloop event loop
pip install 'blackbull[reload]' # add watchfiles for --reload
Hello, world
from blackbull import BlackBull
app = BlackBull()
@app.route(path='/')
async def hello():
return "Hello, world!"
if __name__ == '__main__':
app.run(port=8000)
Run it:
python app.py # HTTP/1.1 on :8000
Or via the bundled CLI:
blackbull app:app --bind 0.0.0.0:8000
Simplified handlers
Route handlers may return a str, bytes, dict, or Response;
path parameters are coerced to the annotation type:
@app.route(path='/tasks/{task_id:int}')
async def get_task(task_id: int):
return {"id": task_id, "title": "..."}
Drop down to full ASGI (scope, receive, send) whenever you need it
— routes accept either shape.
TLS + HTTP/2
app.run(port=8443, certfile='cert.pem', keyfile='key.pem')
ALPN negotiates h2 automatically; HTTP/1.1 clients fall back via
the same socket.
WebSocket
from http import HTTPMethod
from blackbull.utils import Scheme
@app.route(path='/ws', methods=[HTTPMethod.GET], scheme=Scheme.websocket)
async def ws_echo(scope, receive, send):
await receive() # websocket.connect
await send({'type': 'websocket.accept'})
while True:
msg = await receive()
if msg['type'] == 'websocket.disconnect':
break
if msg['type'] == 'websocket.receive':
await send({'type': 'websocket.send',
'text': msg.get('text') or ''})
Built-in middleware
Compose via app.use(...) or per-route middlewares=[...]:
| Middleware | What it does |
|---|---|
Compression |
Negotiates br / zstd / gzip from Accept-Encoding |
StaticFiles |
Serves files from a directory under a URL prefix |
Cache |
Per-worker LRU + ETag / Cache-Control honouring |
Session |
Signed-cookie sessions (HMAC-SHA256) |
CORS |
Preflight + actual-request header injection |
TrustedProxy |
Rewrites scope['client'] / scope['scheme'] from proxy headers |
OpenAPI / Swagger UI
app.enable_openapi() # publishes /openapi.json and /docs
Auto-generates an OpenAPI 3.1 spec from route signatures, path-param
converters, docstrings, and @dataclass annotations on body
parameters. Dataclass-typed bodies are also deserialized at runtime
— async def h(body: CreateTask): ... receives a constructed
instance, no manual json.loads.
Examples
| Example | Demonstrates |
|---|---|
examples/SimpleTaskManager/ |
REST API + HTML UI, middleware pipeline, route groups, SQLite, Bearer token auth |
examples/ChatServer/ |
WebSocket, SSE, long polling side by side; Session + Compression + custom auth |
examples/typed_routes_ok.py |
{param:converter} syntax, url_path_for |
Documentation
- Guide:
docs/guide.md - Architecture:
docs/ActorDesign.md - Changelog:
CHANGELOG.md
Versioning
BlackBull uses ZeroVer prior to a 1.0 commitment.
MINOR advances at each sprint close; PATCH is for bug fixes and
harness work between sprints. See CHANGELOG.md for
the full release history.
License
Apache License 2.0 — © TOKUJI.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file blackbull-0.28.1.tar.gz.
File metadata
- Download URL: blackbull-0.28.1.tar.gz
- Upload date:
- Size: 184.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9e9e48e2a548f83d8364633809948443d61db777f7567572aaeded7dafb73c9
|
|
| MD5 |
9ddd0a0cd32b9c4f9daa5d6fbe0e223e
|
|
| BLAKE2b-256 |
f3101768c449e0b7708cf48fa66498fa951848b649511eb7d0da04535347c8e7
|
File details
Details for the file blackbull-0.28.1-py3-none-any.whl.
File metadata
- Download URL: blackbull-0.28.1-py3-none-any.whl
- Upload date:
- Size: 210.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7fb4b442eead2c0f5843f4a958ac3fc23cf082cfda43f1c181c00ab9fa8c926b
|
|
| MD5 |
3a5703f56726bf3eb186b0b54b82bbdc
|
|
| BLAKE2b-256 |
19b27601662b400af329898f60c97bd0f04f729dc32a2a0a55dba43bcaefd90b
|