FastAPI save context that will be available in endpoints

8.7k views Asked by At

My goal is to extract some information from request and put it in some global context. First thing I tried was middleware function which does this

user_ctx: ContextVar[typing.Optional[UUID]] = ContextVar('user_ctx', default=None)

def save_user_ctx(request: Request):
    uid = ... # logic that extracts user uid from request header
    user_ctx.set(uid)

And I add this in some another middleware function for example my_middleware.

So it really sets the contexts but as far as I see middleware works in another thread, so if I try something like

@router.post('/some_url', response_model=SomeResponseModel)
def some_url(data: SomeRequestModel, auth_checker = Depends(my_middleware)):
    user_ctx.get() # >> None

auth_checker has all auth info, so middleware works okay. But uid = user_ctx.get() returns None.

This is just an example. Please

What I want to achieve is to store some context from request in thread which serves endpoints automatically. I mean without writing ctx.set(smth) as the first line of each endpoint.

I also tried to write decorator

def save_request_ctx(func):
    @wraps(func)
    def wrap(*args, **kwargs):
        ctx.set(...)
        return func(*args, **kwargs)
    return wrap

But if endpoint doesnt have request: Request as parameter it gets - it doesnt appear in either args or kwargs. So, also, if you can tell how to force each endpoint to have request as parameter without manually adding it in each endpoint function - it would also be accepted as solution.

If my explanation of what I want is complicated I'll try to clarify it.

1

There are 1 answers

3
Tom Wojcik On

You might want to use my starlette-context for that.

In order to achieve what you want it'd be best if you subclassed ContextMiddleware and overwrote the set_context method. What's returned will then be available in the context object that you can import anywhere.

Example code


from starlette_context.middleware import ContextMiddleware


class YourMiddleware(ContextMiddleware):
    async def set_context(self, request: Request) -> dict:
        return {"from_middleware": True}

After you register this middleware, in your view/logger you can import

from starlette_context import context

and use context["from_middleware"].

Notice that I prepared "plugins" for most common use cases. If you want to access request/correlation id anywhere in your app, just init middleware with

from starlette_context import context, plugins
from starlette_context.middleware import ContextMiddleware

middleware = [
    Middleware(
        ContextMiddleware,
        plugins=(plugins.RequestIdPlugin(), plugins.CorrelationIdPlugin()),
    )
]

app = Starlette(debug=True, middleware=middleware)

FastAPI uses Starlette interface so you should be good to go.

Regarding

But if endpoint doesnt have request: Request as parameter it gets - it doesnt appear in either args or kwargs. So, also, if you can tell how to force each endpoint to have request as parameter without manually adding it in each endpoint function - it would also be accepted as solution.

I can't answer that as I never tried using a view without a request argument.

Edit: I added RawContextMiddleware that doesn't need the request object.