{"id":19651623,"url":"https://github.com/bryzgaloff/aioevproc","last_synced_at":"2025-02-27T01:26:37.382Z","repository":{"id":57408853,"uuid":"265802070","full_name":"bryzgaloff/aioevproc","owner":"bryzgaloff","description":"Minimal async/sync event processing framework on pure Python","archived":false,"fork":false,"pushed_at":"2022-09-17T10:13:44.000Z","size":12,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-10T05:17:19.234Z","etag":null,"topics":["asyncio","event-handler","python","python3","python38"],"latest_commit_sha":null,"homepage":null,"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/bryzgaloff.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}},"created_at":"2020-05-21T09:01:38.000Z","updated_at":"2022-09-17T10:13:47.000Z","dependencies_parsed_at":"2022-09-13T04:50:56.673Z","dependency_job_id":null,"html_url":"https://github.com/bryzgaloff/aioevproc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bryzgaloff%2Faioevproc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bryzgaloff%2Faioevproc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bryzgaloff%2Faioevproc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bryzgaloff%2Faioevproc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bryzgaloff","download_url":"https://codeload.github.com/bryzgaloff/aioevproc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240960400,"owners_count":19885125,"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":["asyncio","event-handler","python","python3","python38"],"created_at":"2024-11-11T15:07:13.967Z","updated_at":"2025-02-27T01:26:37.006Z","avatar_url":"https://github.com/bryzgaloff.png","language":"Python","readme":"# aioevproc\n\nIt is a minimal async/sync event processing framework. Has **no dependencies**\n    and uses nothing except **pure Python 3.8**.\n\n_TL;DR_ Do not have much time? See [recap on examples](#recap-on-examples) and\n    [recap on conditions](#recap-on-conditions). Now go and use `aioevproc`! :)\n\nThe package name stands for **A**sync**IO** **Ev**ents **Proc**essing.\n\n## Examples\n\nSimplest example for a single async handler, just echo the message text:\n\n```python\nfrom aioevproc import EventsProcessor, handler, Event\n\nclass EchoTelegramBot(EventsProcessor):\n    @handler(lambda event: 'message' in event and 'text' in event['message'])\n    async def echo_message(self, event: Event) -\u003e None:\n        await self.reply_to_message(text=event['message']['text'])\n```\n\nA little bit more complex Telegram bot example, see the\n    [explanation below](#what-do-the-examples-demonstrate):\n\n```python\nfrom aioevproc import EventsProcessor, handler, Event\nfrom contextlib import asynccontextmanager, contextmanager\n\nclass TelegramBot(EventsProcessor):\n    # synchronous middleware for any exception: log exception\n    @handler()\n    @contextmanager\n    def log_exception(self, event: Event) -\u003e Generator[None, None, None]:\n        try:\n            yield\n        except:\n            logging.exception('Error!')\n\n    # async middleware for any exception: send excuse message to the user\n    @handler()\n    @asynccontextmanager\n    def send_excuse_message(self, event: Event) -\u003e AsyncGenerator[None, None]:\n        try:\n            yield\n        except:\n            await self.send_message('Sorry!')\n\n    # synchronous handler for all updates: log message\n    @handler()\n    def log_update_id(self, event: Event) -\u003e Literal[True]:\n        logging.info(event['update_id'])\n        return True  # call following handlers\n\n    # async handler to check if user is admin for update with messages and cb\n    @handler(lambda event: 'message' in event or 'callback_query' in event)\n    async def check_admin(self, event: Event) -\u003e bool:\n        # the next handler will be called only if this returns True\n        return event['message']['from_user']['id'] in await self.get_admins()\n \n    # async handler to echo updates containing a message\n    @handler(lambda event: 'message' in event and 'text' in event['message'])\n    async def echo_message(self, event: Event) -\u003e None:\n        # if the update contains a message then echo it\n        await self.reply_to_message(text=event['message']['text'])\n\n    # async handler to answer a callback query\n    @handler(lambda event: 'callback_query' in event)\n    async def echo_message(self, event: Event) -\u003e None:\n        # if the update does not contain a message but a callback query, answer\n        await self.answer_callback_query(event['callback_query']['id'])\n```\n\n## What do the examples demonstrate?\n\n`handler` decorates methods of `EventsProcessor` subclasses. The method can be\n    one of: async function (like `check_admin`, `handle_message` and\n    `echo_message` in the example above), sync function (`log_update_id`), async\n    context manager (`send_excuse_message`) or sync context manager\n    (`log_exception`).\n\nAll of the handlers are called in the same order as they are declared in the\n    class body. Middlewares follow the same rule: they are entered in the order\n    of declaration and exited in the reversed order (in a recursive manner).\n\nSync and async handlers may return a value: if it is not a truthy value then\n    none of the following handlers will be called and event processing will be\n    stopped at the handler which **did not** return truthy value.\n\nPlease notice: if you return nothing from the sync/async handler method (means\n    you implicitly `return None`) then none of the following handlers will be\n    called. This is an intended default behavior since usually an event requires\n    a single handler. None is a falsy (not truthy) value.\n\nReturning `True` from the handler is useful for logging purposes: the logging\n    method should not block further processing of the event. This is shown in\n    the example below (`log_update_id`) as well as the filtering use case for\n    admins: if the user is not an admin then `check_admin` will return `False`\n    and no further processing will be done.\n\nMiddlewares are based on context managers and are intended to be used for\n    exceptions handling. Also use them when some related actions are required\n    before and after the event is processed by other handlers: for example, for\n    measuring the execution time.\n\n### Recap on examples\n\nLet's sum up on the [examples](#examples):\n1. `aioevproc` supports both sync and async handlers and middlewares.\n2. Every handler or middleware has to be a method of `EventsProcessor` subclass.\n3. If the handler does not return a truthy value then the following handlers are\n    not called.\n4. Middlewares are sync/async context managers.\n5. Handlers and middlewares are called in the same order as they are declared.\n\n## How to use the handlers conditions\n\nHandler usually has to be applied to certain types of events, not all. The\n    following handler will be applied only to updates containing a message:\n```python\n@handler(lambda event: 'message' in event)\nasync def handle_event(self, event: Event) -\u003e None:\n    pass\n```\n\nIf the condition check fails then the next handler condition will be checked:\n```python\n@handler(lambda event: False)\ndef always_skipped(self, event: Event) -\u003e Literal[False]:\n    # this handler is never called since its predicate always evaluates to False\n    return False  # has no effect since this handler is not called\n\n# since previous handler condition check failed this one will be checked next\n@handler(lambda event: 'edited_message' in event)\ndef log_message_edit(self, event: Event) -\u003e None:\n    pass\n```\n\nPlease notice: if the handler condition check failed then the handler's return\n    value does not affect the next handlers. The return value of the handler\n    affects the next handlers only if the handler itself is called (meaning \n    that its condition check is passed).\n\nYou can specify multiple predicates in a `handler` call: this will make handler\n    to be called only if **all** of the predicates evaluate to a truthy value\n    for the event. Example below shows the handers which will be applied only to\n    updates with text messages:\n```python\n@handler(\n        lambda event: 'message' in event,\n        lambda event: 'text' in event['message'],\n)\nasync def handle_event(self, event: Event) -\u003e None:\n    pass\n```\n\nThe predicates are evaluated in the same order as they are declared. So the\n    above pair of conditions is equivalent to\n    `'message' in event and 'text' in event['message']`. This means that\n    specifying multiple predicates for a single `handler` call implements AND\n    semantics (conditions conjunction).\n\nIf you need to apply single handler if **any** of the conditions is true, use\n    multiple `handler` calls:\n```python\n@handler(lambda event: 'message' in event)\n@handler(lambda event: 'callback_query' in event)\nasync def handle_event(self, event: Event) -\u003e None:\n    pass\n```\n\nThis will apply the handler for either update with a message or update with a\n    callback query. This form implements OR semantics (conditions disjunction).\n\nPlease notice: the implementation of `aioevproc` checks handlers predicates in\n    the same order as they are declared. First `'message' in event` will be\n    checked and after that the `'callback_query' in event` predicate will be\n    evaluated. This is a reversed order to how Python applies decorators: Python\n    applies the most inner decorator first. But `aioevproc` applies the most\n    outer `handler` call first since it is more intuitive.\n\nIf you need a handler to be applied unconditionally then use just `handler()`\n    without arguments.\n\nPlease notice: you cannot use `handler()` without arguments on a handler with\n    any other `handler` call with arguments since this has no sense:\n```python\n@handler()  # will raise an AssertionError\n@handler(lambda event: 'message' in event)\nasync def handle_event(self, event: Event) -\u003e None:\n    pass\n```\n\nDon't forget to `return True` from unconditionally applied handler to not ignore\n    all of the following handlers!\n\n### Recap on conditions\n\nLet's sum up on [conditions](#how-to-use-the-handlers-conditions):\n1. Single `handler` call accepts multiple predicates as arguments. The handler\n    then will be called only if **all** of the predicates are true (AND semantics).\n2. If a handler method (or middleware) is decorated with multiple `handler`\n    calls then the handler will be called if **any** of the `handler`s'\n    conditions is true (OR semantics).\n3. OR and AND semantics can be combined.\n4. If the handler's conditions check failed then the handler is skipped and the\n    next handlers' conditions are checked until the matching handler is found.\n5. All the conditions are checked in the same order as they are declared. The\n    most outer `handler` decorator is applied first.\n6. Handler decorated with `handler()` w/o arguments is applied unconditionally.\n\n## Installation\n\n`pip install aioevproc`\n\n## How to run tests\n\nFrom project root directory: `python -m unittest discover -s tests/unit`\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbryzgaloff%2Faioevproc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbryzgaloff%2Faioevproc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbryzgaloff%2Faioevproc/lists"}