Get content-length of FastAPI response - python-3.x

Im trying to get the content-length of FastAPI response on the server side for logging purpose. Is this possible? Thanks.
#app.get("/foo")
async def foo(background_tasks: BackgroundTasks):
data = {"foo": "foo"}
response_content_length = get_content_length()
background_tasks.add_task(log, response_content_length )
return data

You can create your own route by inheriting APIRoute class, now you should be able to log everything, without repeating yourself.
from fastapi import FastAPI, Request, Response, Body, BackgroundTasks, APIRouter
from fastapi.routing import APIRoute
from typing import Callable, List
class ContextIncludedRoute(APIRoute):
def get_route_handler(self) -> Callable:
original_route_handler = super().get_route_handler()
async def custom_route_handler(request: Request) -> Response:
response: Response = await original_route_handler(request)
content_length = response.headers["content-length"]
print(content_length)
return response
return custom_route_handler
app = FastAPI()
router = APIRouter(route_class=ContextIncludedRoute)
#router.post("/dummy")
async def dummy():
return {"foo":"foo"}
app.include_router(router)

Related

Forwarding a request to another API in Python (Rust) Robyn

I am using FastAPI to take in a JSON file which will then be the body of an API request out.. Orwell and Good so far. Now I want to apply the same but with robyn built on rust, instead of FastAPI. Not managed to get any joy with calling the API here at the point marked ??.
What things do I need to consider (documentation is sparse). Does robyn cut it alone, or am I missing something?
from robyn import Robyn, jsonify
app = Robyn(__file__)
#app.post("/yt")
async def json(request):
body = request["body"]
outurl = "https://translate.otherapi.com/translate/v1/translate"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer {0}".format(TOKEN)
}
?? response_data = await call_api(data)
return response_data['translations'][0]
app.start(port=5000)
With FastAPI:
import aiohttp
import aiofiles
import json
import requests
from fastapi import FastAPI, Header, Depends, HTTPException, Request
app = FastAPI()
async def call_api(data):
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
async with session.post(url, headers=headers, json=data) as resp:
response_data = await resp.json()
return response_data
#app.post("/yt")
async def root(request:Request):
data = await request.json()
file_path = "data.json"
await write_json_to_file(data, file_path)
data = await read_json_from_file(file_path)
response_data = await call_api(data)
return response_data['translations'][0]
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)
The author of Robyn here. I am unable to understand what you are trying to achieve here. However, there is one issue, request["body"] returns a byte string array at the moment.
You need to alter your code to this:
import json
#app.post("/yt")
async def json(request):
body = bytearray(request["body"]).decode("utf-8")
data = json.loads(body)
outurl = "https://translate.otherapi.com/translate/v1/translate"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer {0}".format(TOKEN)
}
response_data = await call_api(data)
return response_data['translations'][0]
This is peculiarity that I am not very fond of. We are hoping to fix this within the next few releases.
I hope this helped :D

How to add context to HTMLResponse?

How to add context to HTMLResponse as you can do with TemplateResponse to insert content into the HTML site? Example with TemplateResponse:
return templates.TemplateResponse('index.html', context={'request': request, "Variable1": V1, "Variable2": V2})
And how to add the context here?:
#app.get("/")
def root():
return HTMLResponse(pkg_resources.resource_string(__name__, "index.html"))
You can use jinja to achieve solution to your problem.
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
#app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
context = {'request': request, "Variable1": V1, "Variable2": V2}
return templates.TemplateResponse("item.html", context)
Reference: https://fastapi.tiangolo.com/advanced/templates/

Using FastAPI to create custom decorators with variables and incoming payload

I am trying to add a custom decorator to secure fastapi endpoints to list of users. The incoming payload has JWT data in the header and/or cookies.
With out the decorator, fastapi_jwt_auth allows/denies to the endpoints as long as the token is valid. However when adding the custom decorator, it fails to pass the header/cookie payload to the decorator to be parsed and return the correct response if valid/invalid.
From what I could understand from this post it should be able to obtain the payload from the router function. However it only seems to create a blank class object.
from functools import wraps
from fastapi import Depends
from fastapi.responses import JSONResponse
from fastapi_jwt_auth import AuthJWT
def auth(users: Union[List[str], None, str] = None):
def decorator_auth(func):
#wraps(func)
def wrapper_auth(*args, **kwargs):
Authorize.jwt_required()
jwt_username = Authorize.get_raw_jwt()['username']
if jwt_username not in users:
return JSONResponse(
status_code=401,
content={"detail": "Unauthorized"} )
return func(*args, **kwargs)
return wrapper_auth
return decorator_auth
#app.get('/data')
def secure_data(Authorize: AuthJWT = Depends()):
Authorize.jwt_required()
return JSONResponse(
status_code=200,
content={"detail": "Payload only all with valid tokens."})
#app.get('/data1')
#auth(users=['user1', 'user3')
def secure_data_1(Authorize: AuthJWT = Depends()):
return JSONResponse(
status_code=200,
content={"detail": "Payload only for User 1 and User 3."})
#app.get('/data2')
#auth(users=['user2'])
def secure_data2(Authorize: AuthJWT = Depends()):
return JSONResponse(
status_code=200,
content={"detail": "Payload only for User 2."} )

