FastAPI - Set the url or path for the Request object - python

I have a function that looks like this:
#app.middleware("http")
async def process_api_event(request: Request, call_next):
url = request.url
path = request.url.path
# request.__setattr__('url', 'sample_url')
# request.url.__ setattr__('path', 'sample_path')
In the above function, depending on the situation I would like to change the request url, or path.
I tried request.__setattr__('url', 'sample_url') and request.url.__ setattr__('path', 'sample_path') as shown above but I wasn't able to do it due to AttributeError: can't set attribute error. I read through the FastAPI and Starlette documentation, but couldn't really find info that I needed in this case. Any help would be greatly appreciated!

request.url is a property that gets _url attribute, so you can set _url (but request.scope and request.base_url will not change)
from starlette.datastructures import URL
#app.middleware("http")
async def process_api_event(request: Request, call_next):
request._url = URL('sample_url')
print(request.url)
...

Related

How to pass unencoded URL in FastAPI/Swagger UI via GET method?

I would like to write a FastAPI endpoint, with a Swagger page (or something similar) that will accept a non-encoded URL as input. It should preferably use GET, not POST method.
Here's an example of a GET endpoint that does require double-URL encoding.
#app.get("/get_from_dub_encoded/{double_encoded_url}")
async def get_from_dub_encoded(double_encoded_url: str):
"""
Try https%253A%252F%252Fworld.openfoodfacts.org%252Fapi%252Fv0%252Fproduct%252F7622300489434.json
"""
original_url = urllib.parse.unquote(urllib.parse.unquote(double_encoded_url))
response = requests.get(original_url)
return response.json()
Which generates a Swagger interface as below.
The following PUT request does solve my problem, but the simplicity of a GET request with a form is better for my co-workers.
class InputModel(BaseModel):
unencoded_url: AnyUrl = Field(description="An unencoded URL for an external resource", format="url")
#app.post("/unencoded-url")
def unencoded_url(inputs: InputModel):
response = requests.get(inputs.unencoded_url)
return response.json()
How can I deploy a convenient interface like that without requiring users to write the payload for a PUT request or to perform double URL encoding?
This post has some helpful related discussion, but doesn't explicitly address the FORM solution: How to pass URL as a path parameter to a FastAPI route?
You can use Form instead of query parameters as payload.
from fastapi import FastAPI, Form
import requests
app = FastAPI()
#app.post("/")
def get_url(url: str = Form()):
"""
Try https://world.openfoodfacts.org/api/v0/product/7622300489434.json
"""
response = requests.get(url)
return response.json()
Swagger interface would look like:
You'll need to install python-multipart.
Tip: Don't use async endpoint if you are using requests or any non-async library.

How to access Request body in FastAPI class based view

I have the request object as class-level dependency like shown here, to be able to use it in all routes within the class. The problem however is when I try to access the request body - it errors with Stream consumed error.
Example code:
from fastapi import File, Request, UploadFile
from fastapi_utils.inferring_router import InferringRouter
from fastapi_utils.cbv import cbv
app = FastAPI()
router = InferringRouter()
#cbv(router)
class ExampleRouteClass:
request: Request
file: Optional[UploadFile] = File(None)
type: Optional[str] = None
#router.post("/example-route/")
async def example_function(self):
headers = self.request.headers # this works like a charm
data = await self.request.json() # this errors with RuntimeError: Stream consumed
return headers
app.include_router(router)
Example curl request:
curl -x POST 'http://example.com:port/example-route'
-H 'secret-key: supersecret'
-d '{"some_data": "data"}'
The problem was with the UploadFile - so as a solution I placed it in the route itself, as it is the only route in the class that uses it anyway.
Leaving this here for anyone who has this error:
class ExampleRouteClass:
request: Request
type: Optional[str] = None
#router.post("/example-route/")
async def example_function(self, file: Optional[UploadFile] = File(None)):
headers = self.request.headers # this works like a charm
data = await self.request.json() # this works now too
return headers

aiohttp: How to update request headers according to request body?

I am trying to implement a type of custom authentication by using aiohttp something like the example in this link but I also need request body. Here is an example for requests:
class CustomAuth(AuthBase):
def __init__(self, secretkey):
self.secretkey = secretkey
def get_hash(self, request):
if request.body:
data = request.body.decode('utf-8')
else:
data = "{}"
signature = hmac.new(
str.encode(self.secretkey),
msg=str.encode(data),
digestmod=hashlib.sha256
).hexdigest().upper()
return signature
def __call__(self, request):
request.headers["CUSTOM-AUTH"] = self.get_hash(request)
return request
I've looked into tracing and BasicAuth but they are useless in my situation. On on_request_start request body is not ready, on on_request_chunk_sent headers have already been sent. A solution like BasicAuth don't have access the request data at all.
Do you have any idea?
Thanks in advance.

