{"id":13424718,"url":"https://github.com/melvinkcx/fastapi-events","last_synced_at":"2025-04-13T21:21:06.873Z","repository":{"id":41672825,"uuid":"393966257","full_name":"melvinkcx/fastapi-events","owner":"melvinkcx","description":"Asynchronous event dispatching/handling library for FastAPI and Starlette","archived":false,"fork":false,"pushed_at":"2024-12-21T00:55:15.000Z","size":160,"stargazers_count":475,"open_issues_count":10,"forks_count":25,"subscribers_count":5,"default_branch":"dev","last_synced_at":"2025-04-06T19:01:19.215Z","etag":null,"topics":["asgi","events","fastapi","python","starlette"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/melvinkcx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-08T13:20:27.000Z","updated_at":"2025-04-03T09:19:35.000Z","dependencies_parsed_at":"2024-06-18T18:20:04.143Z","dependency_job_id":"f21c9169-905a-4f5b-b977-0b2caa645ff1","html_url":"https://github.com/melvinkcx/fastapi-events","commit_stats":{"total_commits":93,"total_committers":5,"mean_commits":18.6,"dds":"0.19354838709677424","last_synced_commit":"91b298f100f2a98fa8d9ff1de3109aa6f3350e07"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melvinkcx%2Ffastapi-events","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melvinkcx%2Ffastapi-events/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melvinkcx%2Ffastapi-events/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melvinkcx%2Ffastapi-events/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/melvinkcx","download_url":"https://codeload.github.com/melvinkcx/fastapi-events/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248783270,"owners_count":21160899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asgi","events","fastapi","python","starlette"],"created_at":"2024-07-31T00:00:58.261Z","updated_at":"2025-04-13T21:21:06.840Z","avatar_url":"https://github.com/melvinkcx.png","language":"Python","funding_links":[],"categories":["Third-Party Extensions","Python","Async"],"sub_categories":["Utils"],"readme":"# fastapi-events\n\nAn event dispatching/handling library for FastAPI, and Starlette.\n\n[![](https://github.com/melvinkcx/fastapi-events/actions/workflows/tests.yml/badge.svg?branch=dev\u0026event=push)](https://github.com/melvinkcx/fastapi-events/actions/workflows/tests.yml)\n![PyPI - Downloads](https://img.shields.io/pypi/dw/fastapi-events)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/fastapi-events)\n\n\nFeatures:\n\n* Straightforward API for emitting events anywhere in your code.\n* Events are handled after responses are returned, ensuring no impact on response time.\n* Supports event piping to remote queues.\n* Powerful built-in handlers for local and remote event handling\n* Coroutine functions (`async def`) are treated as first-class citizens\n* Write your own handlers; don't be limited to just what `fastapi_events` provides\n* (__\u003e=0.3.0__) Supports event payload validation via Pydantic (See [here](#event-payload-validation-with-pydantic))\n* (__\u003e=0.4.0__) Supports event chaining: dispatching events within handlers (thanks to [@ndopj](https://github.com/ndopj)\n  for contributing to the idea)\n* (__\u003e=0.7.0__) Supports OpenTelemetry. See [this section](#opentelemetry-otel-support) for details\n* (__\u003e=0.9.0__) Adds support for [FastAPI dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/) in local handlers. See [this section](#using-dependencies-in-local-handler) for\n  details\n* (__\u003e=0.9.1__) Now supports Pydantic v2\n* (__\u003e=0.10.0__) Enables dispatching Pydantic models as events (thanks to [@WilliamStam](https://github.com/WilliamStam) for contributing to this idea)\n\nIf you use or appreciate this project, please consider giving it a star to help it reach more developers. Thanks =)\n\n## Installation\n\n```shell\npip install fastapi-events\n```\n\nTo use it with AWS handlers, install:\n\n```shell\npip install fastapi-events[aws]\n```\n\nTo use it with GCP handlers. install:\n\n```shell\npip install fastapi-events[google]\n```\n\nTo enable OpenTelemetry (OTEL) support, install:\n\n```shell\npip install fastapi-events[otel]\n```\n\n# Usage\n\n`fastapi-events` supports both FastAPI and Starlette. To use it, simply configure it as middleware.\n\n* Configuring `fastapi-events` for FastAPI:\n    ```python\n    from fastapi import FastAPI\n    from fastapi.requests import Request\n    from fastapi.responses import JSONResponse\n  \n    from fastapi_events.dispatcher import dispatch\n    from fastapi_events.middleware import EventHandlerASGIMiddleware\n    from fastapi_events.handlers.local import local_handler\n\n    \n    app = FastAPI()\n    app.add_middleware(EventHandlerASGIMiddleware, \n                       handlers=[local_handler])   # registering handler(s)\n    \n    \n    @app.get(\"/\")\n    def index(request: Request) -\u003e JSONResponse:\n        dispatch(\"my-fancy-event\", payload={\"id\": 1})  # Emit events anywhere in your code\n        return JSONResponse()    \n    ```\n\n* Configuring `fastapi-events` for Starlette:\n\n  ```python\n  from starlette.applications import Starlette\n  from starlette.middleware import Middleware\n  from starlette.requests import Request\n  from starlette.responses import JSONResponse\n  \n  from fastapi_events.dispatcher import dispatch\n  from fastapi_events.handlers.local import local_handler\n  from fastapi_events.middleware import EventHandlerASGIMiddleware\n  \n  app = Starlette(middleware=[\n      Middleware(EventHandlerASGIMiddleware,\n                 handlers=[local_handler])  # registering handlers\n  ])\n  \n  @app.route(\"/\")\n  async def root(request: Request) -\u003e JSONResponse:\n      dispatch(\"new event\", payload={\"id\": 1})   # Emit events anywhere in your code\n      return JSONResponse()\n  ```\n\n* Configuring `fastapi-events` for Starlite:\n\n  ```python\n  from starlite.app import Starlite\n  from starlite.enums import MediaType\n  from starlite.handlers import get\n  from starlite.middleware import DefineMiddleware\n  \n  from fastapi_events.dispatcher import dispatch\n  from fastapi_events.handlers.local import local_handler\n  from fastapi_events.middleware import EventHandlerASGIMiddleware\n  \n  @get(path=\"/\", media_type=MediaType.TEXT)\n  async def root() -\u003e str:\n      dispatch(\"new event\", payload={\"id\": 1})   # Emit events anywhere in your code\n      return \"OK\"\n\n  app = Starlite(middleware=[\n      DefineMiddleware(EventHandlerASGIMiddleware,\n                 handlers=[local_handler])  # registering handlers\n      ],\n      route_handlers=[root],\n    )\n\n  ```\n\n## Dispatching events\n\nEvents can be dispatched anywhere in the code, provided that they are dispatched before a response is generated.\n\n### Option 1 - using dict\n\n```python\n# anywhere in code\n\nfrom fastapi_events.dispatcher import dispatch\n\ndispatch(\n    \"cat-requested-a-fish\",  # Event name, accepts any valid string\n    payload={\"cat_id\": \"fd375d23-b0c9-4271-a9e0-e028c4cd7230\"}  # Event payload, accepts any arbitrary data\n)\n\ndispatch(\"a_cat_is_spotted\")  # This works too!\n```\n\n### Option 2 - using Pydantic model\n\n\u003e New feature since version 0.10.0\n\nIt is now possible to dispatch pydantic model as events. A special thanks to\n[@WilliamStam](https://github.com/WilliamStam) for introducing this remarkable idea.\n\n```python\n# anywhere in code\nimport pydantic\nfrom fastapi_events.dispatcher import dispatch\n\n\nclass CatRequestedAFishEvent(pydantic.BaseModel):\n    __event_name__ = \"cat-requested-a-fish\"\n\n    cat_id: pydantic.UUID4\n\n\n# Option 2 - dispatching event with pydantic model\ndispatch(CatRequestedAFishEvent(cat_id=\"fd375d23-b0c9-4271-a9e0-e028c4cd7230\"))\n\n# which is equivalent to:\ndispatch(\"cat-requested-a-fish\", payload={\"cat_id\": \"fd375d23-b0c9-4271-a9e0-e028c4cd7230\"})\n```\n\n## Event Payload Validation With Pydantic\n\nSince version 0.3.0, event payload validation is possible. To enable this feature, register a Pydantic model with the corresponding event name.\n\n\u003e __\u003e=0.10.0__: Event name can now be defined as a part of the payload schema as `__event_name__`\n```python\nimport uuid\nfrom enum import Enum\nfrom datetime import datetime\n\nfrom pydantic import BaseModel\nfrom fastapi_events.registry.payload_schema import registry as payload_schema\n\n\nclass UserEvents(Enum):\n    SIGNED_UP = \"USER_SIGNED_UP\"\n    ACTIVATED = \"USER_ACTIVATED\"\n\n\n# Registering your event payload schema\n@payload_schema.register(event_name=UserEvents.SIGNED_UP)\nclass SignUpPayload(BaseModel):\n    user_id: uuid.UUID\n    created_at: datetime\n\n# which is also equivalent to\n@payload_schema.register\nclass SignUpPayload(BaseModel):\n    __event_name__ = \"USER_SIGNED_UP\"\n    \n    user_id: uuid.UUID\n    created_at: datetime\n```\n\n\u003e Wildcard in event name is currently not supported\n\nThe payload will be validated automatically without any changes required when invoking the dispatcher.\n\n```python\n# Events with payload schema registered\ndispatch(UserEvents.SIGNED_UP)  # raises ValidationError, missing payload\ndispatch(UserEvents.SIGNED_UP,\n         {\"user_id\": \"9e79cdbb-b216-40f7-9a05-20d223dee89a\"})  # raises ValidationError, missing `created_at`\ndispatch(UserEvents.SIGNED_UP,\n         {\"user_id\": \"9e79cdbb-b216-40f7-9a05-20d223dee89a\", \"created_at\": datetime.utcnow()})  # OK!\n\n# Events without payload schema -\u003e No validation will be performed\ndispatch(UserEvents.ACTIVATED,\n         {\"user_id\": \"9e79cdbb-b216-40f7-9a05-20d223dee89a\"})  # OK! no validation will be performed\n\n# Events dispatched with Pydantic model (\u003e=0.10.0) -\u003e Validation will be skipped since it would have been already validated\n# If you choose to do this, you must ensure __event_name__ is defined in SignUpPayload\ndispatch(SignUpPayload(user_id=\"9e79cdbb-b216-40f7-9a05-20d223dee89a\", created_at=datetime.utcnow()))\n```\n\n\u003e Payload validation is optional. Payload of events without its schema registered will not be validated.\n\n## Handling Events\n\n### Handle events locally\n\nThe flexibility of `fastapi-events` enales customisation of how events should be handled. To begin, you may want to handle your events locally.\n\n```python\n# ex: in handlers.py\n\nfrom fastapi_events.handlers.local import local_handler\nfrom fastapi_events.typing import Event\n\n\n@local_handler.register(event_name=\"cat*\")\ndef handle_all_cat_events(event: Event):\n    \"\"\"\n    this handler will match with an events prefixed with `cat`.\n    ex: \"cat_eats_a_fish\", \"cat_is_cute\", etc\n    \"\"\"\n    # the `event` argument is nothing more than a tuple of event name and payload\n    event_name, payload = event\n\n    # TODO do anything you'd like with the event\n\n\n@local_handler.register(event_name=\"cat*\")  # Tip: You can register several handlers with the same event name\ndef handle_all_cat_events_another_way(event: Event):\n    pass\n\n\n@local_handler.register(event_name=\"*\")\nasync def handle_all_events(event: Event):\n    # event handlers can be coroutine function too (`async def`)\n    pass\n```\n\n#### Using Dependencies in Local Handler\n\n\u003e new feature in fastapi-events\u003e=0.9.0\n\u003e \nDependencies can now be utilized with local handlers, and sub-dependencies are also supported.\n\nAs of now, dependencies utilizing a generator (with the `yield` keyword) are not yet supported.\n\n```python\n# ex: in handlers.py\nfrom fastapi import Depends\n\nfrom fastapi_events.handlers.local import local_handler\nfrom fastapi_events.typing import Event\n\n\nasync def get_db_conn():\n    pass  # return a DB conn\n\n\nasync def get_db_session(\n    db_conn=Depends(get_db_conn)\n):\n    pass  # return a DB session created from `db_conn`\n\n\n@local_handler.register(event_name=\"*\")\nasync def handle_all_events(\n    event: Event,\n    db_session=Depends(get_db_session)\n):\n    # use the `db_session` here\n    pass\n```\n\n### Piping Events To Remote Queues\n\nIn larger projects, it's common to have dedicated services for handling events separately. \nFor example, `fastapi-events` includes an AWS SQS forwarder, allowing you to forward events to a remote queue.\n\n1. Register `SQSForwardHandler` as handlers:\n    ```python\n    app = FastAPI()\n    app.add_middleware(EventHandlerASGIMiddleware, \n                       handlers=[SQSForwardHandler(queue_url=\"test-queue\",\n                                                   region_name=\"eu-central-1\")])   # registering handler(s)\n    ```\n\n2. Start dispatching events! By default, events will be serialised into JSON format:\n    ```python\n    [\"event name\", {\"payload\": \"here is the payload\"}]\n    ```\n\n\u003e Tip: to pipe events to multiple queues, provide multiple handlers while adding `EventHandlerASGIMiddleware`.\n\n# Built-in handlers\n\nHere is a list of built-in event handlers:\n\n* `LocalHandler` / `local_handler`:\n    * import from `fastapi_events.handlers.local`\n    * for handling events locally. See examples [above](#handle-events-locally)\n    * event name pattern matching is done using Unix shell-style matching (`fnmatch`)\n\n* `SQSForwardHandler`:\n    * import from `fastapi_events.handlers.aws`\n    * to forward events to an AWS SQS queue\n\n* `EchoHandler`:\n    * import from `fastapi_events.handlers.echo`\n    * to forward events to stdout with `pprint`. Great for debugging purpose\n\n* `GoogleCloudSimplePubSubHandler`:\n    * import from `fastapi_events.handlers.gcp`\n    * to publish events to a single pubsub topic\n\n# Creating Custom Handlers\n\nCreating your own handler is as simple as inheriting from the `BaseEventHandler` class\nin `fastapi_events.handlers.base`.\n\nTo handle events, `fastapi_events` calls one of these methods, following this priority order:\n\n1. `handle_many(events)`:\n   The coroutine function should expect the backlog of the events collected.\n\n2. `handle(event)`:\n   If `handle_many()` is not defined in your custom handler, `handle()`\n   will be called by iterating through the events in the backlog.\n\n```python\nfrom typing import Iterable\n\nfrom fastapi_events.typing import Event\nfrom fastapi_events.handlers.base import BaseEventHandler\n\n\nclass MyOwnEventHandler(BaseEventHandler):\n    async def handle(self, event: Event) -\u003e None:\n        \"\"\"\n        Handle events one by one\n        \"\"\"\n        pass\n\n    async def handle_many(self, events: Iterable[Event]) -\u003e None:\n        \"\"\"\n        Handle events by batch\n        \"\"\"\n        pass\n```\n\n# OpenTelemetry (OTEL) support\n\nSince version 0.7.0, OpenTelemetry support has been added as an optional feature.\n\nTo enable it, make sure you install the following optional modules:\n\n```shell\npip install fastapi-events[otel]\n```\n\n\u003e Note that no instrumentation library is needed as fastapi_events supports OTEL natively\n\nSpans will be created when:\n\n* `fastapi_events.dispatcher.dispatch` is invoked,\n* `fastapi_events.handlers.local.LocalHandler` is handling an event\n\nSupport for other handlers will be added in the future.\n\n# Cookbook\n\n## 1) Suppressing Events / Disabling `dispatch()` Globally\n\nIf you wish to globally suppress events, especially during testing, you can achieve this without having to mock or patch the dispatch() function. \nSimply set the environment variable FASTAPI_EVENTS_DISABLE_DISPATCH to 1, True, or any truthy values.\n\n## 2) Validating Event Payload During Dispatch\n\n\u003e This feature requires Pydantic, which is included with FastAPI.\n\u003e If you're using Starlette, ensure that Pydantic is installed separately.\n\nSee [Event Payload Validation With Pydantic](#event-payload-validation-with-pydantic)\n\n## 3) Dispatching events within handlers (Event Chaining)\n\nIt is now possible to dispatch events within another event handlers. You'll need version 0.4 or above.\n\nComparison between events dispatched within the request-response cycle and event handlers are:\n\n|                                                                 | dispatched within request-response cycle         | dispatched within event handlers                        |\n|-----------------------------------------------------------------|--------------------------------------------------|---------------------------------------------------------|\n| processing of events                                            | will be handled after the response has been made | will be scheduled to the running event loop immediately |\n| order of processing                                             | always after the response is made                | not guaranteed                                          |\n| supports payload schema validation with Pydantic                | Yes                                              | Yes                                                     |\n| can be disabled globally with `FASTAPI_EVENTS_DISABLE_DISPATCH` | Yes                                              | Yes                                                     |\n\n## 4) Dispatching events outside of a request\n\nOne of the goals of `fastapi-events` is to dispatch events without the need to manage specific instance of `EventHandlerASGIMiddleware`.\nBy default, this is handled using `ContextVars`. \nHowever, there are scenarios where users may want to dispatch events outside the standard request sequence. \nThis can be achieved by generating a custom identifier for the middleware.\n\nBy default, the middleware identifier is generated from the object ID of the `EventHandlerASGIMiddleware` instance and is managed internally without user intervention. \nIf a user needs to dispatch events outside of a request-response lifecycle, they can generate a custom `middleware_id` value and passed it to `EventHandlerASGIMiddleware` during its creation. \nThis value can then be used with `dispatch()` to ensure the correct `EventHandlerASGIMiddleware` instance is selected.\n\nIt's important to note that dispatching events during a request does not require the middleware_id. \nThe dispatcher will automatically discover the appropriate event handler.\n\nIn the following example, the ID is generated using the object ID of the `FastAPI` instance. \nThe middleware identifier must be a unique `int`, but there are no other restrictions.\n\n```python\nimport asyncio\n\nfrom fastapi import FastAPI\nfrom fastapi.requests import Request\nfrom fastapi.responses import JSONResponse\n\nfrom fastapi_events.dispatcher import dispatch\nfrom fastapi_events.middleware import EventHandlerASGIMiddleware\nfrom fastapi_events.handlers.local import local_handler\n\napp = FastAPI()\nevent_handler_id: int = id(app)\napp.add_middleware(EventHandlerASGIMiddleware,\n                   handlers=[local_handler],  # registering handler(s)\n                   middleware_id=event_handler_id)  # register custom middleware id\n\n\nasync def dispatch_task() -\u003e None:\n    \"\"\" background task to dispatch autonomous events \"\"\"\n\n    for i in range(100):\n        # without the middleware_id, this call would raise a LookupError\n        dispatch(\"date\", payload={\"idx\": i}, middleware_id=event_handler_id)\n        await asyncio.sleep(1)\n\n\n@app.on_event(\"startup\")\nasync def startup_event() -\u003e None:\n    asyncio.create_task(dispatch_task())\n\n\n@app.get(\"/\")\ndef index(request: Request) -\u003e JSONResponse:\n    dispatch(\"hello\", payload={\"id\": 1})  # Emit events anywhere in your code\n    return JSONResponse({\"detail\": {\"msg\": \"hello world\"}})\n```\n\n# FAQs:\n\n1. I'm getting `LookupError` when `dispatch()` is used:\n    ```bash\n        def dispatch(event_name: str, payload: Optional[Any] = None) -\u003e None:\n    \u003e       q: Deque[Event] = event_store.get()\n    E       LookupError: \u003cContextVar name='fastapi_context' at 0x400a1f12b0\u003e\n    ```\n\n   Answer:\n\n   The proper functioning of `dispatch()` relies on [ContextVars](https://docs.python.org/3/library/contextvars.html). \n   Various factors can lead to a LookupError, with a common cause being the invocation of `dispatch()` outside the request-response lifecycle of FastAPI/Starlette, such as calling `dispatch()` after a response has been returned.\n\n   If you encounter this issue, a workaround is available by using a user-defined middleware_id. \n   Refer to [Dispatching Events Outside of a Request](#4-dispatching-events-outside-of-a-request) for details.\n\n   If you're encountering this during testing, consider disabling `dispatch()` for testing purposes.\n   Refer to [Suppressing Events / Disabling `dispatch()` Globally](#suppressing-events--disabling-dispatch-globally) for\n   details.\n\n2. My event handlers are not registered / Local handlers are not being executed:\n\n   Answer:\n\n   To ensure that the module where your local event handlers are defined is loaded during runtime, make sure to import the module in your __init__.py. \n   This straightforward fix guarantees the proper loading of modules during runtime.\n\n# Feedback, Questions?\n\nAny form of feedback and questions are welcome! Please create an\nissue [here](https://github.com/melvinkcx/fastapi-events/issues/new).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmelvinkcx%2Ffastapi-events","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmelvinkcx%2Ffastapi-events","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmelvinkcx%2Ffastapi-events/lists"}