Minimal ASGI app scaffold
Project description
yaaf
YAAF stands for "Yet Another ASGI Framework".
A minimal Python ASGI app scaffold that discovers routes from the filesystem. It includes a tiny router and a CLI wrapper around uvicorn.
Design Goals and Opinions
- Filesystem-first routing. Routes are inferred from the
api/directory structure rather than declared with decorators. This keeps routing discoverable by looking at the tree. - Explicit endpoint files. Each route has
_server.pyand_service.pyto separate request handling from domain logic. - Dependency injection without wiring. Services are registered automatically and injected by name/type, so handlers and services focus on behavior, not setup.
- Static-first routing precedence. Static routes always win over dynamic segments, with warnings when a dynamic route would overlap a static route.
- Minimal core. The framework is intentionally small and opinionated, leaving room for you to add auth, middleware, validation, etc.
Quickstart
python -m venv .venv
source .venv/bin/activate
pip install -e .
# Run the built-in example routes
yaaf --reload
Example routes:
GET /helloGET /<name>(dynamic segment)
Routing Model
Routes are inferred from the api/ directory structure.
- Every route directory must contain
_server.pyand_service.py. - The route path is
/...plus the sub-path underapi/. - Dynamic segments use
[param]directory names and are exposed asparams/path_params. - Catch-all segments use
[...filepath]for multi-segment paths.
Example layout:
api/
users/
[id]/
_server.py
_service.py
_server.py
_service.py
hello/
_server.py
_service.py
name_dynamic/
_server.py
_service.py
Services (_service.py)
Use the @service decorator to mark and register services:
from yaaf import service
@service("UsersService")
class UsersService:
def get_user(self, user_id: str) -> dict:
return {"id": user_id, "name": "User"}
service = UsersService
Decorator Options:
name: Custom service name for DI resolution (defaults to class name)aliases: Additional names to resolve by
Handlers (_server.py)
Export lowercase HTTP method functions. Import services directly from their source modules:
from yaaf import Request
from yaaf.types import Params
from api.users._service import UsersService
async def get(request: Request, params: Params, service: UsersService) -> dict:
user_id = params.get("id", "1")
return service.get_user(user_id)
Injectable Parameters:
requestgives you theyaaf.Requestobjectparamsorpath_paramsprovides dynamic route parameters- Services are injected by type annotations
Service Dependencies
Services can depend on other services via constructor injection:
# users/_service.py
from yaaf import service
@service("UsersService")
class UsersService:
def get_user(self, user_id: str) -> dict:
return {"id": user_id, "name": "User"}
service = UsersService
# hello/_service.py
from yaaf import service
from api.users._service import UsersService
@service("HelloService")
class HelloService:
def __init__(self, users: UsersService) -> None:
self._users = users
def message(self) -> str:
user = self._users.get_user("1")
return f"Hello, {user['name']}"
service = HelloService
Static File Serving
Use yaaf_static to serve files from a directory:
# api/static/[...filepath]/_server.py
from yaaf.types import Params
from yaaf_static import static_files
async def get(path_params: Params, static=static_files("public")):
return static(path_params)
The static_files() function returns a handler that:
- Serves files relative to the specified directory
- Returns 404 if file not found
- Blocks path traversal attacks (
../)
Running Another App
yaaf --app your_package.app:app
Versioning
This project uses calendar-based versions with a timestamp (UTC). To bump the version:
python scripts/bump_version.py
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 yaafcli-2026.3.20.33459.tar.gz.
File metadata
- Download URL: yaafcli-2026.3.20.33459.tar.gz
- Upload date:
- Size: 18.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abd292c0b88c7cee7e239f8f09731a11fdd5806187bf43b638a223b944c3b6a9
|
|
| MD5 |
758a8b2151c5c2adb7266639e1587023
|
|
| BLAKE2b-256 |
413dcff6d63b1c0928113b100255dc1fc37183fc0158c13276567081fb35d9f6
|
File details
Details for the file yaafcli-2026.3.20.33459-py3-none-any.whl.
File metadata
- Download URL: yaafcli-2026.3.20.33459-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ef8c161c8d391c1b068a09b20d01d73e1aa5f0093248405085f4c1289b98b0b
|
|
| MD5 |
5ce7412caa3c246e3a35cd8ff2c3f2f9
|
|
| BLAKE2b-256 |
9a338316e7a24a8b147d4973290a611cd1ef379d7224187a4d51428fd0d42e5e
|