Package: aiohttp-catcher
aiohttp-catcheraiohttp-catcher is a centralized error handler foraiohttp servers.
It enables consistent error handling across your web server or API, so your code can raise Python exceptions that
will be mapped to consistent, user-friendly error messages.QuickstartWhat's New in 0.3.0?Key FeaturesReturn a ConstantStringify the ExceptionCanned HTTP 4xx and 5xx Errors (aiohttp Exceptions)Callables and AwaitablesHandle Several Exceptions SimilarlyScenarios as DictionariesAdditional FieldsDefault for Unhandled ExceptionsDevelopmentTL;DR:QuickstartInstall aiohttp-catcher:pipinstallaiohttp-catcherStart catching errors in your aiohttp-based web server:fromaiohttpimportwebfromaiohttp_catcherimportcatch,Catcherasyncdefdivide(request):quotient=1/0returnweb.Response(text=f"1 / 0 ={quotient}")asyncdefmain():# Add a catcher:catcher=Catcher()# Register error-handling scenarios:awaitcatcher.add_scenario(catch(ZeroDivisionError).with_status_code(400).and_return("Zero division makes zero sense"))# Register your catcher as an aiohttp middleware:app=web.Application(middlewares=[catcher.middleware])app.add_routes([web.get("/divide-by-zero",divide)])web.run_app(app)Making a request to/divide-by-zerowill return a 400 status code with the following body:{"code":400,"message":"Zero division makes zero sense"}IMPORTANT NOTE:aiohttp's order of middleware mattersMiddlewares that are appended further in the list of your app's middlewares act
earlier. Consider the following example:app=web.Application(middlewares=[middleware1,middleware2])In the above case,middleware2will be triggered first, and only then
willmiddleware1be triggered.  This means two things:If you register another middleware that catches exceptions but doesn't raise them
when it's done, you will need to add itbeforeyouraiohttp-catchermiddleware
or the other middleware will shadowaiohttp-catcher.If you register another middleware that relies on exceptions being raised, you want
to make sure it's addedafteryouraiohttp-catchermiddleware, to avoid having
youraiohttp-catchermiddleware shadow the other middleware. One good example isaiohttp-debugtoolbar, which, likeaiohttp-catcher, expects exceptions to be thrown and raises them when its middleware's
execution is done. In this case, you want to set upaiohttp-debugtoolbarafter appending
youraiohttp-catchermiddleware.What's New in 0.3.0?Canned Scenarios:You can now use acanned list of scenarios,
capturing all ofaiohttp's web exceptionsout of the box.More flexible Callables and Awaitables:Callables and Awaitables are now invoked with a second argument,the aiohttpRequestinstance, to add more flexibility to custom messages.Key FeaturesReturn a ConstantIn case you want some exceptions to return a constant message across your application, you can do
so by using theand_return("some value")method:awaitcatcher.add_scenario(catch(ZeroDivisionError).with_status_code(400).and_return("Zero division makes zero sense"))Stringify the ExceptionIn some cases, you would want to return a stringified version of your exception, should it entail
user-friendly information.classEntityNotFound(Exception):def__init__(self,entity_id,*args,**kwargs):super(EntityNotFound,self).__init__(*args,**kwargs)self.entity_id=entity_iddef__str__(self):returnf"Entity{self.entity_id}could not be found"@routes.get("/user/{user_id}")asyncdefget_user(request):user_id=request.match_info.get("user_id")ifuser_idnotinuser_db:raiseEntityNotFound(entity_id=user_id)returnuser_db[user_id]# Your catcher can be directed to stringify particular exceptions:awaitcatcher.add_scenario(catch(EntityNotFound).with_status_code(404).and_stringify())Canned HTTP 4xx and 5xx Errors (aiohttp Exceptions)As of version0.3.0, you
can registerall of aiohttp's web exceptions.
This is particularly useful when you want to ensure all possible HTTP errors are handled consistently.Register the canned HTTP errors in the following way:fromaiohttpimportwebfromaiohttp_catcherimportCatcherfromaiohttp_catcher.cannedimportAIOHTTP_SCENARIOSasyncdefmain():# Add a catcher:catcher=Catcher()# Register aiohttp web errors:awaitcatcher.add_scenario(*AIOHTTP_SCENARIOS)# Register your catcher as an aiohttp middleware:app=web.Application(middlewares=[catcher.middleware])web.run_app(app)Once you've registered the canned errors, you can rely on aiohttp-catcher to convert errors raised by aiohttp
to user-friendly error messages.  For example,curling a non-existent route in your server will return the
following error out of the box:{"code":404,"message":"HTTPNotFound"}Callables and AwaitablesIn some cases, you'd want the message returned by your server for some exceptions to call a custom
function.  This function can either be a synchronous function or an awaitable one.  Your function should expect
two arguments:The exception being raised by handlers.The request object - an instance ofaiohttp.web.Request.fromaiohttp.webimportRequestfromaiohttp_catcherimportcatch,Catcher# Can be a synchronous function:asyncdefwrite_message(exc:Exception,request:Request):return"Whoops"catcher=Catcher()awaitcatcher.add_scenarios(catch(MyCustomException2).with_status_code(401).and_call(write_message),catch(MyCustomException2).with_status_code(403).and_call(lambdaexc:str(exc)))Handle Several Exceptions SimilarlyYou can handle several exceptions in the same manner by adding them to the same scenario:awaitcatcher.add_scenario(catch(MyCustomException1,MyCustomException2,MyCustomException3).with_status_code(418).and_return("User-friendly error message"))Scenarios as DictionariesYou can register your scenarios as dictionaries as well:awaitcatcher.add_scenarios({"exceptions":[ZeroDivisionError],"constant":"Zero division makes zero sense","status_code":400,},{"exceptions":[EntityNotFound],"stringify_exception":True,"status_code":404,},{"exceptions":[IndexError],"func":lambdaexc:f"Out of bound:{str(exc)}","status_code":418,},)Additional FieldsYou can enrich your error responses with additional fields. You can provide additional fields using
literal dictionaries or with callables.  Your function should expect two arguments:The exception being raised by handlers.The request object - an instance ofaiohttp.web.Request.# Using a literal dictionary:awaitcatcher.add_scenario(catch(EntityNotFound).with_status_code(404).and_stringify().with_additional_fields({"error_code":"ENTITY_NOT_FOUND"}))# Using a function (or an async function):awaitcatcher.add_scenario(catch(EntityNotFound).with_status_code(404).and_stringify().with_additional_fields(lambdaexc,req:{"error_code":e.error_code,"method":req.method}))Default for Unhandled ExceptionsExceptions that aren't registered with scenarios in yourCatcherwill default to 500, with a payload similar to
the following:{"code":500,"message":"Internal server error"}DevelopmentContributions are warmly welcomed.  Before submitting your PR, please run the tests using the following Make target:makeciAlternatively, you can run each test separately:Unit tests:maketest/pyLinting with pylint:makepylintStatic security checks with bandit:makepybandit