How to access Request body in FastAPI class based view - python

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

Related

How to set up that FastAPI doesn't automatically follow redirect?

Is is possible to set up FastApi in such a way that a redirect response will not automatically be followed? Assume I have the following functions:
from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse
fast_api_app = FastAPI()
#router.get('/some_url')
async def redirect_function():
redirect_response = RedirectResponse('/redirect_url', status_code=303)
return redirect_response
#router.get('/redirect_url')
async def redirected_function():
response = Response(status_code=200)
return response
When I use the requests library or the FastAPI test client to access "/some_url" I always get automatically redirected to "/redirect_url" and the response's status code will be 200. My wished behavior would be that the response status code is 303 and I am responsible for the redirection.

FastAPI test client redirects the request when it is a POST or PUT

I'm writing test for a FastAPI application. When I write test for an endpoint with GET method everything works as expected, but when I call an endpoint with POST method somehow my request gets redirected to http://testserver this is an example of my endpoints:
from json import JSONDecodeError
from fastapi import APIRouter
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.status import HTTP_400_BAD_REQUEST
router = APIRouter()
#router.post("/test")
async def test(
request: Request,
):
try:
body = await request.json()
except JSONDecodeError:
return JSONResponse(content={}, status_code=HTTP_400_BAD_REQUEST)
return JSONResponse(content=body)
and this is an example of my test:
from starlette.testclient import TestClient
from app import app
client = TestClient(app)
def test_cookies():
res = client.post(
"api/test/",
json={
"name": "test"
},
)
assert 200 == res.status_code
again this happens just with POST and PUT requests the GET request works just fine. any idea why is this happening?
Your endpoint is registered as /api/test, while you're calling /api/test/ - notice the difference in the trailing slash.
By default FastAPI will issue a redirect to make your browser talk to the correct endpoint. The http://testserver URL you're seeing is the internal hostname used in the TestClient.

How to use an endpoint with multiple body params and fileupload in FastAPI?

I have an application that stores files and additional information e.g. author, comments DB. I can pass an file to FastAPI, but getting issue by passing it together with parameters.
I checked following question How to add multiple body params with fileupload in FastAPI? and this issue [QUESTION] Use UploadFile in Pydantic model #657 but without success.
I tried 2 definitions in the FastAPI endpoint
Option 1
class Properties(BaseModel):
language: Optional[str] = None
author: Optional[str] = None
#app.post("/uploadfile/")
async def uploadfile(params: Properties, file: UploadFile = File(...)):
#internal logic
Option 2
#app.post("/uploadfile/")
async def uploadfile(file: UploadFile = File(...),
language: str = Form(...),
author: Optional[str] = Form(None)):
#internal logic
Client code:
Following code is used on client side, but response is 422 Unprocessable Entity for both options.
with open(path, 'rb') as f:
response = requests.post('http://localhost:8005/uploadfile/', data={'language':'en',
'author':'me'}, files={'file': f})
Both options can't be tested from swagger, there I am getting response: value is not a valid dict. Data looks good for me, but maybe I am missing something.
It seems that the client code is wrong, but also there I tried several changes without success.
In advance, thanks for your support!
Solution that works for me
As 'Drdilyor' wrote in his comment I have use option 2, as we are sending file. My issue was within order of arguments. After changing them everything starts working.
Per this issue, In my opinion it is an HTTP limitation, we have to either use application/json or multipart/formdata, but not both. And since we are uploading files, multipart/formdata has to be used. This code (somehow) works:
#app.post("/uploadfile/")
async def uploadfile(author: Optional[str] = Form(...),
language: Optional[str] = Form(...),
file: UploadFile = File(...)):
I tested it with
curl http://localhost:8000/uploadfile/ -X POST -F author=me -F language=en -F file=#/path/to/file, not sure about requests
You could also use query params as well i think

Sending files using python 'aiohttp' produce "There was an error parsing the body"

I am trying to make two services communicate. The first API is exposed to the user.
The second is hidden and can process files. So the first can redirect requests.
I want to make of the post request asynchronus using aiohttp but i am facing this error : "There was an error parsing the body"
To recreate the error :
Lets say this is the server code
from fastapi import FastAPI
from fastapi import UploadFile, File
app = FastAPI()
#app.post("/upload")
async def transcript_file(file: UploadFile = File(...)):
pass
And this is the client code :
from fastapi import FastAPI
import aiohttp
app = FastAPI()
#app.post("/upload_client")
async def async_call():
async with aiohttp.ClientSession() as session:
headers = {'accept': '*/*',
'Content-Type': 'multipart/form-data'}
file_dict = {"file": open("any_file","rb")}
async with session.post("http://localhost:8000/upload", headers=headers, data=file_dict) as response:
return await response.json()
Description :
Run the server on port 8000 and the client on any port you like
Open the browser and open docs on the client.
Execute the post request and see the error
Environment :
aiohttp = 3.7.4
fastapi = 0.63.0
uvicorn = 0.13.4
python-multipart = 0.0.2
Python version: 3.8.8
From this answer:
If you are using one of multipart/* content types, you are actually required to specify the boundary parameter in the Content-Type header, otherwise the server (in the case of an HTTP request) will not be able to parse the payload.
You need to remove the explicit setting of the Content-Type header, the client aiohttp will add it implicitly for you, including the boundary parameter.

FastAPI - Set the url or path for the Request object

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)
...

Categories