v0.5.0 Release Notes¶
Release Date: October 2025
Cello v0.5.0 is a major feature release that adds dependency injection, role-based access control (RBAC) through composable guards, built-in Prometheus metrics, automatic OpenAPI schema generation, background task scheduling, and Jinja2 template rendering. These features bring Cello to parity with full-stack frameworks while maintaining Rust-level performance.
Highlights¶
- Dependency Injection -- Constructor-based DI with singleton and transient lifetimes
- Guards (RBAC) -- Composable access control with Role, Permission, and custom guards
- Prometheus Metrics -- Built-in request counters, latency histograms, and gauge metrics
- OpenAPI Generation -- Automatic
/docsendpoint with Swagger UI - Background Tasks -- Fire-and-forget and scheduled tasks
- Jinja2 Templates -- Server-side HTML rendering with template inheritance
New Features¶
Dependency Injection¶
Inject services into handlers using Depends, inspired by FastAPI but resolved at the Rust layer for lower overhead.
from cello import App, Depends
app = App()
class Database:
def __init__(self):
self.connection = connect_to_db()
def get_user(self, user_id: str):
return self.connection.query("SELECT * FROM users WHERE id = ?", user_id)
def get_db():
return Database()
def get_current_user(request, db=Depends(get_db)):
token = request.get_header("Authorization")
return db.get_user_by_token(token)
@app.get("/profile")
def profile(request, user=Depends(get_current_user)):
return {"name": user.name, "email": user.email}
Dependency features:
| Feature | Description |
|---|---|
Depends(factory) | Resolve a dependency per request |
register_singleton(key, instance) | Register a shared instance |
| Nested dependencies | Dependencies can depend on other dependencies |
| Caching | Each dependency is resolved once per request |
Guards (RBAC)¶
Composable access control guards that run before the handler. Guards inspect the request context and either allow or deny access.
from cello import App
from cello.guards import RoleGuard, PermissionGuard, AuthenticatedGuard
app = App()
# Require authentication
@app.get("/dashboard", guards=[AuthenticatedGuard()])
def dashboard(request):
return {"welcome": request.context.get("user_id")}
# Require admin role
@app.get("/admin", guards=[RoleGuard(["admin"])])
def admin_panel(request):
return {"admin": True}
# Require specific permission
@app.delete("/users/{id}", guards=[PermissionGuard(["users:delete"])])
def delete_user(request):
return {"deleted": request.params["id"]}
Composable logic with And, Or, Not:
from cello.guards import AndGuard, OrGuard, NotGuard
# Admin OR has special permission
flexible = OrGuard([
RoleGuard(["admin"]),
PermissionGuard(["elevated:access"]),
])
# Authenticated AND not banned
safe = AndGuard([
AuthenticatedGuard(),
NotGuard(RoleGuard(["banned"])),
])
@app.get("/resource", guards=[flexible])
def resource(request):
return {"access": "granted"}
Custom guards:
from cello.guards import Guard
class IPWhitelistGuard(Guard):
def __init__(self, allowed_ips):
self.allowed_ips = allowed_ips
def check(self, request, context) -> bool:
return request.client_ip in self.allowed_ips
Prometheus Metrics¶
Built-in metrics middleware exposes a /metrics endpoint in Prometheus exposition format.
Exposed metrics:
| Metric | Type | Description |
|---|---|---|
cello_http_requests_total | Counter | Total requests by method, status, path |
cello_http_request_duration_seconds | Histogram | Request latency distribution |
cello_http_requests_in_flight | Gauge | Currently active requests |
cello_http_response_size_bytes | Histogram | Response body size distribution |
The metrics are collected in Rust and serialized on demand, adding negligible overhead to request processing.
OpenAPI Generation¶
Cello generates an OpenAPI 3.0 specification from your route definitions and serves interactive documentation.
from cello import App
app = App()
app.enable_openapi(
title="My API",
version="1.0.1",
description="A Cello-powered REST API",
docs_url="/docs",
redoc_url="/redoc",
openapi_url="/openapi.json",
)
Routes are automatically documented based on:
- HTTP method and path
- Path parameters and their types
- Response examples from handler return types
- DTO validation schemas (when used with v0.6.0+)
Background Tasks¶
Run tasks after the response is sent, or on a schedule.
from cello import App
app = App()
# Fire-and-forget after response
@app.post("/orders")
def create_order(request):
order = process(request.json())
request.add_background_task(send_confirmation_email, order.email)
return {"order_id": order.id}
async def send_confirmation_email(email):
await email_service.send(email, "Order confirmed")
Background tasks run on the Tokio runtime and do not block the response. Failures are logged but do not affect the client.
Jinja2 Templates¶
Render HTML using Jinja2 templates with full inheritance support.
from cello import App, TemplateEngine
app = App()
app.enable_templates(TemplateEngine(
directory="templates",
auto_reload=True, # Watch for changes in development
))
@app.get("/")
def home(request):
return app.render_template("home.html", {
"title": "Welcome",
"items": ["Cello", "is", "fast"],
})
Templates are loaded from the specified directory and cached in memory. In development mode, templates are reloaded automatically when files change.
Improvements¶
Performance¶
- 12% faster middleware chain through pre-computed execution plans
- Reduced PyO3 overhead for handler invocation
- Smarter buffer reuse in response serialization
Developer Experience¶
- Route listing via
python app.py --routesto display all registered endpoints - Startup banner showing host, port, workers, and enabled features
- Improved error tracebacks that include both Python and Rust stack frames
Breaking Changes¶
Middleware Priority¶
Middleware priority values have been renumbered. If you set custom priority() values on middleware, review the new defaults:
| Middleware | Old Priority | New Priority |
|---|---|---|
| Security Headers | 100 | 10 |
| CORS | 90 | 20 |
| Rate Limiting | 80 | 30 |
| Authentication | 70 | 40 |
| Logging | 50 | 50 |
| Compression | 30 | 90 |
Blueprint Registration¶
app.include_blueprint() has been renamed to app.register_blueprint() for consistency.
Bug Fixes¶
- Fixed JWT token refresh returning expired claims
- Fixed rate limiter not resetting window after full expiry
- Fixed WebSocket handler not receiving
closeevents - Fixed static files returning 404 for URL-encoded filenames
- Fixed multipart parser failing on boundaries with special characters
- Fixed session cookie
Pathattribute not being set correctly
Dependencies Added¶
| Dependency | Version | Purpose |
|---|---|---|
prometheus | 0.13 | Metrics collection and exposition |
minijinja | 1.0 | Template rendering engine (Rust-native Jinja2) |
Migration from v0.4.0¶
-
Update your dependency:
-
Rename blueprint registration (if used):
-
Review middleware priority if you use custom values.
-
New features are opt-in. Enable them as needed:
Full Changelog¶
See the complete changelog for all changes in this release.