Skip to main content

A centralized error handler for aiohttp servers

Project description

aiohttp-catcher

CI Job PyPI version

aiohttp-catcher is a centralized error handler for aiohttp servers. It enables consistant error handlling across your web server or API, so your code can raise Python exceptions that will be handled however you want them to.



Quickstart

from aiohttp import web
from aiohttp_catcher import catch, Catcher

async def divide(request):
    quotient = 1 / 0
    return web.Response(text=f"1 / 0 = {quotient}")


async def main():
    # Add a catcher:
    catcher = Catcher()

    # Register error-handling scenarios:
    await catcher.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-zero will return a 400 status code with the following body:

{"code": 400, "message": "Zero division makes zero sense"}

Key Features

Return a Constant

In case you want some exceptions to return a constant message across your application, you can do so by using the and_return("some value") method:

await catcher.add_scenario(
    catch(ZeroDivisionError).with_status_code(400).and_return("Zero division makes zero sense")
)

Stringify the Exception

In some cases, you would want to return a stringified version of your exception, should it entail user-friendly information.

class EntityNotFound(Exception):
    def __init__(self, entity_id, *args, **kwargs):
        super(EntityNotFound, self).__init__(*args, **kwargs)
        self.entity_id = entity_id

    def __str__(self):
        return f"Entity {self.entity_id} could not be found"


@routes.get("/user/{user_id}")
async def get_user(request):
    user_id = request.match_info.get("user_id")
    if user_id not in user_db:
        raise EntityNotFound(entity_id=user_id)
    return user_db[user_id]

# Your catcher can be directed to stringify particular exceptions:

await catcher.add_scenario(
    catch(EntityNotFound).with_status_code(404).and_stringify()
)

Callables and Awaitables

In 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. It should expect a single argument, which is the exception being raised:

# Can be a synchronous function as well:
async def write_message(exc: Exception):
    return "Whoops"

await catcher.add_scenarios(
    catch(MyCustomException2).with_status_code(401).and_call(write_message),
    catch(MyCustomException2).with_status_code(403).and_call(lambda exc: str(exc))
)

Handle Several Exceptions Similarly

You can handle several exceptions in the same manner by adding them to the same scenario:

await catcher.add_scenario(
    catch(
        MyCustomException1,
        MyCustomException2,
        MyCustomException3
    ).with_status_code(418).and_return("User-friendly error message")
)

Scenarios as Dictionaries

You can register your scenarios as dictionaries as well:

await catcher.add_scenarios(
    {
        "exceptions": [ZeroDivisionError],
        "constant": "Zero division makes zero sense",
        "status_code": 400,
    },
    {
        "exceptions": [EntityNotFound],
        "stringify_exception": True,
        "status_code": 404,
    },
    {
        "exceptions": [IndexError],
        "func": lambda exc: f"Out of bound: {str(exc)}",
        "status_code": 418,
    },
)

Additional Fields

You can enrich your error responses with additional fields. You can provide additional fields using literal dictionaries or with callables. Callables will be called with the exception object as their only argument.

# Using a literal dictionary:
await catcher.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):
await catcher.add_scenario(
    catch(EntityNotFound).with_status_code(404).and_stringify().with_additional_fields(lambda e: {"error_code": e.error_code})
)

Default for Unhandled Exceptions

Exceptions that aren't registered with scenarios in your Catcher will default to 500, with a payload similar to the following:

{"code": 500, "message": "Internal server error"}

Development

Contributions are warmly welcomed. Before submitting your PR, please run the tests using the following Make target:

make ci

Alternatively, you can run each test separately:

Unit tests:

make test/py

Linting with pylint:

make pylint

Static security checks with bandit:

make pybandit

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiohttp-catcher-0.2.0.tar.gz (5.4 kB view details)

Uploaded Source

Built Distribution

aiohttp_catcher-0.2.0-py3-none-any.whl (5.2 kB view details)

Uploaded Python 3

File details

Details for the file aiohttp-catcher-0.2.0.tar.gz.

File metadata

  • Download URL: aiohttp-catcher-0.2.0.tar.gz
  • Upload date:
  • Size: 5.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.0.0 CPython/3.8.12 Linux/5.11.0-1022-azure

File hashes

Hashes for aiohttp-catcher-0.2.0.tar.gz
Algorithm Hash digest
SHA256 95c224081e2e26c444a30b07e966247458d9d535ca19b2d6318bee41ea5c2b91
MD5 a811da00dd6758c97e5bc972ce053b21
BLAKE2b-256 541c98d4e79462b69cd2da59b4ab03daf89b5cefa38947c45f74893d4fa05a67

See more details on using hashes here.

File details

Details for the file aiohttp_catcher-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: aiohttp_catcher-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 5.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.0.0 CPython/3.8.12 Linux/5.11.0-1022-azure

File hashes

Hashes for aiohttp_catcher-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 07af6021fda8176c86bd4cf8a961a43bd611e0403e0c016fce3df32d2ba183be
MD5 5f3a45f0c8a1c1e317209d9c18cf241f
BLAKE2b-256 da681ac96412859476936d74c7c885f900ce96bfa989a4a97fc6962801253647

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page