How to access request object in router function using FastAPI? - python

I am new to FastAPI framework, I want to print out the response. For example, in Django:
#api_view(['POST'])
def install_grandservice(req):
print(req.body)
And in FastAPI:
#app.post('/install/grandservice')
async def login():
//print out req
I tried to to like this
#app.post('/install/grandservice')
async def login(req):
print(req.body)
But I received this error: 127.0.0.1:52192 - "POST /install/login HTTP/1.1" 422 Unprocessable Entity
Please help me :(

Here is an example that will print the content of the Request for fastAPI.
It will print the body of the request as a json (if it is json parsable) otherwise print the raw byte array.
async def print_request(request):
print(f'request header : {dict(request.headers.items())}' )
print(f'request query params : {dict(request.query_params.items())}')
try :
print(f'request json : {await request.json()}')
except Exception as err:
# could not parse json
print(f'request body : {await request.body()}')
#app.post("/printREQUEST")
async def create_file(request: Request):
try:
await print_request(request)
return {"status": "OK"}
except Exception as err:
logging.error(f'could not print REQUEST: {err}')
return {"status": "ERR"}

You can define a parameter with a Request type in the router function, as
from fastapi import FastAPI, Request
app = FastAPI()
#app.post('/install/grandservice')
async def login(request: Request):
print(request)
return {"foo": "bar"}
This is also covered in the doc, under Use the Request object directly section

Related

How to return a custom 404 Not Found page using FastAPI?

I am making a rick roll site for Discord and I would like to redirect to the rick roll page on 404 response status codes.
I've tried the following, but didn't work:
#app.exception_handler(fastapi.HTTPException)
async def http_exception_handler(request, exc):
...
Update
A more elegant solution would be to use a custom exception handler, passing the status code of the exception you would like to handle, as shown below:
from fastapi.responses import RedirectResponse
from fastapi.exceptions import HTTPException
#app.exception_handler(404)
async def not_found_exception_handler(request: Request, exc: HTTPException):
return RedirectResponse('https://fastapi.tiangolo.com')
or, use the exception_handlers parameter of the FastAPI class like this:
async def not_found_error(request: Request, exc: HTTPException):
return RedirectResponse('https://fastapi.tiangolo.com')
exception_handlers = {404: not_found_error}
app = FastAPI(exception_handlers=exception_handlers)
Note: In the examples above, a RedirectResponse is returned, as OP asked for redirecting the user. However, you could instead return some custom Response, HTMLResponse or Jinja2 TemplateResponse, as demosntrated in the example below.
Working Example
app.py
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.exceptions import HTTPException
async def not_found_error(request: Request, exc: HTTPException):
return templates.TemplateResponse('404.html', {'request': request}, status_code=404)
async def internal_error(request: Request, exc: HTTPException):
return templates.TemplateResponse('500.html', {'request': request}, status_code=500)
templates = Jinja2Templates(directory='templates')
exception_handlers = {
404: not_found_error,
500: internal_error
}
app = FastAPI(exception_handlers=exception_handlers)
templates/404.html
<!DOCTYPE html>
<html>
<title>Not Found</title>
<body>
<h1>Not Found</h1>
<p>The requested resource was not found on this server.</p>
</body>
</html>
templates/500.html
<!DOCTYPE html>
<html>
<title>Internal Server Error</title>
<body>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error or
misconfiguration and was unable to complete your request.
</p>
</body>
</html>
Original answer
You would need to create a middleware and check for the status_code of the response. If it is 404, then return a RedirectResponse. Example:
from fastapi import Request
from fastapi.responses import RedirectResponse
#app.middleware("http")
async def redirect_on_not_found(request: Request, call_next):
response = await call_next(request)
if response.status_code == 404:
return RedirectResponse("https://fastapi.tiangolo.com")
else:
return response
from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
# --- Constants --- #
templates = Jinja2Templates(directory="./templates")
# --- Error handler --- #
def lost_page(request, exception):
headers = {"Content-Type": "text/html"}
if isinstance(exception, HTTPException):
status_code = exception.status_code
detail = exception.detail
elif isinstance(exception, Exception):
status_code = 500
detail = "Server Error"
headers["X-Error-Message"] = exception.__class__.__name__
headers["X-Error-Line"] = str(exception.__traceback__.tb_lineno)
else:
status_code = 500
detail = f"Server Error\n\nDetails: {exception}"
return templates.TemplateResponse(
"404.html",
{"request": request, "status_code": status_code, "detail": detail},
status_code=status_code,
headers=headers,
)
exception_handlers = {num: lost_page for num in range(400, 599)}
app = FastAPI(exception_handlers=exception_handlers)
This is a snippet I've used across a few projects, it's essentially a catch-all for all 400 and 500 status codes.
from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
# --- Constants --- #
templates = Jinja2Templates(directory="./templates")
This block imports relevant libraries and initializes Jinja2Templates, which allows us to render HTML using FastAPI. Docs.
Let's dissect
def lost_page(request, exception):
headers = {"Content-Type": "text/html"}
if isinstance(exception, HTTPException):
status_code = exception.status_code
detail = exception.detail
elif isinstance(exception, Exception):
status_code = 500
detail = "Server Error"
headers["X-Error-Message"] = exception.__class__.__name__
headers["X-Error-Line"] = str(exception.__traceback__.tb_lineno)
else:
status_code = 500
detail = f"Server Error\n\nDetails: {exception}"
return templates.TemplateResponse(
"404.html",
{"request": request, "status_code": status_code, "detail": detail},
status_code=status_code,
headers=headers,
)
FastAPI's exception handler provides two parameters, the request object that caused the exception, and the exception that was raised.
def lost_page(request, exception):
^^ Our function takes these two parameters.
headers = {"Content-Type": "text/html"}
These are the headers we're going to send back along with the request.
if isinstance(exception, HTTPException):
status_code = exception.status_code
detail = exception.detail
elif isinstance(exception, Exception):
status_code = 500
detail = "Server Error"
headers["X-Error-Name"] = exception.__class__.__name__
else:
status_code = 500
detail = f"Server Error\n\nDetails: {exception}"
If the exception parameter is a HTTPException (raised by Starlette/FastAPI), then we're going to set the status_code and detail appropriately. An example of an HTTPException is a 404 error, if you try accessing an endpoint that doesn't exist, a HTTPException is raised and handled automatically by FastAPI.
Then, we check if it's an instance of Exception, which is one of Python's in-built exception classes. This covers exceptions such as ZeroDivisionError, FileNotFoundError, etc. This usually means that it's an issue with our code, such as trying to open a file that doesn't exist, dividing by zero, using an unknown attribute, or something else that raised an exception which wasn't handled inside of the endpoint function.
The else block shouldn't trigger in any case, and can be removed, it's just something I keep to appease my conscience.
After the status_code, detail and headers are set,
return templates.TemplateResponse(
"404.html",
{"request": request, "status_code": status_code, "detail": detail},
status_code=status_code,
headers=headers,
)
We return our 404 template, the TemplateResponse function takes in a few parameters, "404.html" being the file we want to return, {"request": request, "status_code": status_code, "detail": detail} being the request object and the values for embeds we want to fill (embeds are a way to pass information between jinja2 and Python). Then we define the status code of the response, along with its headers.
This is a 404 html template I use alongside the error handler.
exception_handlers = {num: lost_page for num in range(400, 599)}
app = FastAPI(exception_handlers=exception_handlers)
Exception handlers uses dict comprehension to create a dictionary of status codes, and the functions that should be called,
exception_handlers = {400: lost_page, 401: lost_page, 402: lost_page, ...}
Is how it'll look after the comprehension, until 599.
FastAPI Allows us to pass this dict as a parameter of the FastAPI class,
app = FastAPI(exception_handlers=exception_handlers)
This tells FastAPI to run the following functions when the endpoint function returns a particular status code.
To conclude, the snippet above and this error template should help you handle all FastAPI errors in a nice, user-friendly and clean way.

FAILED test_main.py::test_login_success - assert 405 == 200

I am new to pytest and working on to test the code. How ever the test result showing failed.
**main.py**
#app.post("/loginsuccess/", response_class=HTMLResponse)
async def login_success(request: Request, username: str = Form(...), password: str = Form(...)):
p = await User_Pydantic.from_tortoise_orm(await User.get(username=username, password=password))
json_compatible_item_data = jsonable_encoder(p)
if json_compatible_item_data is not None:
logger.info("Logged in Successfully")
return templates.TemplateResponse("homepage.html", {"request": request, "username":username})
else:
status_code:int
status_code = 500
logger.error("Invalid Credentials")
return templates.TemplateResponse("index.html", {"request":request, "status_code":status_code})
**test_main.py**
def test_login_success():
response = client.get('/loginsuccess/', json={'username': 'sheik', 'password':'abdullah'})
assert response.status_code==200
The test result below
================================================================== short test summary info ==================================================================
FAILED test_main.py::test_login_success - assert 405 == 200
actually, the response you are getting from the server is 405, not 200. That is why your assertion is failing.
HTTP 405 is Method Not Allowed response status code indicates that the request method is known by the server but is not supported by the target resource.
Just make sure that the server/backend handles /loginsuccess/ as a Post request
It looks that your request has invalid arguments or the backend is not configured the right way.
you also need to pass data in the form of json object,
import json
foo = {'username': 'sheik', 'password':'abdullah'}
json_data = json.dumps(foo)
then pass this data to client.post()

How to restrict content-type in FastAPI request header

I'm quite new to the FastAPI framework, I want to restrict my request header content type with "application/vnd.api+json", But I can't able to find a way to configure my content type with the Fast API route instance.
Any info will be really useful.
A better approach is to declare dependency:
from fastapi import FastAPI, HTTPException, status, Header, Depends
app = FastAPI()
def application_vnd(content_type: str = Header(...)):
"""Require request MIME-type to be application/vnd.api+json"""
if content_type != "application/vnd.api+json":
raise HTTPException(
status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
f"Unsupported media type: {content_type}."
" It must be application/vnd.api+json",
)
#app.post("/some-path", dependencies=[Depends(application_vnd)])
def some_path(q: str = None):
return {"result": "All is OK!", "q": q}
So it can be reused if needed.
For successful request it'll return something like this:
{
"result": "All is OK!",
"q": "Some query"
}
And for unsuccessful something like this:
{
"detail": "Unsupported media type: type/unknown-type. It must be application/vnd.api+json"
}
Each request has the content-type in its headers. You could check it like so:
import uvicorn
from fastapi import FastAPI, HTTPException
from starlette import status
from starlette.requests import Request
app = FastAPI()
#app.get("/hello")
async def hello(request: Request):
content_type = request.headers.get("content-type", None)
if content_type != "application/vnd.api+json":
raise HTTPException(
status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
detail=f"Unsupported media type {content_type}")
return {"content-type": content_type}
if __name__ == '__main__':
uvicorn.run("main", host="127.0.0.1", port=8080)
Hope that helps 🙂

Errorhandler aiohttp webserver?

How can I create http error router in aiohttp web server?
from aiohttp import web
routes = list()
routes.append(web.route('POST', '/reg', handler))
How can I create for example:
routes.append(web.error_handler(404, handler404)
Common way to create custom error pages in aiohttp is to use middleware:
from aiohttp import web
#web.middleware
async def error_middleware(request, handler):
try:
response = await handler(request)
# this is needed to handle ``return web.HTTPNotFound()`` case
if response.status == 404:
return web.Response(text='First custom 404 message', status=404)
return response
except web.HTTPException as ex:
# this is needed to handle ``raise web.HTTPNotFound()`` case
if ex.status == 404:
return web.Response(text='Second custom 404 message', status=404)
raise
# this is needed to handle non-HTTPException
except Exception:
return web.Response(text='Oops, something went wrong', status=500)
app = web.Application(middlewares=[error_middleware])
One more example is here

Change response based on content type of request in Flask

I have a Flask application that returns both HTML pages and JSON responses to API requests. I want to change what an error handler returns based on the content type of the request. If the client requests application/json, I want to return a jsonify response, otherwise I want to return a render_template response. How can I detect what was requested and change the response appropriately?
The current error handlers I have only return an HTML response.
def register_errorhandlers(app):
"""Register error handlers."""
def render_error(error):
"""Render error template."""
# If a HTTPException, pull the `code` attribute; default to 500
error_code = getattr(error, 'code', 500)
return render_template('{0}.html'.format(error_code)), error_code
for errcode in [401, 404, 500]:
app.errorhandler(errcode)(render_error)
Use request.content_type to get the content type the client sent with the request. Use request.accept_mimetypes the get the mimetypes the client indicated it can accept in a response. Use these to determine what to return.
from flask import request, jsonify, render_template
if request.accept_mimetypes.accept_json:
return jsonify(...)
else:
return render_template(...)
I used the after_request decorator to do this and checked the content type:
#app.after_request
def after_request_helper(resp):
if resp.content_type == "text/html":
# If a HTTPException, pull the `code` attribute; default to 500
error_code = getattr(error, 'code', 500)
return render_template('{0}.html'.format(error_code)), error_code
else:
return app.errorhandler(errcode)(render_error)
A more detailed answer:
def wants_json_response():
return request.accept_mimetypes['application/json'] >= \
request.accept_mimetypes['text/html']
The wants_json_response() helper function compares the preference for JSON or HTML selected by the client in their list of preferred formats. If JSON rates higher than HTML, then it is necessary to return a JSON response.
Otherwise, return the original HTML responses based on templates.
For the JSON responses would slightly supplement the function with one condition:
if wants_json_response(): which is what you need. So the answer is in that.
If the condition is true we could write a function that would generate a response:
def api_error_response(status_code, message=None):
payload = {'error': HTTP_STATUS_CODES.get(status_code, 'Unknown error')}
if message:
payload['message'] = message
response = jsonify(payload)
response.status_code = status_code
return response
This function uses the handy HTTP_STATUS_CODES dictionary from Werkzeug (a core dependency of Flask) that provides a short descriptive name for each HTTP status code.
For easier and faster understanding, 'error' is used to represent errors, so you only need to worry about the numeric status code and the optional long description.
The jsonify() function returns a Flask Response object with a default status code of 200, so after the response is created, it is necessary to set the status code to the correct one for the error.
So if we put it all together now it would look like this:
# app/__init__.py
import requests
def register_errorhandlers(app):
from .errors import render_error
for e in [
requests.codes.INTERNAL_SERVER_ERROR,
requests.codes.NOT_FOUND,
requests.codes.UNAUTHORIZED,
]:
app.errorhandler(e)(render_error)
and
# app/errors.py
import requests
from flask import render_template, request, jsonify
from werkzeug.http import HTTP_STATUS_CODES
from .extensions import db
def api_error_response(status_code, message=None):
payload = {'error': HTTP_STATUS_CODES.get(status_code, 'Unknown error')}
if message:
payload['message'] = message
response = jsonify(payload)
response.status_code = status_code
return response
def wants_json_response():
return request.accept_mimetypes['application/json'] >= \
request.accept_mimetypes['text/html']
def render_error(e):
if requests.codes.INTERNAL_SERVER_ERROR == e.code:
db.session.rollback()
if wants_json_response():
return api_error_response(e.code)
else:
return render_template(f'{e.code}.html'), e.code
Additionally
Then they could use the response generation for other cases as well.
The most common error that the API is going to return is going to be
the code 400, which is the error for “bad request”. This is the error
that is used when the client sends a request that has invalid data in it.
In order to generate messages to the function below even easier in these cases, we forward only the required description - message.
def bad_request(message):
return api_error_response(400, message)
I hope this will help in approaching with errors :)

Categories