How to get the cookies from an HTTP request using FastAPI? - python

Is it possible to get the cookies when someone hits the API? I need to read the cookies for each request.
#app.get("/")
async def root(text: str, sessionKey: str = Header(None)):
print(sessionKey)
return {"message": text+" returned"}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5001 ,reload=True)

You can do it in the same way you are accessing the headers in your example (see docs):
from fastapi import Cookie
#app.get("/")
async def root(text: str, sessionKey: str = Header(None), cookie_param: int | None = Cookie(None)):
print(cookie_param)
return {"message": f"{text} returned"}

Option 1
Use the Request object to get the cookie you wish, as described in Starlette documentation.
from fastapi import Request
#app.get('/')
async def root(request: Request):
print(request.cookies.get('sessionKey'))
return 'OK'
Option 2
Use the Cookie parameter, as described in FastAPI documentation.
from fastapi import Cookie
#app.get('/')
async def root(sessionKey: str = Cookie(None)):
print(sessionKey)
return 'OK'

Related

Combining Restapi and Websockets

I have a rest api server which makes call to some other Apis,I am accessing the data I get from the server on a react js frontend,But for certain usecases I need to fetch real time data from backed,is there any way do this together,below is my code
from flask import Flask,request
from flask_cors import CORS
from tuya_connector import TuyaOpenAPI, TUYA_LOGGER
app = Flask(__name__)
CORS(app)
#app.get("/api/device/<string:deviceid>")
def getdata(deviceid):
ACCESS_ID = ""
ACCESS_KEY = ""
API_ENDPOINT = ""
# Enable debug log
# Init OpenAPI and connect
openapi = TuyaOpenAPI(API_ENDPOINT, ACCESS_ID, ACCESS_KEY)
openapi.connect()
# Set up device_id
DEVICE_ID = deviceid
# Call APIs from Tuya
# Get the device information
response = openapi.get("/v1.0/devices/{}".format(DEVICE_ID))
return response
I want to have traditional request response service along with real time data fetching
Websockets endpoints are exactly what you're looking for. If that is not too late, I'd recommend switching to FastAPI which supports WebSockets "natively" (out-of-the-box) - https://fastapi.tiangolo.com/advanced/websockets
If you need to keep using Flask, there are a few packages that allow you to add WebSockets endpoints: https://flask-sock.readthedocs.io/en/latest/
With FastAPI, this is that simple:
#app.get("/")
async def get():
return {"msg": "This is a regular HTTP endpoint"}
#app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message text was: {data}")

Fast API with pytest using AsyncClient gives 422 on post?

I'm trying to send a request to an api using pytest through httpx.AsynClient
#pytest.mark.anyio
async def test_device_create_with_data(self, client, random_uuid):
device_create = DeviceCreateFactory.build(subId=random_uuid)
json = device_create.json(by_alias=True)
response = await client.post("/device", json=json)
assert response.status_code == 200
Client fixture:
from httpx import AsyncClient
#pytest.fixture(scope="session")
async def client():
async with AsyncClient(
app=app,
base_url="http://test/api/pc",
headers={"Content-Type": "application/json"}
) as client:
yield client
API endpoint:
#device_router.post("/device", response_model=CommonResponse)
async def create_device(device: DeviceCreate):
_, err = await crud_device.create_device(device)
if err:
return get_common_response(400, err)
return get_common_response(200, "ok")
Schemas:
class DeviceBase(BaseModel):
device_id: StrictStr = Field(..., alias='deviceId')
device_name: StrictStr = Field(..., alias='deviceName')
device_type: StrictStr = Field(..., alias='deviceType')
age_mode: AgeModeType = Field(..., alias='ageMode')
class Config:
allow_population_by_field_name = True
validate_all = True
validate_assignment = True
class DeviceCreate(DeviceBase):
sub_id: StrictStr = Field(..., alias='subId')
class Config:
orm_mode = True
Factory:
from pydantic_factories import ModelFactory
from app.core.schemas.device import DeviceCreate
class DeviceCreateFactory(ModelFactory):
__model__ = DeviceCreate
And i'm getting a 422 error with following response content:
"message":"bad request","details":{"deviceId":"field required","deviceName":"field required","deviceType":"field required","ageMode":"field required","subId":"field required"}
Then i examined the data of the request being sent and got:
b'"{\\"deviceId\\": \\"de\\", \\"deviceName\\": \\"\\", \\"deviceType\\": \\"\\", \\"ageMode\\": \\"child\\", \\"subId\\": \\"11aded61-9966-4be1-a781-387f75346811\\"}"'
Seems like everything is okay, but where is the trouble then?
I've tried to examine the request data in exception handler of 422
I've made:
#app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
print(await request.json())
response = validation_error_response(exc)
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder(response.dict())
)
But code after print is unreachable, because await request.json() never ends and runs forever trying to print a request json
Is there a way to manage this problem?
Thanks for any suggest!
P.S.
python version: 3.8.9
fastapi version: 0.68.1
httpx version: 0.21.1
You're double encoding your content as JSON - you're both asking for it to be returned as a JSON string, and then telling your request method to encode it as JSON a second time. json= as an argument to the method on the client converts the given data to JSON - it does not expect already serialized JSON.
You can see this in your request string because it starts with " and not with { as you'd expect:
b'"{\
^
Instead build your model around a dictionary - or as I'd prefer in a test - build the request by hand, so that you're testing how you imagine an actual request should look like.
You can use dict in the same was as you'd use json for a Pydantic model:
device_create = DeviceCreateFactory.build(subId=random_uuid)
response = await client.post("/device", json=device_create.dict(by_alias=True))
assert response.status_code == 200

