Framework utility

Runtime

Between booting and shutting down, your app spends its life handling requests. WebFluid wraps each one in a context and gives you a handful of hooks to shape the request on its way in and the response on its way out. This is the layer that makes render, url_for and friends "just know" about the current request.

The request context

On every request the framework enters a FluidContext. From anywhere downstream — a route, a service, an event handler — you can grab it with FluidContext.current() to reach the active app and request without threading them through every function call. It's the same pattern our generated routes use:

fluid/app/index.py python
from webfluid.core.context import FluidContext


async def handle_request():
    ctx = FluidContext.current()
    user = ctx.request.session.get("user")
    return await ctx.fluid.render("index.html", user=user)

Outside of a request there is no context, and that is a case worth handling rather than hoping about — a scheduled job, a startup hook and an event handler all run without one. Two accessors let you choose how loudly to fail:

  • FluidContext.current() — the context, or a RuntimeError. Use it where a request is genuinely required.
  • FluidContext.try_current() — the context, or None. This is what the framework's own helpers use, so they can fall back to a default instead of raising in a background job.

The context is also a scratchpad for the current request. It behaves like a dict (ctx["key"], get, pop), and FluidContext.cached_or(key, factory) memoises a value for the rest of the request — calling the factory directly when there is no context at all. That is how the active locale is parsed once per request instead of once per translated string, and it works just as well for anything of yours that is expensive and stable within a request.

example python
from webfluid.core.context import FluidContext


def pricing_tier():
    # Computed at most once per request, and computed every time
    # when called from a job that has no request at all.
    return FluidContext.cached_or("pricing_tier", _resolve_tier)

Shaping requests and responses

Three decorators let you step into the request flow, all running inside that context:

  • before_request — runs before the route. Return nothing to continue, or return a response to short-circuit the request entirely.
  • after_request — receives the response and returns one (possibly a new one). Great for headers or post-processing.
  • context_processor — returns a dict that is merged into every render, so shared template variables don't have to be passed by hand.
fluid/runtime.py python
from webfluid.core.context import FluidContext


def register(app):

    @app.before_request
    def require_session():
        ctx = FluidContext.current()
        if ctx.request.url.path.startswith("/admin"):
            if not ctx.request.session.get("user"):
                from fastapi.responses import RedirectResponse
                return RedirectResponse("/login")

    @app.after_request
    async def add_header(response):
        response.headers["X-Powered-By"] = "WebFluid"
        return response

    @app.context_processor
    def globals_():
        return {"brand": "My Portal"}

This is exactly how the surface's processing layer works internally — it registers a context processor for the shared template variables, a before-request logger and an after-request hook that swaps in the styled error pages. Additives get their own scoped versions of all three, as we saw in the previous chapter.

 

There is a cost worth knowing about: as soon as a single after_request processor is registered, the response body has to be buffered so the processor can see it. Requests are passed straight through when there is none, and a streaming response is detected and released unbuffered either way — but a processor that only ever sets a header is still cheaper than one that inspects the body.

Themes at runtime

With WF_THEMES enabled, theme selection is a request-time decision. You remember a choice in the session and read the active one back when rendering:

example python
from fastapi import Request


async def pick_theme(request: Request):
    ctx = FluidContext.current()
    ctx.fluid.set_theme(request, "ocean")   # stored in the session
    return await ctx.fluid.render("index.html")
 

set_theme only accepts a theme you registered with add_theme and raises otherwise, so a typo is an error rather than a page that silently keeps the old style. The client-side light/dark helper (window.wf.switchTheme()) is independent of all this and works on its own.

Proxies and rate limiting

Two more runtime conveniences worth knowing. If PROXY_FIX is set, your app gets a proxy-headers middleware so client ips survive a reverse proxy — there is no wrapper object to hand to your server any more, it is simply part of the middleware stack, and PROXY_TRUSTED_HOSTS decides whose headers are believed. And the built-in slowapi limiter is reachable through fluid.limit for per-route limits on top of the global defaults:

example python
@app.get("/expensive")
@app.limit("5/minute")
async def expensive(request: Request):
    return {"ok": True}
 

The limit is enforced correctly, but the rejection currently leaves the app as a 500 rather than a 429, because the framework does not hand the limiter to the handler that builds that response. If your clients read the status code, register your own handler for slowapi.errors.RateLimitExceeded until the fix ships.

Continue reading

From here you can continue straight with Logging.