24

Within the FastAPI framework:

While request data can certainly be passed around as an argument, I would like to know if it is possible for a function to access information about the current request without being passed an argument.

Disclaimer: I do not think global access to Request data a good practice, and yet I have a use case where it would be very good to be able to do.

Andrew Allaire
  • 1,162
  • 1
  • 11
  • 19
  • 1
    This question has 25K views and was asked over three years ago. It was marked as a duplicate of a question that has 50 or so views and was asked yesterday...and closed? Isn't this a bit backwards? – Andrew Allaire May 25 '23 at 19:06
  • Not my doing, but I think you are taking it personally. I think, its a way to collect all answers for a question in one spot, not two. So it doesnt matter which one is flagged as the duplicate – Rohit Gupta Jun 21 '23 at 13:25
  • Yeah I am taking it a little personally in part because there is now the incorrect sentence on top saying: "This question already has an answer here:". It feels a bit off-putting to be rebuked for asking a question already answered when one hasn't. Maybe they should change the wording to "these questions are related:"? – Andrew Allaire Jun 21 '23 at 14:28

5 Answers5

23

A solution provided here defines a context manager, that you can access globally. For each request, you are extracting the relevant information (like headers) & pass it to the context manager.

Since fastapi is built with Starlette, you can use the library starlette-context. It is creating a context object that you can use without passing it as argument. The main caveat is that you still need to pass a request object to all your routes.

EDIT: In starlette-context==0.3.0 new middleware has been added. Starlette team started to discourage (here) the use of their BaseHTTPMiddleware, in particulary for StreamingResponse/FileResponse endpoints. You might want to use RawContextMiddleware which also doesn't require the request object but it's experimental as there is no documentation in Starlette for writing custom middleware without the interface. But it seems to be working.

Sample code from this lib to illustrate:

import uvicorn
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.middleware import Middleware

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

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

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


@app.route('/')
async def index(request: Request):  # This argument is still needed here
    return JSONResponse(context.data)  # Your context data


uvicorn.run(app, host="0.0.0.0")
Colin Le Nost
  • 460
  • 4
  • 10
  • 11
    Hey, I wrote [starlette-context](https://github.com/tomwojcik/starlette-context)! If you have questions don't hesitate to open a ticket on GH. – Tom Wojcik Sep 28 '20 at 19:47
  • Hey @TomWojcik, I have an issue with starlette_context: I am working on a FastAPI app, and I had to add your package because I need to receive some data over a http header and put something in a context to be used laters. The application works perfectly, but I have a few test that fail because "You didn't use ContextMiddleware or you're trying to access context object outside of the request-response cycle". Now, I am using the same factory to generate the app object with all the correct middlewares, and the test call contains the header I need to test. I don't understand why the error occurs – Bruno Ripa Apr 01 '21 at 13:33
  • Hey @bruno-ripa, please open a ticket on GH but chances are it's due to incorrect order of middlewares. – Tom Wojcik Apr 02 '21 at 07:54
  • 1
    Very glad to find this thread, and also very glad to find this library. I'd suggest checking out [the latest discussion on Github](https://github.com/tomwojcik/starlette-context/issues/47#issuecomment-1154018437). In short: it looks like RawContextMiddleware has been established as the path forward. Thanks @TomWojcik for providing this library and pivoting to address this issue. – Paul Keister Jul 22 '22 at 21:52
6

You can get/set arbitrary attributes on request.state from Starlette.

https://www.starlette.io/requests/#other-state

Please refer the below issue for detailed explanation and implementation:

https://github.com/tiangolo/fastapi/issues/633

K3---rnc
  • 6,717
  • 3
  • 31
  • 46
gksingh
  • 277
  • 3
  • 6
6

Wanted to provide an updated answer here. My original comment is on a starlette issue, here.

I am using FastAPI, and needed a way to access information on the request object outside of a view. I initially looked at using starlette-context but found the below solution to work for my needs.

Credit to Marc (see starlette issue above) for the basis of this solution.

As noted by Colin Le Nost above, the authors warn against using BaseHTTPMiddleware -- the parent class Marc's middleware inherits from.

Instead, the suggestion is to use a raw ASGI middleware. However, there isn't much documentation for this. I was able to use Starlette's AuthenticationMiddleware as a reference point, and develop what I needed in combination with Marc's wonderful solution of ContextVars.

# middleware.py
from starlette.types import ASGIApp, Receive, Scope, Send

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 CustomRequestMiddleware:
    def __init__(
        self,
        app: ASGIApp,
    ) -> None:
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] not in ["http", "websocket"]:
            await self.app(scope, receive, send)
            return

        request_id = _request_id_ctx_var.set(str(uuid4()))

        await self.app(scope, receive, send)

        _request_id_ctx_var.reset(request_id)

And then in the app setup:

# main.py
app.add_middleware(CustomRequestMiddleware)

And finally, the non-view function:

# myfunc.py
import get_request_id

request_id = get_request_id()

This should enable you to use ContextVars as a way to get any info from the request object you need, and make it available outside of view function. Thanks again to everyone in this thread for all the help, and I hope the above is useful!

Klutch27
  • 167
  • 2
  • 9
0

I would typically do this using producer-consumer style messaging queue. I have an example repo showing how I use a global queue to push data from a post request to a WebSocket that broadcasts this to clients.

While this might not be your exact use case, you should be able to adapt it to suit.

The guts of it are a Notifier class which pushes data to the queue:

async def push(self, msg: str):
    await self.channel.default_exchange.publish(
        Message(msg.encode("ascii")),
        routing_key=self.queue_name,
    )

And on the consumer side, I have a _notify function that receives messages from the queue and sends it through the WebSocket:

async def _notify(self, message: IncomingMessage):
    living_connections = []
    while len(self.connections) > 0:
        websocket = self.connections.pop()
        await websocket.send_text(f"{message.body}")
        living_connections.append(websocket)
    self.connections = living_connections
Nick Martin
  • 731
  • 3
  • 17
-2

you can use starlette Request

for example:

from starlette.requests import Request
from fastapi import FastApi

app = FastApi()
@app.get('/')
def get(request:Request):
    requests_header = request.headers
    return "Hi"
Includeamin
  • 73
  • 1
  • 3
  • 4
    In this example we are getting the request object passed as an argument to our get callback. And we could pass it to other functions as an argument. However I was asking about being able to access whatever the current request was from another module without passing it as an argument. For example if we create a logging.Formatter that wants to include something from the request as a standard part of each log message, we need a way to access the request object without passing it to the format method. – Andrew Allaire Mar 09 '20 at 13:43
  • Woops, sorry about that. I haven't found a good way, I was looking into this. You can use fastapi Dependencies (https://fastapi.tiangolo.com/tutorial/bigger-applications/#dependencies) but I've found the function is invoked every time. Another way I've heard about is using a MiddleWare. Some stuff I found on the web but haven't been able to get personally working. - https://github.com/tiangolo/fastapi/issues/633 - https://github.com/tiangolo/fastapi/issues/81 Sorry about that, I understand better now what you're interested in. – mathewguest Jul 03 '23 at 05:28