Is it possible to have an api call another api, having them both in same application?

I have a python application running on my localhost:3978. Is it possible to make an api call to http://localhost:3978/api/users from http://localhost:3978/api/accounts?
#routes.get("/api/accounts")
async def accounts(request):
api_url = "http://127.0.0.1:3978/api/users"
response = requests.get(api_url)
return json_response(data=respone.json())
#routes.get("/api/users")
async def users(request):
pass
You can use url_path_for() to feed in RedirectResponse of starlette.responses as stated here. For example:
from fastapi import FastAPI
from starlette.responses import RedirectResponse
app = FastAPI()
#app.get("/a")
async def a():
return {"message": "Hello World"}
#app.get('/b')
async def b():
url = app.url_path_for("a")
response = RedirectResponse(url=url)
return response

FastApi Post Request With Bytes Object Got 422 Error

I am writing a python post request with a bytes body:
with open('srt_file.srt', 'rb') as f:
data = f.read()
res = requests.post(url='http://localhost:8000/api/parse/srt',
data=data,
headers={'Content-Type': 'application/octet-stream'})
And in the server part, I tried to parse the body:
app = FastAPI()
BaseConfig.arbitrary_types_allowed = True
class Data(BaseModel):
data: bytes
#app.post("/api/parse/{format}", response_model=CaptionJson)
async def parse_input(format: str, data: Data) -> CaptionJson:
...
However, I got the 422 error:
{"detail":[{"loc":["body"],"msg":"value is not a valid dict","type":"type_error.dict"}]}
So where is wrong with my code, and how should I fix it?
Thank you all in advance for helping out!!
FastAPI by default will expect you to pass json which will parse into a dict. It can't do that if it's isn't json, which is why you get the error you see.
You can use the Request object instead to receive arbitrary bytes from the POST body.
from fastapi import FastAPI, Request
app = FastAPI()
#app.get("/foo")
async def parse_input(request: Request):
data: bytes = await request.body()
# Do something with data
You might consider using Depends which will allow you to clean up your route function. FastAPI will first call your dependency function (parse_body in this example) and will inject that as an argument into your route function.
from fastapi import FastAPI, Request, Depends
app = FastAPI()
async def parse_body(request: Request):
data: bytes = await request.body()
return data
#app.get("/foo")
async def parse_input(data: bytes = Depends(parse_body)):
# Do something with data
pass
If the endgoal of your request is to only send bytes then please look at the documentation of FastAPI to accept bytes-like objects: https://fastapi.tiangolo.com/tutorial/request-files.
There is no need for the bytes to be enclosed into a model.

Is it possible to use Flask RestX wih Flask's 2.0+ async await?

Usage of async/await was presented in Flask 2.0. (https://flask.palletsprojects.com/en/2.0.x/async-await/)
I am using Flask-RestX so is it possible to use async/await in RestX requests handlers?
Something like:
#api.route('/try-async')
class MyResource(Resource):
#api.expect(some_schema)
async def get(self):
result = await async_function()
return result
is not working and when I try to reach this endpoint I'm getting error:
TypeError: Object of type coroutine is not JSON serializable
Is there any info on that?
Package versions:
flask==2.0.1
flask-restx==0.4.0
and I've also installed flask[async] as documentation suggests.
I've gotten around this by using an internal redirect
#api.route('/try-async')
class MyResource(Resource):
#api.expect(some_schema)
def get(self):
return redirect(url_for('.hidden_async'), code=307)
#api.route('/hidden-async', methods=['GET'])
async def hidden_async():
result = await async_function()
return result
Redirecting with code=307 will ensure any method and body are unchanged after the redirect (Link). So passing data to the async function is possible as well.
#api.route('/try-async')
class MyResource(Resource):
#api.expect(some_schema)
def post(self):
return redirect(url_for('.hidden_async'), code=307)
#api.route('/hidden-async', methods=['POST'])
async def hidden_async():
data = request.get_json()
tasks = [async_function(d) for d in data]
result = await asyncio.gather(tasks)
return result

Categories

Resources