Serving: The Extensible Python Web Framework 🚀
[!WARNING]
Serving is currently in alpha and is NOT recommended for production use. APIs are subject to change.
Serving is a small ASGI web framework built on Starlette with first‑class dependency injection (Bevy), YAML configuration, typed routing, forms with CSRF, and themed error pages.
✨ Highlights
- ASGI/Starlette core with a minimal surface area
- Dependency Injection via Bevy (request‑scoped container)
- YAML configuration with typed
ConfigModels (including collections)
- Lightweight routing decorator; return‑type‑based responses
- Forms + CSRF with Jinja2 templates
- Themed error pages with dev-mode details
- Simple CLI wrapper around Uvicorn
- Pluggable sessions with DI-friendly access
- Configurable event system for app/request triggers
- Static asset mount in dev compatible with
url_for('static', ...)
🚀 Quick Start
Install
pip install getserving[server]
Minimal App
- Router module
# myapp/web.py
from serving.router import Router
from serving.types import PlainText, JSON, Jinja2
from serving.injectors import QueryParam
from serving import redirect
app = Router()
@app.route("/")
async def index() -> Jinja2:
return "home.html", {"message": "Hello from Serving"}
@app.route("/hello")
async def hello(name: QueryParam[str] = "world") -> PlainText:
return f"Hello, {name}!"
@app.route("/redirect")
async def go_home() -> PlainText:
redirect("/")
return "This will not be sent"
- Template
<!-- templates/home.html -->
<h1>{{ message }}</h1>
- Configuration
# serving.dev.yaml
environment: dev
auth:
credential_provider: myapp.auth:MyProvider
config:
csrf_secret: change-me-long-random-string
templates:
directory: templates
static:
mount: /static
directory: static
# In dev, assets are served by default; in other envs default is false.
# Explicitly enable serving in non-dev if desired:
# serve: true
routers:
- entrypoint: myapp.web:app
routes:
- path: "/"
- path: "/hello"
- path: "/redirect"
- Run
serv -e dev --reload
Your app will be available at http://127.0.0.1:8000.
🗝️ Sessions
Serving supports pluggable session providers and a dict‑like Session mapping bound to each request.
Configure a provider in YAML:
# serving.dev.yaml (add alongside `auth`/`routers`)
session:
session_provider: serving.session:InMemorySessionProvider
session_type: serving.session:Session # optional
config: {}
Use the session in routes:
from serving.session import Session
from serving.injectors import SessionParam
@app.route("/whoami")
async def whoami(sess: Session) -> JSON:
return {"user": sess.get("user_id")}
@app.route("/feature")
async def feature(beta: SessionParam[bool] = False) -> JSON:
# Uses parameter name as key; default applies if key is missing
return {"beta": beta}
See the full guide: Sessions
🧭 Return Types
PlainText → PlainTextResponse
JSON → JSONResponse
HTML → HTMLResponse
Jinja2 → TemplateResponse (tuple of template_name, context_dict)
- Returning a Starlette
Response is passed through as‑is
🔐 Authentication & Permissions
Configure an auth provider in YAML (auth.credential_provider). Serving calls has_credentials(permissions) before invoking a route; if denied, a themed 401 page is rendered. See Authentication for the CredentialProvider protocol and examples.
🧾 Forms & CSRF
Use serving.forms.Form with Jinja2. When CSRF is enabled (default), templates must call {{ csrf() }}; invalid tokens are rejected by CSRFMiddleware. See Forms & CSRF.
🎨 Error Pages & Theming
Customize error templates via the theming section in YAML. Dev mode can include extra details (stack traces, missing path). See Error Handling & Theming.
🧰 CLI
serv [-d DIR] [-e ENV] [uvicorn options...]
-d, --working-directory DIR — where your serving.{env}.yaml lives
-e, --env ENV — choose environment (e.g., dev, prod)
- All other flags are passed to Uvicorn (e.g.,
--reload, --host, --port)
📚 Documentation
See the docs/ directory for detailed guides and references:
Also see the demo/blog demo for a runnable example.
🤝 Contributing
Contributions are welcome! Bug reports, feature suggestions, docs, and tests are appreciated. Please open an issue or pull request.
📄 License
MIT — see LICENSE.