Flask Middleware with both Request and Response

I want to create a middleware function in Flask that logs details from the request and the response. The middleware should run after the Response is created, but before it is sent back. I want to log:
The request's HTTP method (GET, POST, or PUT)
The request endpoint
The response HTTP status code, including 500 responses. So, if an exception is raised in the view function, I want to record the resulting 500 Response before the Flask internals send it off.
Some options I've found (that don't quite work for me):
The before_request and after_request decorators. If I could access the request data in after_request, my problems still won't be solved, because according to the documentation
If a function raises an exception, any remaining after_request functions will not be called.
Deferred Request Callbacks - there is an after_this_request decorator described on this page, which decorates an arbitrary function (defined inside the current view function) and registers it to run after the current request. Since the arbitrary function can have info from both the request and response in it, it partially solves my problem. The catch is that I would have to add such a decorated function to every view function; a situation I would very much like to avoid.
#app.route('/')
def index():
#after_this_request
def add_header(response):
response.headers['X-Foo'] = 'Parachute'
return response
return 'Hello World!'
Any suggestions?
My first answer is very hacky. There's actually a much better way to achieve the same result by making use of the g object in Flask. It is useful for storing information globally during a single request. From the documentation:
The g name stands for “global”, but that is referring to the data being global within a context. The data on g is lost after the context ends, and it is not an appropriate place to store data between requests. Use the session or a database to store data across requests.
This is how you would use it:
#app.before_request
def gather_request_data():
g.method = request.method
g.url = request.url
#app.after_request
def log_details(response: Response):
g.status = response.status
logger.info(f'method: {g.method}\n url: {g.url}\n status: {g.status}')
return response
Gather whatever request information you want in the function decorated with #app.before_request and store it in the g object.
Access whatever you want from the response in the function decorated with #app.after_request. You can still refer to the information you stored in the g object from step 1. Note that you'll have to return the response at the end of this function.
you can use flask-http-middleware for it link
from flask import Flask
from flask_http_middleware import MiddlewareManager, BaseHTTPMiddleware
app = Flask(__name__)
class MetricsMiddleware(BaseHTTPMiddleware):
def __init__(self):
super().__init__()
def dispatch(self, request, call_next):
url = request.url
response = call_next(request)
response.headers.add("x-url", url)
return response
app.wsgi_app = MiddlewareManager(app)
app.wsgi_app.add_middleware(MetricsMiddleware)
#app.get("/health")
def health():
return {"message":"I'm healthy"}
if __name__ == "__main__":
app.run()
Every time you make request, it will pass the middleware
Okay, so the answer was staring me in the face the whole time, on the page on Deferred Request Callbacks.
The trick is to register a function to run after the current request using after_this_request from inside the before_request callback. This is the code snippet of what worked for me:
#app.before_request
def log_details():
method = request.method
url = request.url
#after_this_request
def log_details_callback(response: Response):
logger.info(f'method: {method}\n url: {url}\n status: {response.status}')
These are the steps:
Get the required details from the response in the before_request callback and store them in some variables.
Then access what you want of the response in the function you decorate with after_this_request, along with the variables you stored the request details in earlier.

Flask - access the request in after_request or teardown_request

I want to be able to access the request object before I return the response of the HTTP call.
I want access to the request via "teardown_request" and "after_request":
from flask import Flask
...
app = Flask(__name__, instance_relative_config=True)
...
#app.before_request
def before_request():
# do something
#app.after_request
def after_request(response):
# get the request object somehow
do_something_based_on_the_request_endpoint(request)
#app.teardown_request
def teardown_request(response):
# get the request object somehow
do_something_based_on_the_request_endpoint(request)
I saw that I can add the request to g and do something like this:
g.curr_request = request
#app.after_request
def after_request(response):
# get the request object somehow
do_something_based_on_the_request_endpoint(g.curr_request)
But the above seems a bit strange. I'm sure that there's a better way to access the request.
Thanks
The solution is simple -
from flask import request
#app.after_request
def after_request(response):
do_something_based_on_the_request_endpoint(request)
return response
Also try teardown_request(exception). This executes "regardless of whether there was an exception or not". Check the documentation: http://flask.pocoo.org/docs/0.12/api/#flask.Flask.teardown_request

Categories