{"id":15044030,"url":"https://github.com/mdapena/pyventus","last_synced_at":"2025-04-06T04:06:52.009Z","repository":{"id":212840054,"uuid":"714718289","full_name":"mdapena/pyventus","owner":"mdapena","description":"A powerful Python library for event-driven and reactive programming.","archived":false,"fork":false,"pushed_at":"2025-01-10T14:50:39.000Z","size":6500,"stargazers_count":54,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-30T03:02:50.039Z","etag":null,"topics":["asynchronous-programming","asyncio","celery","event-driven-programming","event-emitters","events","fastapi","observables","reactive-programming","redis-queue"],"latest_commit_sha":null,"homepage":"https://mdapena.github.io/pyventus/","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/mdapena.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-05T17:13:50.000Z","updated_at":"2025-03-29T14:06:07.000Z","dependencies_parsed_at":"2024-03-24T20:21:14.135Z","dependency_job_id":"a1994d47-c640-4d21-ad75-ff33f9a9cdd5","html_url":"https://github.com/mdapena/pyventus","commit_stats":{"total_commits":197,"total_committers":2,"mean_commits":98.5,"dds":"0.29949238578680204","last_synced_commit":"2ab1605332c1d96d6bd01f7a42510e184bd5403c"},"previous_names":["mdapena/pyventus"],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdapena%2Fpyventus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdapena%2Fpyventus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdapena%2Fpyventus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mdapena%2Fpyventus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mdapena","download_url":"https://codeload.github.com/mdapena/pyventus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247430864,"owners_count":20937874,"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":["asynchronous-programming","asyncio","celery","event-driven-programming","event-emitters","events","fastapi","observables","reactive-programming","redis-queue"],"created_at":"2024-09-24T20:49:58.707Z","updated_at":"2025-04-06T04:06:51.989Z","avatar_url":"https://github.com/mdapena.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n\u003cbr\u003e\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://raw.githubusercontent.com/mdapena/pyventus/refs/heads/master/docs/images/logo/pyventus-logo-name-slogan.svg\" alt=\"Pyventus\" width=\"750px\"\u003e\n\u003c/p\u003e\n\u003cbr\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/mdapena/pyventus/actions?query=workflow%3ATests+event%3Apush+branch%3Amaster\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://github.com/mdapena/pyventus/actions/workflows/run-tests.yml/badge.svg?branch=master\" alt=\"Tests\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/mdapena/pyventus/actions?query=workflow%3ADocs+event%3Apush+branch%3Amaster\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://github.com/mdapena/pyventus/actions/workflows/deploy-docs.yml/badge.svg?branch=master\" alt=\"Docs\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://coveralls.io/github/mdapena/pyventus?branch=master\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://coveralls.io/repos/github/mdapena/pyventus/badge.svg?branch=master\" alt=\"Coverage Status\"/\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://pypi.org/project/pyventus\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/pypi/v/pyventus?color=0097a8\" alt=\"Package Version\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://pypi.org/project/pyventus\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/pypi/pyversions/pyventus?color=0097a8\" alt=\"Supported Python Versions\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://pypi.org/project/pyventus\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/pypi/dm/pyventus.svg?color=0097a8\" alt=\"Monthly Downloads\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n---\n\n**Documentation**: \u003ca href=\"https://mdapena.github.io/pyventus\" target=\"_blank\"\u003ehttps://mdapena.github.io/pyventus\u003c/a\u003e\n\n**Source Code**: \u003ca href=\"https://github.com/mdapena/pyventus\" target=\"_blank\"\u003ehttps://github.com/mdapena/pyventus\u003c/a\u003e\n\n---\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;Pyventus is a powerful Python library for event-driven and reactive programming, designed to simplify the development of asynchronous and event-based applications in Python.\n\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n## Key Features\n\n\u003cp style=\"text-align: justify;\"\u003e\n    Pyventus offers several key features, such as:\n\u003c/p\u003e\n\n\u003cul style=\"text-align: justify;\"\u003e\n\n\u003cli\u003e\u003cb\u003eEvent-Driven \u0026 Reactive Programming\u003c/b\u003e ─\nWhether you opt for an event-driven design or a reactive approach, Pyventus lets you select the paradigm that best fits your architecture.\n\u003c/li\u003e\n\n\u003cli\u003e\u003cb\u003eHigh Performance\u003c/b\u003e ─\nPyventus is designed from the ground up with a focus on efficiency, taking into account optimizations for time complexity, memory usage, and Python-specific features.\n\u003c/li\u003e\n\n\u003cli\u003e\u003cb\u003eSync and Async Support\u003c/b\u003e ─\nWhether your code is synchronous or asynchronous, Pyventus allows you to seamlessly work with both sync and async callables, as well as access its API from both contexts.\n\u003c/li\u003e\n\n\u003cli\u003e\u003cb\u003eReliable Asynchronous Processing\u003c/b\u003e ─\nWith Pyventus, you have full control over your asynchronous workflows, allowing you to customize how they are processed upon completion, whether they succeed or encounter errors.\n\u003c/li\u003e\n\n\u003cli\u003e\u003cb\u003eComprehensive Documentation\u003c/b\u003e ─\nPyventus offers a comprehensive documentation suite that includes API references, usage examples, and tutorials to effectively leverage all the features and capabilities of the library.\n\u003c/li\u003e\n\n\u003cli\u003e\u003cb\u003eIntuitive \u0026 User-Friendly API\u003c/b\u003e ─\nPyventus provides a user-friendly API that simplifies the process of working with event-driven and reactive paradigms, enabling you to organize your code around discrete actions and their responses.\n\u003c/li\u003e\n\n\u003c/ul\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n## Quick Start\n\n\u003cp style=\"text-align: justify;\"\u003e\n \u0026emsp;\u0026emsp;Pyventus is published as a \u003ca href=\"https://pypi.org/project/pyventus/\" target=\"_blank\"\u003ePython package\u003c/a\u003e and can be installed using \u003ccode\u003epip\u003c/code\u003e, ideally in a \u003ca href=\"https://realpython.com/python-virtual-environments-a-primer/\" target=\"_blank\"\u003evirtual environment\u003c/a\u003e for proper dependency isolation. To get started, open up a terminal and install Pyventus with the following command:\n\u003c/p\u003e\n\n```console\npip install pyventus\n```\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;Pyventus by default relies on the Python standard library and \u003cb\u003erequires Python 3.10 or higher\u003c/b\u003e with no additional dependencies aside from \u003ca href=\"https://pypi.org/project/typing-extensions/\" target=\"_blank\"\u003e\u003ccode\u003etyping-extensions\u003c/code\u003e\u003c/a\u003e, which is primarily used to support advanced typing features in older versions of Python. However, this package also includes alternative integrations to access additional features such as asynchronous processing with Redis Queue and Celery. For more information on this matter, please refer to the \u003ca href=\"https://mdapena.github.io/pyventus/0.7/getting-started/#optional-dependencies\"\u003eOptional Dependencies\u003c/a\u003e section.\n\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n## Basic Usage\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;Let’s begin with some introductory examples that will not only illustrate the core concepts and basic usage of the library but also provide a foundation for more complex scenarios.\n\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n### A Simple Event-Driven Example\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;Starting with the event-driven paradigm, let's explore the capabilities of Pyventus through a simple event-based \u003ccode\u003eHello, World!\u003c/code\u003e example, where you will learn how to subscribe to events and emit them within your application.\n\u003c/p\u003e\n\n```Python title=\"Hello, World! Example\" linenums=\"1\"\nfrom pyventus.events import AsyncIOEventEmitter, EventEmitter, EventLinker\n\n\n@EventLinker.on(\"GreetEvent\")\ndef handle_greet_event():\n    print(\"Hello, World!\")\n\n\nevent_emitter: EventEmitter = AsyncIOEventEmitter()\nevent_emitter.emit(\"GreetEvent\")\n```\n\n\u003cdetails markdown=\"1\" class=\"tip\"\u003e\n\u003csummary\u003eYou can also work with \u003ccode\u003easync\u003c/code\u003e functions and contexts...\u003c/summary\u003e\n\n```Python title=\"Hello, World! Example (Async version)\" linenums=\"1\" hl_lines=\"5\"\nfrom pyventus.events import AsyncIOEventEmitter, EventEmitter, EventLinker\n\n\n@EventLinker.on(\"GreetEvent\")\nasync def handle_greet_event():\n    print(\"Hello, World!\")\n\n\nevent_emitter: EventEmitter = AsyncIOEventEmitter()\nevent_emitter.emit(\"GreetEvent\")\n```\n\n\u003c/details\u003e\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;As we can see from the \u003ccode\u003eHello, World!\u003c/code\u003e example, Pyventus follows a simple and intuitive workflow for defining and emitting events. Let’s recap the essential steps involved:\n\u003c/p\u003e\n\n\u003col style=\"text-align: justify;\"\u003e\n\u003cli\u003e\n\u003cb\u003eImporting Necessary Components:\u003c/b\u003e\nWe first imported the required components from the \u003ccode\u003eevents\u003c/code\u003e module of Pyventus, which included the \u003ccode\u003eEventLinker\u003c/code\u003e, the \u003ccode\u003eEventEmitter\u003c/code\u003e, and the \u003ccode\u003eAsyncIOEventEmitter\u003c/code\u003e factory method.\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cb\u003eLinking Events to Callbacks:\u003c/b\u003e\nNext, we used the \u003ccode\u003e@EventLinker.on()\u003c/code\u003e decorator to define and link the string event \u003ccode\u003eGreetEvent\u003c/code\u003e to the function \u003ccode\u003ehandle_greet_event()\u003c/code\u003e, which will print \u003ci\u003e\"Hello, World!\"\u003c/i\u003e to the console whenever the \u003ccode\u003eGreetEvent\u003c/code\u003e is emitted.\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cb\u003eInstantiating an Event Emitter:\u003c/b\u003e\nAfter that, and in order to trigger our event, we used the \u003ccode\u003eAsyncIOEventEmitter\u003c/code\u003e factory method to create an instance of the event emitter class, which in this case is preconfigured with the \u003ccode\u003eAsyncIOProcessingService\u003c/code\u003e.\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cb\u003eTriggering the Event:\u003c/b\u003e\nFinally, by using the \u003ccode\u003eemit()\u003c/code\u003e method of the event emitter instance, we triggered the \u003ccode\u003eGreetEvent\u003c/code\u003e, resulting in the execution of the \u003ccode\u003ehandle_greet_event()\u003c/code\u003e callback.\n\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;Having gained a clear understanding of the workflow showcased in the \u003ccode\u003eHello, World!\u003c/code\u003e example, you are now well-equipped to explore more intricate event-driven scenarios and fully harness the capabilities of Pyventus in your own projects. For a deep dive into the package's functionalities, you can refer to the API and Learn sections.\n\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n### A Simple Reactive Example\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;Now, let's take a look at the capabilities of Pyventus within the reactive paradigm through a simple example, where you will not only learn how to define observables and stream data over time, but also how to subscribe to them.\n\u003c/p\u003e\n\n```Python title=\"Simple Counter Example\" linenums=\"1\"\nfrom pyventus.reactive import as_observable_task, Completed\n\n\n@as_observable_task\ndef simple_counter(stop: int):\n    for count in range(1, stop + 1):\n        yield count\n    raise Completed\n\n\nobs = simple_counter(stop=16)\nobs.subscribe(\n    next_callback=lambda val: print(f\"Received: {val}\"),\n    complete_callback=lambda: print(\"Done!\"),\n)\nobs()\n```\n\n\u003cdetails markdown=\"1\" class=\"tip\"\u003e\n\u003csummary\u003eYou can also work with \u003ccode\u003easync\u003c/code\u003e functions and contexts...\u003c/summary\u003e\n\n```Python title=\"Simple Counter Example (Async version)\" linenums=\"1\" hl_lines=\"5\"\nfrom pyventus.reactive import as_observable_task, Completed\n\n\n@as_observable_task\nasync def simple_counter(stop: int):\n    for count in range(1, stop + 1):\n        yield count\n    raise Completed\n\n\nobs = simple_counter(stop=16)\nobs.subscribe(\n    next_callback=lambda val: print(f\"Received: {val}\"),\n    complete_callback=lambda: print(\"All done!\"),\n)\nobs()\n```\n\n\u003c/details\u003e\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;As shown in the \u003ccode\u003eSimple Counter\u003c/code\u003e example, Pyventus follows a simple and intuitive workflow for defining observables and streaming data to subscribers. Let’s recap the essential steps involved:\n\u003c/p\u003e\n\n\u003col style=\"text-align: justify;\"\u003e\n\u003cli\u003e\n\u003cb\u003eImporting Necessary Components:\u003c/b\u003e\nWe first imported the required components from the \u003ccode\u003ereactive\u003c/code\u003e module of Pyventus, which included the \u003ccode\u003e@as_observable_task\u003c/code\u003e decorator and the \u003ccode\u003eCompleted\u003c/code\u003e signal.\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cb\u003eDefining Observables:\u003c/b\u003e\nAfter that, and using the \u003ccode\u003e@as_observable_task\u003c/code\u003e decorator in conjunction with the \u003ccode\u003esimple_counter()\u003c/code\u003e function, we defined our observable task, which, once executed, will yield a count from one up to the specified number and raise a \u003ccode\u003eCompleted\u003c/code\u003e signal when done.\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cb\u003eInstantiating Observables:\u003c/b\u003e\nThen, we called the \u003ccode\u003esimple_counter()\u003c/code\u003e function to instantiate the observable task, so that we can subscribe to it and control its execution as needed.\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cb\u003eSubscribing to Observables:\u003c/b\u003e\nNext, we added a subscriber to the observable instance by calling its \u003ccode\u003esubscribe()\u003c/code\u003e method and passing the corresponding callbacks. In this case, we used two lambda functions: one to print the received values and another to indicate when the observable has completed emitting values.\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cb\u003eExecuting Observables:\u003c/b\u003e\nFinally, and in order to initiate the execution of the observable, we called its instance, which resulted in the execution of the \u003ccode\u003esimple_counter()\u003c/code\u003e function and the streaming of its results.\n\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;With a clear understanding of the workflow showcased in the \u003ccode\u003eSimple Counter\u003c/code\u003e example, you are now well-equipped to explore more intricate reactive scenarios and fully harness the capabilities of Pyventus in your own projects. For a deep dive into the package's functionalities, you can refer to the API and Learn sections.\n\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n## Practical Examples\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;To truly see Pyventus in action, let’s explore some practical examples that will not only illustrate specific use cases of the library but also showcase its various features and demonstrate how to use them effectively.\n\u003c/p\u003e\n\n### Dynamic Voltage Monitoring: An Event-Driven Perspective\n\n\u003cp style=\"text-align: center;\"\u003e\n    \u003ca href=\"https://unsplash.com/photos/macro-photography-of-black-circuit-board-FO7JIlwjOtU?utm_content=creditShareLink\u0026utm_medium=referral\u0026utm_source=unsplash\" target=\"_blank\"\u003e\n        \u003cimg style=\"border-radius: 0.5rem;\" src=\"https://raw.githubusercontent.com/mdapena/pyventus/refs/heads/master/docs/images/examples/black-circuit-board.jpg\" alt=\"Macro photography of a black circuit board illustrating a voltage sensor in action.\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;A common aspect found in many systems is the need to monitor and respond to changes in sensor data. Whether it involves pressure, temperature, or voltage, capturing and reacting accordingly to sensor readings is crucial for any related process.\n\u003c/p\u003e\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;In this practical example, we will focus on voltage sensors, where timely detection of low or high voltage conditions can prevent equipment damage and ensure system reliability. However, designing a sensor architecture that is both easy to extend and flexible can be challenging, especially if we want users to simply attach their logic without needing to understand or modify the underlying implementation. This complexity also increases if we aim for an architecture that enables a proper separation of concerns.\n\u003c/p\u003e\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;One way to effectively address this challenge is by implementing an event-driven architecture, where each voltage sensor encapsulates its own logic for reading values and only exposes a series of events that users can utilize to attach their domain logic. To translate this into code, we will define a \u003ccode\u003eVoltageSensor\u003c/code\u003e class that reads voltage levels and emits events based on predefined thresholds using Pyventus. The code below illustrates the implementation of this use case.\n\u003c/p\u003e\n\n```Python title=\"Dynamic Voltage Monitoring (Event-Driven Implementation)\" linenums=\"1\" hl_lines=\"4 9 14 27-30 35-36 41-42 47-48 55\"\nimport asyncio\nimport random\n\nfrom pyventus.events import AsyncIOEventEmitter, EventEmitter, EventLinker\n\n\nclass VoltageSensor:\n\n    def __init__(self, name: str, low: float, high: float, event_emitter: EventEmitter) -\u003e None:\n        # Initialize the VoltageSensor object with the provided parameters\n        self._name: str = name\n        self._low: float = low\n        self._high: float = high\n        self._event_emitter: EventEmitter = event_emitter\n\n    async def __call__(self) -\u003e None:\n        # Start voltage readings for the sensor\n        print(f\"Starting voltage readings for: {self._name}\")\n        print(f\"Low: {self._low:.3g}v | High: {self._high:.3g}v\\n-----------\\n\")\n\n        while True:\n            # Simulate sensor readings\n            voltage: float = random.uniform(0, 5)\n            print(\"\\tSensor Reading:\", \"\\033[32m\", f\"{voltage:.3g}v\", \"\\033[0m\")\n\n            # Emit events based on voltage readings\n            if voltage \u003c self._low:\n                self._event_emitter.emit(\"LowVoltageEvent\", sensor=self._name, voltage=voltage)\n            elif voltage \u003e self._high:\n                self._event_emitter.emit(\"HighVoltageEvent\", sensor=self._name, voltage=voltage)\n\n            await asyncio.sleep(1)\n\n\n@EventLinker.on(\"LowVoltageEvent\")\ndef handle_low_voltage_event(sensor: str, voltage: float):\n    print(f\"🪫 Starting low-voltage protection for '{sensor}'. ({voltage:.3g}v)\\n\")\n    # Perform action for low voltage...\n\n\n@EventLinker.on(\"HighVoltageEvent\")\ndef handle_high_voltage_event(sensor: str, voltage: float):\n    print(f\"⚡ Starting high-voltage protection for '{sensor}'. ({voltage:.3g}v)\\n\")\n    # Perform action for high voltage...\n\n\n@EventLinker.on(\"LowVoltageEvent\", \"HighVoltageEvent\")\nasync def handle_voltage_event(sensor: str, voltage: float):\n    print(f\"\\033[31m\\nSensor '{sensor}' out of range.\\033[0m (Voltage: {voltage:.3g})\")\n    # Perform notification for out of range voltage...\n\n\nasync def main():\n    # Initialize the sensor and run the sensor readings\n    sensor = VoltageSensor(name=\"CarSensor\", low=0.5, high=3.9, event_emitter=AsyncIOEventEmitter())\n    await asyncio.gather(sensor(), )  # Add new sensors inside the 'gather' for multi-device monitoring\n\n\nasyncio.run(main())\n```\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n### Non-Blocking HTTP Fetcher: A Responsive Approach\n\n\u003cp style=\"text-align: center;\"\u003e\n    \u003ca href=\"https://unsplash.com/photos/a-rack-of-electronic-equipment-in-a-dark-room-OnI_TNcIv9U\" target=\"_blank\"\u003e\n        \u003cimg style=\"border-radius: 0.5rem;\" src=\"https://raw.githubusercontent.com/mdapena/pyventus/refs/heads/master/docs/images/examples/rack-of-electronic-equipment.jpg\" alt=\"A set of interconnected electronic devices in a dark room.\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;In today's interconnected world, retrieving information from the network is essential for many applications. Whether through WebSocket connections, RESTful APIs, or HTTP requests, these methods facilitate vital data exchange. However, blocking network retrievals can severely impact user experience, making it imperative for applications to remain responsive.\n\u003c/p\u003e\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;In this practical example, we will explore the design and implementation of a non-blocking HTTP getter function, along with its integration into a console-style application that mimics the behavior of a simplified web browser. While there are various mechanisms for implementing non-blocking HTTP fetchers, such as Python threads or the asyncio library, we will leverage the reactive paradigm of Pyventus due to its readability, declarative style, and ease of implementation.\n\u003c/p\u003e\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;To accomplish this, we will first define a basic blocking HTTP function called \u003ccode\u003ehttp_get()\u003c/code\u003e. This function will then be transformed into an observable using the \u003ccode\u003e@as_observable_task\u003c/code\u003e decorator from Pyventus, allowing us to attach subscribers for result notifications. Finally, we will utilize the \u003ccode\u003eThreadPoolExecutor\u003c/code\u003e for concurrent execution of the observables, enabling us to handle multiple requests seamlessly while maintaining an interactive user experience.\n\u003c/p\u003e\n\n```Python title=\"Non-Blocking HTTP Fetcher (Reactive Implementation)\" linenums=\"1\" hl_lines=\"5 8-9 51 54 56-57 61-62 66\"\nfrom concurrent.futures import ThreadPoolExecutor\nfrom http.client import HTTPConnection, HTTPException, HTTPResponse, HTTPSConnection\nfrom urllib.parse import ParseResult, urlparse\n\nfrom pyventus.reactive import as_observable_task\n\n\n@as_observable_task\ndef http_get(url: str) -\u003e str:\n    \"\"\"Perform an HTTP GET request and return the response data.\"\"\"\n    parsed_url: ParseResult = urlparse(url)  # Parse the URL\n\n    # Create a connection based on the URL scheme (HTTP or HTTPS)\n    if parsed_url.scheme == \"https\":\n        connection: HTTPConnection = HTTPSConnection(parsed_url.netloc)\n    else:\n        connection: HTTPConnection = HTTPConnection(parsed_url.netloc)\n\n    # Send the request, retrieve the response, and close the connection\n    connection.request(\"GET\", parsed_url.path)\n    response: HTTPResponse = connection.getresponse()\n    data: str = response.read().decode()\n    connection.close()\n\n    # Raise an exception for HTTP errors; otherwise, return the response\n    if response.status \u003e= 400:\n        raise HTTPException(response.status, data)\n    return data\n\n\ndef main():\n    print(\n        \"🌐  Welcome to the Reactive HTTP Fetcher!\\n\\n💡  Try searching for:\\n\"\n        \"    1. - https://httpbin.org/get\\n\"\n        \"    2. - https://httpbin.org/uuid\\n\"\n        \"    3. - https://httpbin.org/ip\\n\"\n        \"    4. - https://httpbin.org/404\"\n    )\n\n    prompt = \"\\n🔗  Enter the URL (Type '\\033[36mexit\\033[0m' to quit): \"\n    metrics = {\"success_count\": 0, \"error_count\": 0}  # Initialize metrics\n    executor = ThreadPoolExecutor()  # Create a thread pool for concurrent execution\n\n    while True:\n        # Get user input\n        url = input(prompt)\n        if url.lower() == \"exit\":\n            break\n\n        # Call the HTTP function, which returns an observable\n        obs = http_get(url)\n\n        # Subscribe to the observable using a subscription context\n        with obs.subscribe() as subctx:\n\n            @subctx.on_next\n            def next(result: str) -\u003e None:\n                metrics[\"success_count\"] += 1  # Increment success count\n                print(f\"\\r{' ' * len(prompt)}\\r✅  HTTPResponse: {result!r}\\n\", end=f\"{prompt}\")\n\n            @subctx.on_error\n            def error(error: Exception) -\u003e None:\n                metrics[\"error_count\"] += 1  # Increment error count\n                print(f\"\\r{' ' * len(prompt)}\\r⚠️   HTTPException: {error!r}\\n\", end=f\"{prompt}\")\n\n        obs(executor)  # Execute the observable with the thread pool\n        print(f\"🔍  Requested URL: \\033[32m{url!r}\\033[0m \\n⏳  Fetching data... \")\n\n    # Shutdown the executor\n    executor.shutdown()\n\n    # Print summary of requests\n    print(\n        f\"\\n🎯  Summary:\\n\"\n        f\"    - Total Requests: {metrics['success_count'] + metrics['error_count']}\\n\"\n        f\"    - Successful Requests: \\033[32m{metrics['success_count']}\\033[0m\\n\"\n        f\"    - Error Requests: \\033[31m{metrics['error_count']}\\033[0m\"\n    )\n\n\nmain()\n```\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n## Event-Driven Programming: Key Highlights of Pyventus\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;Alongside the standard functionalities of event-driven programming, Pyventus also introduces some unique aspects that set it apart from other implementations. In this section, we will cover some of these key features and how to use them effectively.\n\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#ep-event-objects\" name=\"ep-event-objects\" title=\"Permanent link\"\u003eEvent Objects\u003c/a\u003e ─ \n        Besides supporting string-based events, as we've seen in previous examples, Pyventus also supports Event Objects, which provide a structured way to define events and encapsulate relevant data payloads.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"1-2 7-8 16-19\"\n    @dataclass  # Define a Python dataclass to represent an event and its payload.\n    class OrderCreatedEvent:\n        order_id: int\n        payload: dict\n\n\n    @EventLinker.on(OrderCreatedEvent)  # Use the event class to attach subscribers.\n    def handle_order_created_event(event: OrderCreatedEvent):\n        # The event instance is automatically passed as the first argument.\n        # In methods with self or cls, the event is passed after those arguments.\n        print(f\"Event Object: {event}\")\n\n\n    event_emitter: EventEmitter = AsyncIOEventEmitter()\n    event_emitter.emit(\n        event=OrderCreatedEvent(  # Emit an instance of the event!\n            order_id=6452879,\n            payload={},\n        ),\n    )\n    ```\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#ep-global-events\" name=\"ep-global-events\" title=\"Permanent link\"\u003eGlobal Events\u003c/a\u003e ─ \n        In addition to Event Objects and string-based events, Pyventus also provides support for global events, which are particularly useful for implementing cross-cutting concerns such as logging, monitoring, and analytics. By subscribing event callbacks to \u003ccode\u003e...\u003c/code\u003e, you can capture all events that may occur within that specific \u003ccode\u003eEventLinker\u003c/code\u003e context.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"1-2\"\n    @EventLinker.on(...)\n    def logging(*args, **kwargs):\n        print(f\"Logging:\\n- Args: {args}\\n- Kwargs: {kwargs}\")\n\n\n    event_emitter: EventEmitter = AsyncIOEventEmitter()\n    event_emitter.emit(\"AnyEvent\", name=\"Pyventus\")\n    ```\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#ep-success-and-error-handling\" name=\"ep-success-and-error-handling\" title=\"Permanent link\"\u003eSuccess and Error Handling\u003c/a\u003e ─ \n        With Pyventus, you can customize how events are handled upon completion, whether they succeed or encounter errors. This customization is achieved through the configuration of the success and failure callbacks in the event workflow definition, which is done during the subscription process.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"4 6-7 10-11 14-15\"\n    from pyventus.events import AsyncIOEventEmitter, EventEmitter, EventLinker\n\n    # Create a subscription context for the \"DivisionEvent\" event\n    with EventLinker.on(\"DivisionEvent\") as subctx:\n\n        @subctx.on_event\n        def divide(a: float, b: float) -\u003e float:\n            return a / b\n\n        @subctx.on_success\n        def handle_success(result: float) -\u003e None:\n            print(f\"Division result: {result:.3g}\")\n\n        @subctx.on_failure\n        def handle_failure(e: Exception) -\u003e None:\n            print(f\"Oops, something went wrong: {e}\")\n\n\n    event_emitter: EventEmitter = AsyncIOEventEmitter()  # Create an event emitter\n    event_emitter.emit(\"DivisionEvent\", a=1, b=0)  # Example: Division by zero\n    event_emitter.emit(\"DivisionEvent\", a=1, b=2)  # Example: Valid division\n    ```\n\n    \u003cdetails markdown=\"1\" class=\"tip\"\u003e\n    \u003csummary\u003eYou can also set up your callbacks using the \u003ccode\u003esubscribe()\u003c/code\u003e method...\u003c/summary\u003e\n\n    \u003cp style=\"text-align: justify;\"\u003e\n        \u0026emsp;\u0026emsp;Alternatively, for more straightforward definitions, such as lambda functions, or when you have existing functions defined elsewhere in your code, you can utilize the \u003ccode\u003esubscribe()\u003c/code\u003e method to set up these callbacks.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"3-8\"\n    from pyventus.events import AsyncIOEventEmitter, EventEmitter, EventLinker\n\n    EventLinker.subscribe(\n        \"DivisionEvent\",\n        event_callback=lambda a, b: a / b,\n        success_callback=lambda result: print(f\"Division result: {result:.3g}\"),\n        failure_callback=lambda e: print(f\"Oops, something went wrong: {e}\"),\n    )\n\n    event_emitter: EventEmitter = AsyncIOEventEmitter()  # Create an event emitter\n    event_emitter.emit(\"DivisionEvent\", a=1, b=0)  # Example: Division by zero\n    event_emitter.emit(\"DivisionEvent\", a=1, b=2)  # Example: Valid division\n    ```\n\n    \u003c/details\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#ep-sync-and-async-support\" name=\"ep-sync-and-async-support\" title=\"Permanent link\"\u003eSync and Async Support\u003c/a\u003e ─ \n        Pyventus is designed from the ground up to seamlessly support both synchronous and asynchronous programming models. Its unified sync/async API allows you to define event callbacks as either \u003ccode\u003esync\u003c/code\u003e or \u003ccode\u003easync\u003c/code\u003e callables, as well as emit events from both contexts.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"2 7\"\n    @EventLinker.on(\"MyEvent\")\n    def sync_event_callback():\n        pass  # Synchronous event handling\n\n\n    @EventLinker.on(\"MyEvent\")\n    async def async_event_callback():\n        pass  # Asynchronous event handling\n    ```\n\n    \u003cdetails markdown=\"1\" class=\"tip\"\u003e\n    \u003csummary\u003eYou can optimize the execution of your callbacks based on their workload...\u003c/summary\u003e\n\n    \u003cp style=\"text-align: justify;\"\u003e\n        \u0026emsp;\u0026emsp;By default, event subscribers in Pyventus are executed concurrently during an event emission, running their \u003ccode\u003esync\u003c/code\u003e and \u003ccode\u003easync\u003c/code\u003e callbacks as defined. However, if you have a \u003ccode\u003esync\u003c/code\u003e callback that involves I/O or non-CPU bound operations, you can enable the \u003ccode\u003eforce_async\u003c/code\u003e parameter to offload it to a thread pool, ensuring optimal performance and responsiveness. The offloading process is handled by the \u003ca href=\"https://docs.python.org/3/library/asyncio-task.html#running-in-threads\" target=\"_blank\"\u003e\u003ccode\u003easyncio.to_thread()\u003c/code\u003e\u003c/a\u003e function.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"1\"\n    @EventLinker.on(\"BlockingIO\", force_async=True)\n    def blocking_io():\n        print(f\"start blocking_io at {time.strftime('%X')}\")\n        # Note that time.sleep() can be replaced with any blocking\n        # IO-bound operation, such as file operations.\n        time.sleep(1)\n        print(f\"blocking_io complete at {time.strftime('%X')}\")\n    ```\n\n    \u003c/details\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"3 8\"\n    def sync_function(event_emitter: EventEmitter):\n        # Emitting events from sync functions\n        event_emitter.emit(\"MyEvent\")\n\n\n    async def async_function(event_emitter: EventEmitter):\n        # Emitting events from async functions\n        event_emitter.emit(\"MyEvent\")\n    ```\n\n    \u003cdetails markdown=\"1\" class=\"info\"\u003e\n    \u003csummary\u003eConsiderations on the processing of event emissions...\u003c/summary\u003e\n    \u003cp style=\"text-align: justify;\"\u003e\n        \u0026emsp;\u0026emsp;It's important to note that, while Pyventus provides a unified sync/async API, the processing of each event emission will depend on the concrete implementation of the \u003ccode\u003eProcessingService\u003c/code\u003e used in the event emitter. For example, an event emitter configured with the \u003ccode\u003eAsyncIOProcessingService\u003c/code\u003e will leverage the \u003ccode\u003eAsyncIO\u003c/code\u003e framework to handle the execution of the event emission, whereas other implementations may structure their propagation differently.\n    \u003c/p\u003e\n    \u003c/details\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#ep-runtime-flexibility\" name=\"ep-runtime-flexibility\" title=\"Permanent link\"\u003eRuntime Flexibility\u003c/a\u003e ─ \n        At its core, Pyventus utilizes a modular event emitter design that, along with the \u003ccode\u003eEventLinker\u003c/code\u003e, allows you to change the event emitter at runtime without needing to reconfigure all subscriptions or apply complex logic.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"11-12 17-18\"\n    from concurrent.futures import ThreadPoolExecutor\n\n    from pyventus.events import AsyncIOEventEmitter, EventEmitter, EventLinker, ExecutorEventEmitter\n\n\n    @EventLinker.on(\"Event\")\n    def handle_event(msg: str):\n        print(msg)\n\n\n    def main(event_emitter: EventEmitter) -\u003e None:\n        event_emitter.emit(\"Event\", msg=f\"{event_emitter}\")\n\n\n    if __name__ == \"__main__\":\n        executor = ThreadPoolExecutor()\n        main(event_emitter=AsyncIOEventEmitter())\n        main(event_emitter=ExecutorEventEmitter(executor))\n        executor.shutdown()\n    ```\n\n    \u003cp style=\"margin-top: -1rem;\"\u003e\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n## Reactive Programming: Key Highlights of Pyventus\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;In addition to the standard functionalities of reactive programming, Pyventus also provides some unique aspects that set it apart from other implementations. In this section, we will explore some of these key features and how to use them effectively.\n\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#rp-python-callables-as-observable-tasks\" name=\"rp-python-callables-as-observable-tasks\" title=\"Permanent link\"\u003ePython Callables as Observable Tasks\u003c/a\u003e ─\n        Whether you are working with generators or regular functions, Pyventus allows you to easily convert any Python callable into an observable task. These tasks are specialized observables that encapsulate a unit of work and provide a mechanism for streaming their results to a series of subscribers.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"1-2 5\"\n    @as_observable_task\n    def compute_square(n):\n        return n * n\n\n    obs = compute_square(2)\n    obs.subscribe(print)\n    obs()\n    ```\n\n    \u003cdetails markdown=\"1\" class=\"tip\"\u003e\n    \u003csummary\u003eYou can also work with \u003ccode\u003easync\u003c/code\u003e functions...\u003c/summary\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"1-2 6\"\n    @as_observable_task\n    async def fetch_data():\n        await asyncio.sleep(1)\n        return {\"data\": \"Sample Data\"}\n\n    obs = fetch_data()\n    obs.subscribe(print)\n    obs()\n    ```\n\n    \u003c/details\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"1-2 4 8\"\n    @as_observable_task\n    def simple_counter(stop: int):\n        for count in range(1, stop + 1):\n            yield count\n        raise Completed\n\n\n    obs = simple_counter(stop=16)\n    obs.subscribe(print)\n    obs()\n    ```\n\n    \u003cdetails markdown=\"1\" class=\"tip\"\u003e\n    \u003csummary\u003eYou can also work with \u003ccode\u003easync\u003c/code\u003e generators...\u003c/summary\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"1-2 5 8\"\n    @as_observable_task\n    async def async_counter(stop: int):\n        for count in range(1, stop + 1):\n            await asyncio.sleep(0.25)\n            yield count\n        raise Completed\n\n    obs = async_counter(stop=16)\n    obs.subscribe(print)\n    obs()\n    ```\n\n    \u003c/details\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#rp-multicast-support\" name=\"rp-multicast-support\" title=\"Permanent link\"\u003eMulticast Support\u003c/a\u003e ─\n        Observables in Pyventus are designed from the ground up to efficiently support both unicast and multicast scenarios. So, it doesn't matter if you need to work with either single or multiple subscribers; Pyventus allows you to utilize these notification models and even optimizes the processing of each to ensure optimal performance.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"9-11\"\n    @as_observable_task\n    def simple_counter(stop: int):\n        for count in range(1, stop + 1):\n            yield count\n        raise Completed\n\n\n    obs = simple_counter(stop=16)\n    obs.subscribe(next_callback=lambda val: print(f\"Subscriber 1 - Received: {val}\"))\n    obs.subscribe(next_callback=lambda val: print(f\"Subscriber 2 - Received: {val}\"))\n    obs.subscribe(next_callback=lambda val: print(f\"Subscriber 3 - Received: {val}\"))\n    obs()\n    ```\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#rp-success-and-error-handling\" name=\"rp-success-and-error-handling\" title=\"Permanent link\"\u003eSuccess and Error Handling\u003c/a\u003e ─\n    With Pyventus, you can customize how data streams are handled upon completion, whether they succeed or encounter errors. This customization is achieved through the configuration of the complete and error callbacks in the observer definition, which is done during the subscription process.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"11-13\"\n    @as_observable_task\n    async def interactive_counter():\n        stop: int = int(input(\"Please enter a number to count up to: \"))  # Can raise ValueError\n        for count in range(1, stop + 1):\n            yield count\n        raise Completed\n\n\n    obs = interactive_counter()\n    obs.subscribe(\n        next_callback=lambda val: print(f\"Received: {val}\"),\n        error_callback=lambda err: print(f\"Error: {err}\"),\n        complete_callback=lambda: print(\"All done!\"),\n    )\n    obs()\n    ```\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#rp-declarative-subscription-model\" name=\"rp-declarative-subscription-model\" title=\"Permanent link\"\u003eDeclarative Subscription Model\u003c/a\u003e ─\n        Alongside standard subscription models, such as using lambda functions or predefined callbacks, Pyventus also provides a declarative subscription model that allows you to not only define the observer's callbacks inline and in a step-by-step manner but also to do so right before the subscription takes place.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\"  hl_lines=\"1 3-4 7-8 11-12\"\n    with obs.subscribe() as subctx:\n\n        @subctx.on_next\n        def next(value: int) -\u003e None:\n            print(f\"Received: {value}\")\n\n        @subctx.on_error\n        def error(error: Exception) -\u003e None:\n            print(f\"Error: {error}\")\n\n        @subctx.on_complete\n        def complete() -\u003e None:\n            print(\"All done!\")\n    ```\n\n    \u003cdetails markdown=\"1\" class=\"tip\"\u003e\n    \u003csummary\u003eYou can also use the \u003ccode\u003esubscribe()\u003c/code\u003e method as a decorator...\u003c/summary\u003e\n\n    \u003cp style=\"text-align: justify;\"\u003e\n        \u0026emsp;\u0026emsp;The \u003ccode\u003esubscribe()\u003c/code\u003e method, besides being used as a regular function and a context manager, can also be utilized as a decorator. When used this way, it creates and subscribes an observer, using the decorated function as its next callback.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"1-2\"\n    @obs.subscribe()\n    def next(value: int) -\u003e None:\n        print(f\"Received: {value}\")\n    ```\n\n    \u003c/details\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#rp-simplified-execution-for-observable-tasks\" name=\"rp-simplified-execution-for-observable-tasks\" title=\"Permanent link\"\u003eSimplified Execution for Observable Tasks\u003c/a\u003e ─\n        Having to explicitly call each observable task to initiate their execution can be tedious and easily overlooked, especially when working with multiple observables at the same time. However, by using observable tasks within a \u003ccode\u003ewith\u003c/code\u003e statement block, you can avoid this manual work and enable what is known as their execution context, which will allow you to work with them as usual while ensuring that they are called upon exiting the context block.\n    \u003c/p\u003e\n\n    ```Python linenums=\"1\"  hl_lines=\"7\"\n    @as_observable_task\n    def simple_counter(stop: int):\n        for count in range(1, stop + 1):\n            yield count\n        raise Completed\n\n    with simple_counter(stop=16) as obs:\n        obs.subscribe(lambda val: print(f\"Subscriber 1 - Received: {val}\"))\n        obs.subscribe(lambda val: print(f\"Subscriber 2 - Received: {val}\"))\n    ```\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#rp-thread-offloading-for-observable-tasks\" name=\"rp-thread-offloading-for-observable-tasks\" title=\"Permanent link\"\u003eThread Offloading for Observable Tasks\u003c/a\u003e ─\n        By default, the processing of each observable task is handled by the AsyncIO framework, either synchronously or asynchronously depending on the context. However, for multithreaded environments, Pyventus also provides support for running these observable tasks in separate threads.  \n    \u003c/p\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"1 12 16 20\"\n    from concurrent.futures import ThreadPoolExecutor\n    from pyventus.reactive import as_observable_task, Completed\n\n    @as_observable_task\n    def simple_counter(stop: int):\n        for count in range(1, stop + 1):\n            yield count\n        raise Completed\n\n    if __name__ == \"__main__\":\n\n        with ThreadPoolExecutor() as executor:\n\n            obs1 = simple_counter(16)\n            obs1.subscribe(print)\n            obs1(executor)\n\n            obs2 = simple_counter(16)\n            obs2.subscribe(print)\n            obs2(executor)\n    ```\n\n    \u003cdetails markdown=\"1\" class=\"tip\"\u003e\n    \u003csummary\u003eThread offloading is also available for the execution context of observable tasks...\u003c/summary\u003e\n\n    ```Python linenums=\"1\" hl_lines=\"14 17\"\n    from concurrent.futures import ThreadPoolExecutor\n    from pyventus.reactive import as_observable_task, Completed\n\n    @as_observable_task\n    def simple_counter(stop: int):\n        for count in range(1, stop + 1):\n            yield count\n        raise Completed\n\n    if __name__ == \"__main__\":\n\n        with ThreadPoolExecutor() as executor:\n\n            with simple_counter(16).to_thread(executor) as obs1:\n                obs1.subscribe(print)\n\n            with simple_counter(16).to_thread(executor) as obs2:\n                obs2.subscribe(print)\n    ```\n\n    \u003c/details\u003e\n\n    \u003cp style=\"margin-top: -1rem;\"\u003e\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n## Additional Highlights\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;Beyond the core functionalities of event-driven and reactive programming, Pyventus also includes some additional features that are worth noting. In this section, we will explore these aspects and how to use them effectively.\n\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#ah-debugging-utilities\" name=\"ah-debugging-utilities\" title=\"Permanent link\"\u003eDebugging Utilities\u003c/a\u003e ─\n        Debugging plays a crucial role in the development of asynchronous and event-driven applications, as it allows you to understand what’s going on under the hood and provides valuable insights when troubleshooting errors. For this reason, Pyventus offers a clear string representation of each component, along with a debug mode flag that lets you view the package's logs to better understand the processes at work.\n    \u003c/p\u003e\n\n    \u003cp style=\"text-align: center;\"\u003e\n        \u003cimg style=\"border-radius: 0.5rem;\" src=\"https://raw.githubusercontent.com/mdapena/pyventus/refs/heads/master/docs/images/examples/debug-mode-example.png\"\u003e\n    \u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n-   \u003cp style=\"text-align: justify;\"\u003e\u003ca href=\"#ah-efficient-import-management\" name=\"ah-efficient-import-management\" title=\"Permanent link\"\u003eEfficient Import Management\u003c/a\u003e ─\n        Pyventus encapsulates each paradigm into its own isolated package, so that you can not only have a clear boundary between event-driven and reactive programming features, but also apply Python import optimizations based on the required paradigm. For example, if you are only working with the events module of Pyventus and never import the reactive package, Python does not load it.\n    \u003c/p\u003e\n\n\u003cp style=\"margin-top: -1rem;\"\u003e\u003c/p\u003e\n\n[//]: # \"--------------------------------------------------------------------------------------------------------------\"\n\n## License\n\n\u003cp style=\"text-align: justify;\"\u003e\n    \u0026emsp;\u0026emsp;Pyventus is distributed as open-source software and is released under the \u003ca href=\"https://choosealicense.com/licenses/mit/\" target=\"_blank\"\u003eMIT License\u003c/a\u003e. For a detailed view of the license, please refer to the \u003ca href=\"https://github.com/mdapena/pyventus/blob/master/LICENSE\" target=\"_blank\"\u003e\u003ccode\u003eLICENSE\u003c/code\u003e\u003c/a\u003e file located in the Pyventus repository.\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmdapena%2Fpyventus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmdapena%2Fpyventus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmdapena%2Fpyventus/lists"}