# Philosophy URL: /docs/about/philosophy/ Section: about Tags: philosophy, design, principles -------------------------------------------------------------------------------- Design Principles These are distilled from building bengal, kida, patitas, and rosettes -- not as rigid rules, but as consistent instincts that shape every decision. 1. The obvious thing should be the easy thing app = App() @app.route("/") def index(): return "Hello, World!" app.run() You never make someone understand the system to use the system. The simple call works. The architecture reveals itself only when you need it. Five lines to hello world. Return a string, get a response. Return a Template, get rendered HTML. Return a Fragment, get a block. The type is the intent. 2. Data should be honest about what it is If something doesn't change after creation, it shouldn't pretend it might. If something is built incrementally, it should be honest about that too. Request is frozen. Received data doesn't change. Response is built through .with_*() chains. It is constructed incrementally, then sent. AppConfig is frozen. Configuration doesn't change at runtime. Route is frozen. The route table doesn't mutate after compile. Don't force immutability where the shape of the problem is mutable -- match the tool to the truth. g is mutable because per-request state is mutable. 3. Extension should be structural, not ceremonial Never make someone inherit from a base class just to participate. If a thing quacks like a middleware, it is a middleware. # A function is middleware async def timing(request: Request, next: Next) -> Response: start = time.monotonic() response = await next(request) return response.with_header("X-Time", f"{time.monotonic() - start:.3f}") # A class is middleware class RateLimiter: async def __call__(self, request: Request, next: Next) -> Response: ... The system discovers capability from shape, not from lineage. The Middleware protocol accepts either. 4. The system should be transparent No proxies hiding type: ignore. No magic globals. No "it works but don't look at how." If someone reads the code, the flow is traceable from entry to exit: Request enters through the ASGI handler Middleware pipeline executes in registration order Router matches the path via trie lookup Handler is called with injected arguments Return value is negotiated into a response Response is sent back through the middleware stack No hidden context, no implicit behavior, no action-at-a-distance. 5. Own what matters, delegate what doesn't Own the interface, own the developer experience, own the hot path. Delegate the commodity infrastructure. Own: Template integration (kida, same author), routing, middleware protocol, return-value negotiation, fragment rendering Delegate: Async runtime (anyio), form parsing (python-multipart), session signing (itsdangerous), password hashing (argon2) Write the template engine because templates are the thing. Use anyio for the async runtime because writing your own is insane. Non-Goals Chirp deliberately does not: Include an ORM. Database access is your choice. Chirp serves HTML. Include an admin panel. Build it yourself with Chirp's own tools. Generate OpenAPI specs. Chirp is an HTML-over-the-wire framework, not a JSON API framework. Support WSGI. Chirp is ASGI-only. Synchronous Python is not the future. Compete with Django. If you need auth, admin, ORM, email, and background jobs by next Tuesday, use Django. Chirp is for people who want to own their stack. Abstract away the web platform. Chirp embraces HTML, CSS, and the browser's native APIs. Next Steps Architecture -- How these principles manifest in code Comparison -- How Chirp compares to other frameworks Thread Safety -- Free-threading patterns -------------------------------------------------------------------------------- Metadata: - Word Count: 544 - Reading Time: 3 minutes