Hello, I had an issue with Starlette where I had to access request information outside of a view function. I successfully fixed my issue and would like to share the discussion I had with @tomchristie and how I solved the issue.
Here's a snippet of the question I asked and the answer I got:
From me:
Hi!
I am currently working with Starlette (porting over a Flask project to FastAPI) and I'm wondering if there's some way to get the current request from anywhere in the code, like you can do with Flask using from flask import request.
I need to access data from the request but from a non-view part of the code (a logging filter, so I can't even pass it along).
I've read the whole documentation and looked at all the GitHub issues but couldn't find anything that fits my needs, this https://github.com/encode/starlette/issues/379 is the closest I found, which seems to be part of what I want. However I found no way of importing the request object so it's useless to me.
Reply:
No we don't currently do that. Using contextvars would be the way to implement it. You could do this in a middleware class without having to alter Starlette directly. Happy to chat through how you'd achieve that, but let's open a ticket if there's more to talk over here. :)
RequestContextMiddleware:
from contextvars import ContextVar
from uuid import uuid4
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
REQUEST_ID_CTX_KEY = "request_id"
_request_id_ctx_var: ContextVar[str] = ContextVar(REQUEST_ID_CTX_KEY, default=None)
def get_request_id() -> str:
return _request_id_ctx_var.get()
class RequestContextMiddleware(BaseHTTPMiddleware):
async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
):
request_id = _request_id_ctx_var.set(str(uuid4()))
response = await call_next(request)
_request_id_ctx_var.reset(request_id)
return response
In your app initialisation:
app.add_middleware(RequestContextMiddleware)
Usage:
Now you will be able to import the get_request_id function anywhere and get the current request ID, or None if you are not in the context of a request.
You can also add many more context vars by creating new ContextVar objects, and using them the same way as _request_id_ctx_var in my example.
Maybe Starlette could expose an object that abstracts the context vars in some way. But my idea on this isn't clear, I don't know how generic this could be.
Hi @MarcDufresne, FastAPI creator here :smile:
I see you solved your problem, but in case it's useful here are some additional notes you might want to have in mind:
ContextVars are Python 3.7 only. So, for example, not compatible with TensorFlow for now (although there's PyTorch for Python 3.7). Also ContextVars might be hard to understand, a bit black magic-like (might be difficult for teammates).
Given that you are using FastAPI, there's a chance you can solve it using the Dependency Injection system.
I'm curious about how your logging system works, I understand you can't pass the request as a parameter but it can access the global context var...
I can imagine, for example, a middleware that adds the UUID to the requests' .state, and then a dependency that requests the logger (I imagine you call a logger object at some point, or something similar). Then, in the dependency, you could get the request, read the UUID and do the filtering at that level, before passing one object or another to the actual path operation function/route.
As it's less "magic" and (maybe) more "functional", it might be easier to grasp for teammates, etc.
An example of something similar is the SQL tutorial, it adds a SQLAlchemy session to the request, that is then received with dependency injection in each route/"path operation function".
I don't wanna hijack this issue (in Starlette), but if something like that makes sense (at the FastAPI level) feel free to open an issue there: https://github.com/tiangolo/fastapi/issues/new/choose
@tiangolo logger may be called outside of view function, if using Dependency Injection, logger object has to be passed to all functions again and again.
Would very much welcome a third party packaging along the lines of @MarcDufresne's middleware.
I needed something like this so I made a package.
https://github.com/tomwojcik/starlette-context
One of the examples show how to use logging with context so request id and context id are automatically injected into json logs. Hope it helps : ) Feedback welcome.
CC @tomchristie
Fantastic, want to open a PR adding it to the “Third Party Packages” docs?
Fantastic, want to open a PR adding it to the “Third Party Packages” docs?
There goes my first PR in OSS!
Most helpful comment
There goes my first PR in OSS!
https://github.com/encode/starlette/pull/770