Errorhandler aiohttp webserver? - python

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

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.

How to access request object in router function using FastAPI?

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

Migrating AWS Lamda + APIs to FastAPI + Mangum

I have a Lambda function handler which takes in a request, and classify into REST or HTTP based on the call and preprocess it inpreprocess_and_call to go through validation of the call, and finally makes a call to the backend API endpoints.
My current Lambda function (main.py) is set up like this:
# Lambda handler
def handler(event, context):
# if REST call
if event.get('routeKey', None) == "$default" and 'rawPath' in event:
path = event.get('path', None)
# ... parse other parameters such as method, headers, etc ...
# else if HTTP call
elif event.get('path', None):
path = event.get('rawPath', None)
# ... parse other parameters such as method, headers, etc ...
return preprocess_and_call(path, method, headers, query_params, body)
# calls the endpoints after validation, authrization, etc
def preprocess_and_call(path, method, headers, query_params, body):
try:
if path in endpoints_list:
endpoint_to_call = endpoints_list[path]
# ... validation logics here ...
response = endpoint_to_call(path, method, headers, query_params, body)
return response
except Exception as ex:
... return error ...
This is one of the sample endpoints:
def sample_endpoint(path, method, headers, query_params, body):
try:
id = query_paraams.get('id', None)
model = SampleModel()
model.process()
response_body = model.json()
return {
'status_code': 200,
'body': response_body
}
except Exception as ex:
# ... process exception ...
I am trying to move my lambda function + APIs to make use of FastAPI + Mangum and this is what I have so far:
Currently, my lambda handler is set up using FastAPI middleware decorator and Mangum like the following (Mangum works as an adapter for ASGI applications like the ones you can create with fastAPI so that they can send and receive information from API Gateway to Lambda and vice versa. I removed handler because Mangum provides HTTP and REST support):
from routers import router
app = FastAPI()
app.include_router(router)
# Handler for api calls
#app.middleware("http")
async def preprocess_and_call(request: Request, call_next):
try:
if path in endpoints_list:
# ... validation logics here ...
response = await call_next(request) # calls the endpoints with corresponding path
# ... process the response ...
except Exception as ex:
# ... process exception ...
handler = Mangum(app)
And I have several endpoints which gets called from the above lambda function, do their job and return the response accordingly like this:
#router.get("/index")
def sample_endpoint(request: Request):
try:
query_params= request.query_params
# ....
return Response(
status_code = 200,
content=json.dumps(response_body)
)
except Exception as ex:
# ... process exception ...
I've tested this locally, and things work smoothly. Would this be the best way of doing this or are there other/better ways? Any suggestions would be greatly appreciated!

Combining async and sync requests in python?

I am trying to make a request to server A, where the response will be a list of requests, which I will make to server B.
Currently request to server A is just a simple sync request like this:
import requests
req = requests.get('https://server-a.com')
data = req.json()
list_of_requests = data['requests'] # requests for server B
Since list_of_requests can be a few thousand items long, I would like to use async to speed up the requests to B.
I've looked at several examples of async HTTP requests using aiohttp, such as from
https://towardsdatascience.com/fast-and-async-in-python-accelerate-your-requests-using-asyncio-62dafca83c33
import aiohttp
import asyncio
import os
from aiohttp import ClientSession
GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes?q=isbn:"
LIST_ISBN = [
'9780002005883',
'9780002238304',
'9780002261982',
'9780006163831',
'9780006178736',
'9780006280897',
'9780006280934',
'9780006353287',
'9780006380832',
'9780006470229',
]
def extract_fields_from_response(response):
"""Extract fields from API's response"""
item = response.get("items", [{}])[0]
volume_info = item.get("volumeInfo", {})
title = volume_info.get("title", None)
subtitle = volume_info.get("subtitle", None)
description = volume_info.get("description", None)
published_date = volume_info.get("publishedDate", None)
return (
title,
subtitle,
description,
published_date,
)
async def get_book_details_async(isbn, session):
"""Get book details using Google Books API (asynchronously)"""
url = GOOGLE_BOOKS_URL + isbn
try:
response = await session.request(method='GET', url=url)
response.raise_for_status()
print(f"Response status ({url}): {response.status}")
except HTTPError as http_err:
print(f"HTTP error occurred: {http_err}")
except Exception as err:
print(f"An error ocurred: {err}")
response_json = await response.json()
return response_json
async def run_program(isbn, session):
"""Wrapper for running program in an asynchronous manner"""
try:
response = await get_book_details_async(isbn, session)
parsed_response = extract_fields_from_response(response)
print(f"Response: {json.dumps(parsed_response, indent=2)}")
except Exception as err:
print(f"Exception occured: {err}")
pass
async with ClientSession() as session:
await asyncio.gather(*[run_program(isbn, session) for isbn in LIST_ISBN])
However, all of the examples I have looked at start with the list of requests already defined. My question is, what is the proper pythonic way/pattern of combining a single sync request and then using that request to 'spawn' async tasks?
Thanks a bunch!

abort or make_response or jsonify

I'm writing Flask web-application and want to know about best practice for returning unsuccessful response.
Code example:
#app.route("/api/model", methods=["DELETE"])
def delete_models():
"""
Deleting all models.
"""
try:
model_service.delete_all_models()
response = make_response(jsonify(success=True))
except Exception as ex:
response = make_response(jsonify(str(ex)), 500)
response.headers["Content-Type"] = "application/json"
return response
I found theree different approaches.
return jsonify(success=False)
abort(404, description="There is no model with this index!")
response.headers["Content-Type"] = "application/json"
return response```
Which one is the best way? What advantages and disadvantages in each of them?
You can use the error handler decorator of flask, as it explained in the doc.
For example:
#app.errorhandler(InvalidUsage)
def handle_invalid_usage(error):
response = jsonify(error.to_dict())
response.status_code = error.status_code
# Log here the error
return response
# In your exception or error control use:
raise InvalidUsage('This view is gone', status_code=410)

Categories