FastAPI - can't access path parameters from middleware

My typical path is something like
/user/{user_id}/resource/{resource_id}
I have a validation method, already written in async python, like this:
async def is_allowed(user_id: int, resource_id: int) -> bool
That returns a boolean: true if the user can access the resource, false otherwise.
I want to write a middleware that calls is_allowed extracting the variables from the path.
I fiddled around but I can't find how to get them: I was expecting to get this information from request.path_params.
A somehow more complete example (edited following #Marcelo Trylesinski answer):
import logging
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import Response
app = FastAPI()
_logger = logging.getLogger()
_logger.setLevel(logging.DEBUG)
async def is_allowed(user_id, resource_id):
_logger.error(user_id)
_logger.error(resource_id)
return True
#app.middleware('http')
async def acl(request: Request, call_next):
user_id = request.path_params.get("user_id", None)
resource_id = request.path_params.get("resource_id", None)
allowed = await is_allowed(user_id, resource_id)
if not allowed:
return Response(status_code=403)
else:
return await call_next(request)
#app.get('/user/{user_id}/resource/{resource_id}')
async def my_handler(user_id: int, resource_id: int):
return {"what": f"Doing stuff with {user_id} on {resource_id}"}
The logged values are None.
You will not be able to achieve your goal with a Middleware, because Middlewares are executed before the routing.
Therefore FastAPI/Starlette doesn't know which path it will match to and cannot populate path_params.
You will have to use a different solution, such as passing these params on a cookie, header or query arg, or using a decorator/Dependency.
Reference:
https://github.com/encode/starlette/issues/230
https://fastapi.tiangolo.com/tutorial/middleware/#middleware

Response not loading in localhost when using requests.request method in python

import tornado.web
import tornado.ioloop
from apiApplicationModel import userData
from cleanArray import Label_Correction
import json
import requests
colName=['age', 'resting_blood_pressure', 'cholesterol', 'max_heart_rate_achieved', 'st_depression', 'num_major_vessels', 'st_slope_downsloping', 'st_slope_flat', 'st_slope_upsloping', 'sex_male', 'chest_pain_type_atypical angina', 'chest_pain_type_non-anginal pain', 'chest_pain_type_typical angina', 'fasting_blood_sugar_lower than 120mg/ml', 'rest_ecg_left ventricular hypertrophy', 'rest_ecg_normal', 'exercise_induced_angina_yes', 'thalassemia_fixed defect', 'thalassemia_normal',
'thalassemia_reversable defect']
class processRequestHandler(tornado.web.RequestHandler):
def post(self):
data_input_array = []
for name in colName:
x = self.get_body_argument(name, default=0)
data_input_array.append(int(x))
label = Label_Correction(data_input_array)
finalResult = int(userData(label))
output = json.dumps({"Giveput":finalResult})
self.write(output)
class basicRequestHandler(tornado.web.RequestHandler):
def get(self):
self.render('report.html')
class staticRequestHandler(tornado.web.RequestHandler):
def post(self):
data_input_array = []
for name in colName:
x = self.get_body_argument(name, default=0)
data_input_array.append(str(x))
send_data = dict(zip(colName, data_input_array))
print(send_data)
print(type(send_data))
url = "http://localhost:8887/output"
headers={}
response = requests.request('POST',url,headers=headers,data=send_data)
print(response.text.encode('utf8'))
print("DONE")
if __name__=='__main__':
app = tornado.web.Application([(r"/",basicRequestHandler),
(r"/result",staticRequestHandler),
(r"/output",processRequestHandler)])
print("API IS RUNNING.......")
app.listen(8887)
tornado.ioloop.IOLoop.current().start()
Actually I am trying to create an API and the result of the API can be used but
The page keeps on loading, and no response is shown.
Response should be a python dictionary send by post function of class processRequestHandler
After using a debugger the lines after response = requests.request('POST',url,headers=headers,data=send_data) are not executed.
The class processRequestHandler is working fine when checked with POSTMAN.
requests.request is a blocking method. This blocks the event loop and prevents any other handlers from running. In a Tornado handler, you need to use Tornado's AsyncHTTPClient (or another non-blocking HTTP client such as aiohttp) instead.
async def post(self):
...
response = await AsyncHTTPClient().fetch(url, method='POST', headers=headers, body=send_data)
See the Tornado users's guide for more information.

Resources