pyventus


Namepyventus JSON
Version 0.6.0 PyPI version JSON
download
home_pageNone
SummaryA powerful Python package for event-driven programming; define, emit, and orchestrate events with ease.
upload_time2024-10-19 13:52:22
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseNone
keywords event-dispatchers event-driven event-emitters event-handlers event-linkers events python
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            
<br>

<p align="center">
   <img src="https://mdapena.github.io/pyventus/images/logo/pyventus-logo-name-slogan.svg" alt="Pyventus" width="750px">
</p>

<br>

<p align="center">

<a href="https://github.com/mdapena/pyventus/actions?query=workflow%3ATests+event%3Apush+branch%3Amaster" target="_blank">
    <img src="https://github.com/mdapena/pyventus/actions/workflows/run-tests.yml/badge.svg?branch=master" alt="Tests">
</a>

<a href="https://github.com/mdapena/pyventus/actions?query=workflow%3ADocs+event%3Apush+branch%3Amaster" target="_blank">
    <img src="https://github.com/mdapena/pyventus/actions/workflows/deploy-docs.yml/badge.svg?branch=master" alt="Docs">
</a>

<a href='https://coveralls.io/github/mdapena/pyventus?branch=master'>
	<img src='https://coveralls.io/repos/github/mdapena/pyventus/badge.svg?branch=master' alt='Coverage Status'/>
</a>

<a href="https://pypi.org/project/pyventus" target="_blank">
    <img src="https://img.shields.io/pypi/v/pyventus?color=0097a8" alt="Package version">
</a>

<a href="https://pypi.org/project/pyventus" target="_blank">
    <img src="https://img.shields.io/pypi/pyversions/pyventus?color=0097a8" alt="Supported Python versions">
</a>

<a href="https://pypi.org/project/pyventus">
	<img src="https://img.shields.io/pypi/dm/pyventus.svg?color=0097a8" alt="Code style: black">
</a>

</p>

<br>

---

**Documentation**: <a href="https://mdapena.github.io/pyventus" target="_blank">https://mdapena.github.io/pyventus</a>

**Source Code**: <a href="https://github.com/mdapena/pyventus" target="_blank">https://github.com/mdapena/pyventus</a>

---

<p style='text-align: justify;' markdown>
    &emsp;&emsp;Pyventus is a powerful Python package for event-driven programming. It offers a comprehensive suite
	of tools to easily define, emit, and orchestrate events. With Pyventus, you can build scalable, extensible, and
	loosely-coupled event-driven applications.
</p>

[//]: # (--------------------------------------------------------------------------------------------------------------)

## Key Features

<p style='text-align: justify;' markdown>
    Pyventus offers several key features, such as:
</p>

<ul style='text-align: justify;'>

<li><b>Sync and Async Support</b> ─ 
Whether your code is synchronous or asynchronous, Pyventus allows you to define event handlers as either sync or async
callbacks and emit events from both scopes. 
</li>

<li><b>Customization</b> ─ 
Whether you choose official emitters or custom ones, Pyventus allows you to customize the behavior and capabilities of
the event emitters to perfectly align with your unique requirements.
</li>

<li><b>An Intuitive API</b> ─ 
Pyventus provides a user-friendly API for defining events, emitters, and handlers. Its design simplifies the process
of working with events, enabling you to organize your code around discrete events and their associated actions.
</li>

<li><b>Runtime Flexibility</b> ─ 
Pyventus' runtime flexibility allows you to switch seamlessly between different built-in or custom event emitter
implementations on the fly, providing a dynamic and adaptable environment for event-driven programming.
</li>

<li><b>Reliable Event Handling</b> ─ 
Pyventus allows you to define handlers to customize how events are processed upon completion. Attach success and
failure logic to take targeted actions based on the outcome of each event execution. 
</li>

<li><b>Scalability and Maintainability</b> ─ 
By adopting an event-driven approach with Pyventus, you can create scalable and maintainable code thanks to the loose
coupling between its components that enables extensibility and modularity.
</li>

<li><b>Comprehensive Documentation</b> ─ 
Pyventus provides a comprehensive documentation suite that includes API references, usage examples, and tutorials to
effectively leverage all the features and capabilities of the package.
</li>

</ul>

[//]: # (--------------------------------------------------------------------------------------------------------------)

## Quick Start

<p style='text-align: justify;'>
	&emsp;&emsp;Pyventus is published as a <a href="https://pypi.org/project/pyventus/" target="_blank">Python package</a> 
	and can be installed using <code>pip</code>, ideally in a <a href="https://realpython.com/python-virtual-environments-a-primer/" target="_blank">virtual environment</a>
	for proper dependency isolation. To get started, open up a terminal and install Pyventus with the following command:
</p>

```console
pip install pyventus
```

<p style='text-align: justify;'>
	&emsp;&emsp;Pyventus by default relies on the Python standard library and <b>requires Python 3.10 or higher</b> with no 
	additional dependencies. However, this package also includes alternative integrations to access additional features 
	such as Redis Queue, Celery, and FastAPI. For more information on this matter, please refer to the 
	<a href="https://mdapena.github.io/pyventus/0.6/getting-started/#optional-dependencies" target="_blank">Optional Dependencies</a>
	section.
</p>

[//]: # (--------------------------------------------------------------------------------------------------------------)

## A Simple Example

<p style='text-align: justify;'>
    &emsp;&emsp;Experience the power of Pyventus through a simple <code>Hello, World!</code> example that illustrates
	the core concepts and basic usage of the package. By following this example, you’ll learn how to subscribe to events
	and emit them within your application.
</p>

```Python title="Hello, World! Example" linenums="1"
from pyventus import EventLinker, EventEmitter, AsyncIOEventEmitter


@EventLinker.on("GreetEvent")
def handle_greet_event():
    print("Hello, World!")


event_emitter: EventEmitter = AsyncIOEventEmitter()
event_emitter.emit("GreetEvent")
```

<details markdown="1" class="info">
<summary>You can also work with <code>async</code> functions and contexts...</summary>

```Python title="Hello, World! Example (Async version)" linenums="1" hl_lines="5"
from pyventus import EventLinker, EventEmitter, AsyncIOEventEmitter


@EventLinker.on("GreetEvent")
async def handle_greet_event():
    print("Hello, World!")


event_emitter: EventEmitter = AsyncIOEventEmitter()
event_emitter.emit("GreetEvent")
```

</details>

<p style='text-align: justify;'>
    &emsp;&emsp;As we can see from the <code>Hello, World!</code> example, Pyventus follows a simple and intuitive 
	workflow for defining and emitting events. Let's recap the essential steps involved:
</p>

<ol style='text-align: justify;'>

<li>
<b>Importing Necessary Modules:</b> 
We first imported the required modules from Pyventus,  which encompassed the <code>EventLinker</code>
class, the <code>EventEmitter</code> interface, and the <code>AsyncIOEventEmitter</code> implementation.
</li>

<li>
<b>Linking Events to Callbacks:</b> 
Next, we used the <code>@EventLinker.on()</code> decorator to define and link the string event <code>GreetEvent</code> 
to the function <code>handle_greet_event()</code>, which will print <i>'Hello, World!'</i> to the console whenever the
<code>GreetEvent</code> is emitted.
</li>

<li>
<b>Instantiating an Event Emitter:</b> 
After that, and in order to trigger our event, we needed to create an instance of the event emitter class. While 
<code>AsyncIOEventEmitter</code> was utilized, any built-in or custom implementation could be employed.
</li>

<li>
<b>Triggering the Event:</b>
Finally, by using the <code>emit()</code> method of the event emitter instance, we were able to trigger the 
<code>GreetEvent</code>, which resulted in the execution of the <code>handle_greet_event()</code> callback.
</li>

</ol>

<p style='text-align: justify;'>
    &emsp;&emsp;Having gained a clear understanding of the workflow showcased in the <code>Hello, World!</code> 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 
	Pyventus Tutorials or API.
</p>

[//]: # (--------------------------------------------------------------------------------------------------------------)

## A Practical Example

<p style='text-align: justify;'>
    &emsp;&emsp;To showcase Pyventus' event-driven capabilities in a real-world scenario, we will explore a practical 
	example of implementing a voltage sensor using an event-driven architecture (crucial for such scenarios). The 
	purpose of this example is to create an efficient voltage sensor that can seamlessly handle real-time data 
	and respond appropriately to specific voltage conditions.
</p>


<details markdown="1" class="quote" open>
<summary>Example ─ Monitoring Voltage Levels Across Devices (Context)</summary>

<a style="text-align: center" href="https://unsplash.com/photos/macro-photography-of-black-circuit-board-FO7JIlwjOtU?utm_content=creditShareLink&utm_medium=referral&utm_source=unsplash" target="_blank">
	<img src="https://mdapena.github.io/pyventus/images/examples/practical-example-index.jpg" alt="Macro photography of black circuit board">
</a>

<p style='text-align: justify;'>
	<i>&emsp;&emsp;A common aspect found in many systems is the need to monitor and respond to changes in sensor data.
	Whether it's pressure sensors, temperature sensors, or other types, capturing and reacting to sensor data is crucial
	for effective system operation. In our practical example, we will focus on a specific scenario: building a sensor 
	system that monitors voltage levels across devices. The goal of our voltage sensor is to detect potential issues,
	such as low or high voltage conditions, and respond appropriately in real-time.</i>
</p>

</details>


<p style='text-align: justify;'>
    &emsp;&emsp;To accomplish our goal, we will define a <code>VoltageSensor</code> class to read voltage levels and emit 
	events based on predefined thresholds. We will create event handlers to respond to these events, performing actions 
	such as activating eco-mode for low voltage or implementing high-voltage protection. Additionally, a shared event 
	handler will provide general notifications for out-of-range voltage situations. The code example below illustrates 
	the implementation of this system.
</p>

```Python title="Voltage Sensor System with Pyventus (Practical Example)" linenums="1" hl_lines="9 14 27-30 35-36 41-42 47-48 55"
import asyncio
import random

from pyventus import EventEmitter, EventLinker, AsyncIOEventEmitter


class VoltageSensor:

    def __init__(self, name: str, low: float, high: float, event_emitter: EventEmitter) -> None:
        # Initialize the VoltageSensor object with the provided parameters
        self._name: str = name
        self._low: float = low
        self._high: float = high
        self._event_emitter: EventEmitter = event_emitter

    async def __call__(self) -> None:
        # Start voltage readings for the sensor
        print(f"Starting voltage readings for: {self._name}")
        print(f"Low: {self._low:.3g}v | High: {self._high:.3g}v\n-----------\n")

        while True:
            # Simulate sensor readings
            voltage: float = random.uniform(0, 5)
            print("\tSensor Reading:", "\033[32m", f"{voltage:.3g}v", "\033[0m")

            # Emit events based on voltage readings
            if voltage < self._low:
                self._event_emitter.emit("LowVoltageEvent", sensor=self._name, voltage=voltage)
            elif voltage > self._high:
                self._event_emitter.emit("HighVoltageEvent", sensor=self._name, voltage=voltage)

            await asyncio.sleep(1)


@EventLinker.on("LowVoltageEvent")
def handle_low_voltage_event(sensor: str, voltage: float):
    print(f"🪫 Turning on eco-mode for '{sensor}'. ({voltage:.3g}v)\n")
    # Perform action for low voltage...


@EventLinker.on("HighVoltageEvent")
async def handle_high_voltage_event(sensor: str, voltage: float):
    print(f"⚡ Starting high-voltage protection for '{sensor}'. ({voltage:.3g}v)\n")
    # Perform action for high voltage...


@EventLinker.on("LowVoltageEvent", "HighVoltageEvent")
def handle_voltage_event(sensor: str, voltage: float):
    print(f"\033[31m\nSensor '{sensor}' out of range.\033[0m (Voltage: {voltage:.3g})")
    # Perform notification for out of range voltage...


async def main():
    # Initialize the sensor and run the sensor readings
    sensor = VoltageSensor(name="PressureSensor", low=0.5, high=3.9, event_emitter=AsyncIOEventEmitter())
    await asyncio.gather(sensor(), )  # Add new sensors inside the 'gather' for multi-device monitoring


asyncio.run(main())
```

<p style='text-align: justify;'>
    &emsp;&emsp;As we can see from this practical example, Pyventus enables us to easily build an event-driven system 
	for voltage sensors that is flexible, efficient, and highly responsive. With its intuitive API and support for both
	synchronous and asynchronous operations, we were able to effectively monitor voltage levels, detect anomalies, and 
	trigger appropriate actions in real-time.
</p>

[//]: # (--------------------------------------------------------------------------------------------------------------)

## Support for Synchronous and Asynchronous Code

<p style='text-align: justify;'>
    &emsp;&emsp;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 and emit events across 
	<code>sync</code> and <code>async</code> contexts.
</p>

### Subscribing Event Handlers with <code>Sync</code> and <code>Async</code> Callbacks

```Python linenums="1" hl_lines="2 7"
@EventLinker.on("MyEvent")
def sync_event_callback():
    pass  # Synchronous event handling


@EventLinker.on("MyEvent")
async def async_event_callback():
	pass  # Asynchronous event handling
```

<details markdown="1" class="info">
<summary>You can optimize the execution of your callbacks based on their workload...</summary>

<p style='text-align: justify;'>
    &emsp;&emsp;By default, event handlers in Pyventus are executed concurrently during an event emission, running their
	<code>sync</code> and <code>async</code> callbacks as defined. However, if you have a <code>sync</code> callback
	that involves I/O or non-CPU bound operations, you can enable the <code>force_async</code> parameter to offload it
	to a thread pool, ensuring optimal performance and responsiveness. The <code>force_async</code> parameter utilizes 
	the <a href="https://docs.python.org/3/library/asyncio-task.html#running-in-threads" target="_blank"><code>asyncio.to_thread()</code></a>
	function to execute <code>sync</code> callbacks asynchronously.
</p>

```Python linenums="1" hl_lines="1"
@EventLinker.on("BlockingIO", force_async=True)
def blocking_io():
    print(f"start blocking_io at {time.strftime('%X')}")
    # Note that time.sleep() can be replaced with any blocking
    # IO-bound operation, such as file operations.
    time.sleep(1)
    print(f"blocking_io complete at {time.strftime('%X')}")
```

</details>

### Emitting Events from <code>Sync</code> and <code>Async</code> Contexts

```Python linenums="1" hl_lines="3 8"
# Emitting an event within a sync function
def sync_function(event_emitter: EventEmitter):
    event_emitter.emit("MyEvent")


# Emitting an event within an async function
async def async_function(event_emitter: EventEmitter):
    event_emitter.emit("MyEvent")
```

<details markdown="1" class="info">
<summary>Event propagation within different contexts...</summary>

<p style='text-align: justify;'>
    &emsp;&emsp;While Pyventus provides a base <code>EventEmitter</code> class with a unified sync/async API, the 
	specific propagation behavior when emitting events may vary depending on the concrete <code>EventEmitter</code>
	used. For example, the <code>AsyncIOEventEmitter</code> implementation leverages the <code>AsyncIO</code> event
	loop to schedule callbacks added from asynchronous contexts without blocking. But alternative emitters could 
	structure propagation differently to suit their needs.
</p>

</details>

[//]: # (--------------------------------------------------------------------------------------------------------------)

## Runtime Flexibility and Customization

<p style='text-align: justify;'>
    &emsp;&emsp;At its core, Pyventus utilizes a modular event emitter design that allows you to switch seamlessly
	between different built-in or custom event emitter implementations on the fly. Whether you opt for official emitters
	or decide to create your custom ones, Pyventus allows you to tailor the behavior and capabilities of the event 
	emitters to perfectly align with your unique requirements.
</p>

### Swapping Event Emitter Implementations at Runtime

<p style='text-align: justify;'>
    &emsp;&emsp;By leveraging the principle of dependency inversion and using the base <code>EventEmitter</code> as a
	dependency, you can change the concrete implementation on the fly. Let's demonstrate this using the AsyncIO 
	Event Emitter and the Executor Event Emitter: 
</p>

```Python title="Event Emitter Runtime Flexibility Example" linenums="1" hl_lines="10-11 14 16"
from pyventus import EventLinker, EventEmitter, AsyncIOEventEmitter, ExecutorEventEmitter


@EventLinker.on("GreetEvent")
def handle_greet_event(name: str = "World"):
    print(f"Hello, {name}!")


if __name__ == "__main__":
    def main(event_emitter: EventEmitter) -> None:
        event_emitter.emit("GreetEvent", name=type(event_emitter).__name__)


    main(event_emitter=AsyncIOEventEmitter())
    with ExecutorEventEmitter() as executor_event_emitter:
        main(event_emitter=executor_event_emitter)
```

### Defining Custom Event Emitters

<p style='text-align: justify;'>
    &emsp;&emsp;To illustrate Pyventus' customization capabilities, we will define and implement a custom event emitter
	class for the FastAPI framework. This class will efficiently handle the execution of event emissions through its 
	<a href="https://fastapi.tiangolo.com/reference/background/" target="_blank">background tasks</a> workflow.
</p>

```Python title="Custom Event Emitter Example" linenums="1" hl_lines="6 10-11 13-14"
from fastapi import BackgroundTasks

from pyventus import EventEmitter, EventLinker


class FastAPIEventEmitter(EventEmitter):
    """A custom event emitter that uses the FastAPI background tasks."""

    def __init__(self, background_tasks: BackgroundTasks):
        super().__init__(event_linker=EventLinker, debug=False)
        self._background_tasks = background_tasks

    def _process(self, event_emission: EventEmitter.EventEmission) -> None:
        self._background_tasks.add_task(event_emission)  # Process the event emission as a background task
```

<details markdown="1" class="tip">
<summary>Official <code>FastAPIEventEmitter</code> Integration.</summary>

<p style='text-align: justify;'>
    In case you're interested in integrating Pyventus with FastAPI, you can refer to the official Pyventus 
	<a href="https://mdapena.github.io/pyventus/0.6/tutorials/emitters/fastapi/"><i>FastAPI Event Emitter</i></a> 
	implementation.
</p>

</details>

[//]: # (--------------------------------------------------------------------------------------------------------------)

## Event Objects and Global Events

<p style='text-align: justify;'>
    &emsp;&emsp;In addition to string events, Pyventus also supports Event Objects, which provide a structured way to 
	define events and encapsulate relevant data payloads.
</p>

```Python title="Event Object Example" linenums="1" hl_lines="1-2 7-8 16-19"
@dataclass  # Define a Python dataclass representing the event and its payload.
class OrderCreatedEvent:
    order_id: int
    payload: dict[str, any]


@EventLinker.on(OrderCreatedEvent)  # Subscribe event handlers to the event.
def handle_order_created_event(event: OrderCreatedEvent):
    # Pyventus will automatically pass the Event Object 
    # as the first positional argument.
    print(f"Event Object: {event}")


event_emitter: EventEmitter = AsyncIOEventEmitter()
event_emitter.emit(
    event=OrderCreatedEvent(  # Emit an instance of the event!
        order_id=6452879,
        payload={},
    ),
)
```

<p style='text-align: justify;'>
    &emsp;&emsp;Furthermore, Pyventus provides support for Global Events, which are particularly useful for 
	implementing cross-cutting concerns such as logging, monitoring, or analytics. By subscribing event handlers to
	<code>...</code> or <code>Ellipsis</code>, you can capture all events that may occur within that 
	<code>EventLinker</code> context.
</p>

```Python title="Global Event Example" linenums="1" hl_lines="1"
@EventLinker.on(...)
def handle_any_event(*args, **kwargs):
    print(f"Perform logging...\nArgs: {args}\tKwargs: {kwargs}")


event_emitter: EventEmitter = AsyncIOEventEmitter()
event_emitter.emit("GreetEvent", name="Pyventus")
```

[//]: # (--------------------------------------------------------------------------------------------------------------)

## Success and Error Handling

<p style='text-align: justify;'>
    &emsp;&emsp;With Pyventus, you can customize how events are handled upon completion, whether they succeed or 
	encounter errors. This customization is achieved by using either the EventLinker's <code>on()</code> or 
	<code>once()</code> decorator within a <code>with</code> statement block. Inside this block, you can 
	define not only the event callbacks but also the overall workflow of the event. Now, let’s explore 
	this simple yet powerful Pythonic syntax of Pyventus through an example.
</p>

```Python title="Success and Error Handling Example" linenums="1" hl_lines="4 6-7 10-11 14-15"
from pyventus import EventLinker, EventEmitter, AsyncIOEventEmitter

# Create an event linker for the "DivisionEvent"
with EventLinker.on("DivisionEvent") as linker:
    @linker.on_event
    def divide(a: float, b: float) -> float:
        return a / b

    @linker.on_success
    def handle_success(result: float) -> None:
        print(f"Division result: {result:.3g}")

    @linker.on_failure
    def handle_failure(e: Exception) -> None:
        print(f"Oops, something went wrong: {e}")

event_emitter: EventEmitter = AsyncIOEventEmitter()  # Create an event emitter
event_emitter.emit("DivisionEvent", a=1, b=0)  # Example: Division by zero
event_emitter.emit("DivisionEvent", a=1, b=2)  # Example: Valid division
```

<p style='text-align: justify;'>
    &emsp;&emsp;As we have seen from the example, Pyventus offers a reliable and Pythonic solution for customizing 
	event handling. By utilizing the EventLinker and its decorators within a <code>with</code> statement block, we
	were able to define the <code>DivisionEvent</code> and specify the callbacks for division, success, and failure
	cases.
</p>

[//]: # (--------------------------------------------------------------------------------------------------------------)

## Continuous Evolution

<p style='text-align: justify;'>
	&emsp;&emsp;Pyventus continuously adapts to support developers across technological and programming domains. Its
	aim is to remain at the forefront of event-driven design. Future development may introduce new official event 
	emitters, expanding compatibility with different technologies through seamless integration.
</p>

<p style='text-align: justify;'>
	&emsp;&emsp;Current default emitters provide reliable out-of-the-box capabilities for common use cases. They
	efficiently handle core event operations and lay the foundation for building event-driven applications.
</p>

<details markdown="1" class="info">
<summary>Driving Innovation Through Collaboration</summary>

<p style='text-align: justify;'>
    &emsp;&emsp;Pyventus is an open source project that welcomes community involvement. If you wish to contribute
	additional event emitters, improvements, or bug fixes, please check the <a href="https://mdapena.github.io/pyventus/latest/contributing/" target="_blank">Contributing</a> 
	section for guidelines on collaborating. Together, we can further the possibilities of event-driven development.
</p>

</details>

[//]: # (--------------------------------------------------------------------------------------------------------------)

## License

<p style='text-align: justify;' markdown>
    &emsp;&emsp;Pyventus is distributed as open source software and is released under the <a href="https://choosealicense.com/licenses/mit/" target="_blank">MIT License</a>. 
    You can view the full text of the license in the <a href="https://github.com/mdapena/pyventus/blob/master/LICENSE" target="_blank"><code>LICENSE</code></a> 
	file located in the Pyventus repository.
</p>

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "pyventus",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "event-dispatchers, event-driven, event-emitters, event-handlers, event-linkers, events, python",
    "author": null,
    "author_email": "Manuel Da Pena <dapensoft@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/4c/9d/be394945ec4cf31f6d49f02866ad3c3db9566b99470ead998580794db4da/pyventus-0.6.0.tar.gz",
    "platform": null,
    "description": "\n<br>\n\n<p align=\"center\">\n   <img src=\"https://mdapena.github.io/pyventus/images/logo/pyventus-logo-name-slogan.svg\" alt=\"Pyventus\" width=\"750px\">\n</p>\n\n<br>\n\n<p align=\"center\">\n\n<a href=\"https://github.com/mdapena/pyventus/actions?query=workflow%3ATests+event%3Apush+branch%3Amaster\" target=\"_blank\">\n    <img src=\"https://github.com/mdapena/pyventus/actions/workflows/run-tests.yml/badge.svg?branch=master\" alt=\"Tests\">\n</a>\n\n<a href=\"https://github.com/mdapena/pyventus/actions?query=workflow%3ADocs+event%3Apush+branch%3Amaster\" target=\"_blank\">\n    <img src=\"https://github.com/mdapena/pyventus/actions/workflows/deploy-docs.yml/badge.svg?branch=master\" alt=\"Docs\">\n</a>\n\n<a href='https://coveralls.io/github/mdapena/pyventus?branch=master'>\n\t<img src='https://coveralls.io/repos/github/mdapena/pyventus/badge.svg?branch=master' alt='Coverage Status'/>\n</a>\n\n<a href=\"https://pypi.org/project/pyventus\" target=\"_blank\">\n    <img src=\"https://img.shields.io/pypi/v/pyventus?color=0097a8\" alt=\"Package version\">\n</a>\n\n<a href=\"https://pypi.org/project/pyventus\" target=\"_blank\">\n    <img src=\"https://img.shields.io/pypi/pyversions/pyventus?color=0097a8\" alt=\"Supported Python versions\">\n</a>\n\n<a href=\"https://pypi.org/project/pyventus\">\n\t<img src=\"https://img.shields.io/pypi/dm/pyventus.svg?color=0097a8\" alt=\"Code style: black\">\n</a>\n\n</p>\n\n<br>\n\n---\n\n**Documentation**: <a href=\"https://mdapena.github.io/pyventus\" target=\"_blank\">https://mdapena.github.io/pyventus</a>\n\n**Source Code**: <a href=\"https://github.com/mdapena/pyventus\" target=\"_blank\">https://github.com/mdapena/pyventus</a>\n\n---\n\n<p style='text-align: justify;' markdown>\n    &emsp;&emsp;Pyventus is a powerful Python package for event-driven programming. It offers a comprehensive suite\n\tof tools to easily define, emit, and orchestrate events. With Pyventus, you can build scalable, extensible, and\n\tloosely-coupled event-driven applications.\n</p>\n\n[//]: # (--------------------------------------------------------------------------------------------------------------)\n\n## Key Features\n\n<p style='text-align: justify;' markdown>\n    Pyventus offers several key features, such as:\n</p>\n\n<ul style='text-align: justify;'>\n\n<li><b>Sync and Async Support</b> \u2500 \nWhether your code is synchronous or asynchronous, Pyventus allows you to define event handlers as either sync or async\ncallbacks and emit events from both scopes. \n</li>\n\n<li><b>Customization</b> \u2500 \nWhether you choose official emitters or custom ones, Pyventus allows you to customize the behavior and capabilities of\nthe event emitters to perfectly align with your unique requirements.\n</li>\n\n<li><b>An Intuitive API</b> \u2500 \nPyventus provides a user-friendly API for defining events, emitters, and handlers. Its design simplifies the process\nof working with events, enabling you to organize your code around discrete events and their associated actions.\n</li>\n\n<li><b>Runtime Flexibility</b> \u2500 \nPyventus' runtime flexibility allows you to switch seamlessly between different built-in or custom event emitter\nimplementations on the fly, providing a dynamic and adaptable environment for event-driven programming.\n</li>\n\n<li><b>Reliable Event Handling</b> \u2500 \nPyventus allows you to define handlers to customize how events are processed upon completion. Attach success and\nfailure logic to take targeted actions based on the outcome of each event execution. \n</li>\n\n<li><b>Scalability and Maintainability</b> \u2500 \nBy adopting an event-driven approach with Pyventus, you can create scalable and maintainable code thanks to the loose\ncoupling between its components that enables extensibility and modularity.\n</li>\n\n<li><b>Comprehensive Documentation</b> \u2500 \nPyventus provides a comprehensive documentation suite that includes API references, usage examples, and tutorials to\neffectively leverage all the features and capabilities of the package.\n</li>\n\n</ul>\n\n[//]: # (--------------------------------------------------------------------------------------------------------------)\n\n## Quick Start\n\n<p style='text-align: justify;'>\n\t&emsp;&emsp;Pyventus is published as a <a href=\"https://pypi.org/project/pyventus/\" target=\"_blank\">Python package</a> \n\tand can be installed using <code>pip</code>, ideally in a <a href=\"https://realpython.com/python-virtual-environments-a-primer/\" target=\"_blank\">virtual environment</a>\n\tfor proper dependency isolation. To get started, open up a terminal and install Pyventus with the following command:\n</p>\n\n```console\npip install pyventus\n```\n\n<p style='text-align: justify;'>\n\t&emsp;&emsp;Pyventus by default relies on the Python standard library and <b>requires Python 3.10 or higher</b> with no \n\tadditional dependencies. However, this package also includes alternative integrations to access additional features \n\tsuch as Redis Queue, Celery, and FastAPI. For more information on this matter, please refer to the \n\t<a href=\"https://mdapena.github.io/pyventus/0.6/getting-started/#optional-dependencies\" target=\"_blank\">Optional Dependencies</a>\n\tsection.\n</p>\n\n[//]: # (--------------------------------------------------------------------------------------------------------------)\n\n## A Simple Example\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;Experience the power of Pyventus through a simple <code>Hello, World!</code> example that illustrates\n\tthe core concepts and basic usage of the package. By following this example, you\u2019ll learn how to subscribe to events\n\tand emit them within your application.\n</p>\n\n```Python title=\"Hello, World! Example\" linenums=\"1\"\nfrom pyventus import EventLinker, EventEmitter, AsyncIOEventEmitter\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<details markdown=\"1\" class=\"info\">\n<summary>You can also work with <code>async</code> functions and contexts...</summary>\n\n```Python title=\"Hello, World! Example (Async version)\" linenums=\"1\" hl_lines=\"5\"\nfrom pyventus import EventLinker, EventEmitter, AsyncIOEventEmitter\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</details>\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;As we can see from the <code>Hello, World!</code> example, Pyventus follows a simple and intuitive \n\tworkflow for defining and emitting events. Let's recap the essential steps involved:\n</p>\n\n<ol style='text-align: justify;'>\n\n<li>\n<b>Importing Necessary Modules:</b> \nWe first imported the required modules from Pyventus,  which encompassed the <code>EventLinker</code>\nclass, the <code>EventEmitter</code> interface, and the <code>AsyncIOEventEmitter</code> implementation.\n</li>\n\n<li>\n<b>Linking Events to Callbacks:</b> \nNext, we used the <code>@EventLinker.on()</code> decorator to define and link the string event <code>GreetEvent</code> \nto the function <code>handle_greet_event()</code>, which will print <i>'Hello, World!'</i> to the console whenever the\n<code>GreetEvent</code> is emitted.\n</li>\n\n<li>\n<b>Instantiating an Event Emitter:</b> \nAfter that, and in order to trigger our event, we needed to create an instance of the event emitter class. While \n<code>AsyncIOEventEmitter</code> was utilized, any built-in or custom implementation could be employed.\n</li>\n\n<li>\n<b>Triggering the Event:</b>\nFinally, by using the <code>emit()</code> method of the event emitter instance, we were able to trigger the \n<code>GreetEvent</code>, which resulted in the execution of the <code>handle_greet_event()</code> callback.\n</li>\n\n</ol>\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;Having gained a clear understanding of the workflow showcased in the <code>Hello, World!</code> example,\n\tyou are now well-equipped to explore more intricate event-driven scenarios and fully harness the capabilities of \n\tPyventus in your own projects. For a deep dive into the package's functionalities, you can refer to the \n\tPyventus Tutorials or API.\n</p>\n\n[//]: # (--------------------------------------------------------------------------------------------------------------)\n\n## A Practical Example\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;To showcase Pyventus' event-driven capabilities in a real-world scenario, we will explore a practical \n\texample of implementing a voltage sensor using an event-driven architecture (crucial for such scenarios). The \n\tpurpose of this example is to create an efficient voltage sensor that can seamlessly handle real-time data \n\tand respond appropriately to specific voltage conditions.\n</p>\n\n\n<details markdown=\"1\" class=\"quote\" open>\n<summary>Example \u2500 Monitoring Voltage Levels Across Devices (Context)</summary>\n\n<a style=\"text-align: center\" href=\"https://unsplash.com/photos/macro-photography-of-black-circuit-board-FO7JIlwjOtU?utm_content=creditShareLink&utm_medium=referral&utm_source=unsplash\" target=\"_blank\">\n\t<img src=\"https://mdapena.github.io/pyventus/images/examples/practical-example-index.jpg\" alt=\"Macro photography of black circuit board\">\n</a>\n\n<p style='text-align: justify;'>\n\t<i>&emsp;&emsp;A common aspect found in many systems is the need to monitor and respond to changes in sensor data.\n\tWhether it's pressure sensors, temperature sensors, or other types, capturing and reacting to sensor data is crucial\n\tfor effective system operation. In our practical example, we will focus on a specific scenario: building a sensor \n\tsystem that monitors voltage levels across devices. The goal of our voltage sensor is to detect potential issues,\n\tsuch as low or high voltage conditions, and respond appropriately in real-time.</i>\n</p>\n\n</details>\n\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;To accomplish our goal, we will define a <code>VoltageSensor</code> class to read voltage levels and emit \n\tevents based on predefined thresholds. We will create event handlers to respond to these events, performing actions \n\tsuch as activating eco-mode for low voltage or implementing high-voltage protection. Additionally, a shared event \n\thandler will provide general notifications for out-of-range voltage situations. The code example below illustrates \n\tthe implementation of this system.\n</p>\n\n```Python title=\"Voltage Sensor System with Pyventus (Practical Example)\" linenums=\"1\" hl_lines=\"9 14 27-30 35-36 41-42 47-48 55\"\nimport asyncio\nimport random\n\nfrom pyventus import EventEmitter, EventLinker, AsyncIOEventEmitter\n\n\nclass VoltageSensor:\n\n    def __init__(self, name: str, low: float, high: float, event_emitter: EventEmitter) -> 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) -> 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 < self._low:\n                self._event_emitter.emit(\"LowVoltageEvent\", sensor=self._name, voltage=voltage)\n            elif voltage > 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\"\ud83e\udeab Turning on eco-mode for '{sensor}'. ({voltage:.3g}v)\\n\")\n    # Perform action for low voltage...\n\n\n@EventLinker.on(\"HighVoltageEvent\")\nasync def handle_high_voltage_event(sensor: str, voltage: float):\n    print(f\"\u26a1 Starting high-voltage protection for '{sensor}'. ({voltage:.3g}v)\\n\")\n    # Perform action for high voltage...\n\n\n@EventLinker.on(\"LowVoltageEvent\", \"HighVoltageEvent\")\ndef 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=\"PressureSensor\", 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<p style='text-align: justify;'>\n    &emsp;&emsp;As we can see from this practical example, Pyventus enables us to easily build an event-driven system \n\tfor voltage sensors that is flexible, efficient, and highly responsive. With its intuitive API and support for both\n\tsynchronous and asynchronous operations, we were able to effectively monitor voltage levels, detect anomalies, and \n\ttrigger appropriate actions in real-time.\n</p>\n\n[//]: # (--------------------------------------------------------------------------------------------------------------)\n\n## Support for Synchronous and Asynchronous Code\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;Pyventus is designed from the ground up to seamlessly support both synchronous and asynchronous\n\tprogramming models. Its unified sync/async API allows you to define event callbacks and emit events across \n\t<code>sync</code> and <code>async</code> contexts.\n</p>\n\n### Subscribing Event Handlers with <code>Sync</code> and <code>Async</code> Callbacks\n\n```Python linenums=\"1\" hl_lines=\"2 7\"\n@EventLinker.on(\"MyEvent\")\ndef sync_event_callback():\n    pass  # Synchronous event handling\n\n\n@EventLinker.on(\"MyEvent\")\nasync def async_event_callback():\n\tpass  # Asynchronous event handling\n```\n\n<details markdown=\"1\" class=\"info\">\n<summary>You can optimize the execution of your callbacks based on their workload...</summary>\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;By default, event handlers in Pyventus are executed concurrently during an event emission, running their\n\t<code>sync</code> and <code>async</code> callbacks as defined. However, if you have a <code>sync</code> callback\n\tthat involves I/O or non-CPU bound operations, you can enable the <code>force_async</code> parameter to offload it\n\tto a thread pool, ensuring optimal performance and responsiveness. The <code>force_async</code> parameter utilizes \n\tthe <a href=\"https://docs.python.org/3/library/asyncio-task.html#running-in-threads\" target=\"_blank\"><code>asyncio.to_thread()</code></a>\n\tfunction to execute <code>sync</code> callbacks asynchronously.\n</p>\n\n```Python linenums=\"1\" hl_lines=\"1\"\n@EventLinker.on(\"BlockingIO\", force_async=True)\ndef 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</details>\n\n### Emitting Events from <code>Sync</code> and <code>Async</code> Contexts\n\n```Python linenums=\"1\" hl_lines=\"3 8\"\n# Emitting an event within a sync function\ndef sync_function(event_emitter: EventEmitter):\n    event_emitter.emit(\"MyEvent\")\n\n\n# Emitting an event within an async function\nasync def async_function(event_emitter: EventEmitter):\n    event_emitter.emit(\"MyEvent\")\n```\n\n<details markdown=\"1\" class=\"info\">\n<summary>Event propagation within different contexts...</summary>\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;While Pyventus provides a base <code>EventEmitter</code> class with a unified sync/async API, the \n\tspecific propagation behavior when emitting events may vary depending on the concrete <code>EventEmitter</code>\n\tused. For example, the <code>AsyncIOEventEmitter</code> implementation leverages the <code>AsyncIO</code> event\n\tloop to schedule callbacks added from asynchronous contexts without blocking. But alternative emitters could \n\tstructure propagation differently to suit their needs.\n</p>\n\n</details>\n\n[//]: # (--------------------------------------------------------------------------------------------------------------)\n\n## Runtime Flexibility and Customization\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;At its core, Pyventus utilizes a modular event emitter design that allows you to switch seamlessly\n\tbetween different built-in or custom event emitter implementations on the fly. Whether you opt for official emitters\n\tor decide to create your custom ones, Pyventus allows you to tailor the behavior and capabilities of the event \n\temitters to perfectly align with your unique requirements.\n</p>\n\n### Swapping Event Emitter Implementations at Runtime\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;By leveraging the principle of dependency inversion and using the base <code>EventEmitter</code> as a\n\tdependency, you can change the concrete implementation on the fly. Let's demonstrate this using the AsyncIO \n\tEvent Emitter and the Executor Event Emitter: \n</p>\n\n```Python title=\"Event Emitter Runtime Flexibility Example\" linenums=\"1\" hl_lines=\"10-11 14 16\"\nfrom pyventus import EventLinker, EventEmitter, AsyncIOEventEmitter, ExecutorEventEmitter\n\n\n@EventLinker.on(\"GreetEvent\")\ndef handle_greet_event(name: str = \"World\"):\n    print(f\"Hello, {name}!\")\n\n\nif __name__ == \"__main__\":\n    def main(event_emitter: EventEmitter) -> None:\n        event_emitter.emit(\"GreetEvent\", name=type(event_emitter).__name__)\n\n\n    main(event_emitter=AsyncIOEventEmitter())\n    with ExecutorEventEmitter() as executor_event_emitter:\n        main(event_emitter=executor_event_emitter)\n```\n\n### Defining Custom Event Emitters\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;To illustrate Pyventus' customization capabilities, we will define and implement a custom event emitter\n\tclass for the FastAPI framework. This class will efficiently handle the execution of event emissions through its \n\t<a href=\"https://fastapi.tiangolo.com/reference/background/\" target=\"_blank\">background tasks</a> workflow.\n</p>\n\n```Python title=\"Custom Event Emitter Example\" linenums=\"1\" hl_lines=\"6 10-11 13-14\"\nfrom fastapi import BackgroundTasks\n\nfrom pyventus import EventEmitter, EventLinker\n\n\nclass FastAPIEventEmitter(EventEmitter):\n    \"\"\"A custom event emitter that uses the FastAPI background tasks.\"\"\"\n\n    def __init__(self, background_tasks: BackgroundTasks):\n        super().__init__(event_linker=EventLinker, debug=False)\n        self._background_tasks = background_tasks\n\n    def _process(self, event_emission: EventEmitter.EventEmission) -> None:\n        self._background_tasks.add_task(event_emission)  # Process the event emission as a background task\n```\n\n<details markdown=\"1\" class=\"tip\">\n<summary>Official <code>FastAPIEventEmitter</code> Integration.</summary>\n\n<p style='text-align: justify;'>\n    In case you're interested in integrating Pyventus with FastAPI, you can refer to the official Pyventus \n\t<a href=\"https://mdapena.github.io/pyventus/0.6/tutorials/emitters/fastapi/\"><i>FastAPI Event Emitter</i></a> \n\timplementation.\n</p>\n\n</details>\n\n[//]: # (--------------------------------------------------------------------------------------------------------------)\n\n## Event Objects and Global Events\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;In addition to string events, Pyventus also supports Event Objects, which provide a structured way to \n\tdefine events and encapsulate relevant data payloads.\n</p>\n\n```Python title=\"Event Object Example\" linenums=\"1\" hl_lines=\"1-2 7-8 16-19\"\n@dataclass  # Define a Python dataclass representing the event and its payload.\nclass OrderCreatedEvent:\n    order_id: int\n    payload: dict[str, any]\n\n\n@EventLinker.on(OrderCreatedEvent)  # Subscribe event handlers to the event.\ndef handle_order_created_event(event: OrderCreatedEvent):\n    # Pyventus will automatically pass the Event Object \n    # as the first positional argument.\n    print(f\"Event Object: {event}\")\n\n\nevent_emitter: EventEmitter = AsyncIOEventEmitter()\nevent_emitter.emit(\n    event=OrderCreatedEvent(  # Emit an instance of the event!\n        order_id=6452879,\n        payload={},\n    ),\n)\n```\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;Furthermore, Pyventus provides support for Global Events, which are particularly useful for \n\timplementing cross-cutting concerns such as logging, monitoring, or analytics. By subscribing event handlers to\n\t<code>...</code> or <code>Ellipsis</code>, you can capture all events that may occur within that \n\t<code>EventLinker</code> context.\n</p>\n\n```Python title=\"Global Event Example\" linenums=\"1\" hl_lines=\"1\"\n@EventLinker.on(...)\ndef handle_any_event(*args, **kwargs):\n    print(f\"Perform logging...\\nArgs: {args}\\tKwargs: {kwargs}\")\n\n\nevent_emitter: EventEmitter = AsyncIOEventEmitter()\nevent_emitter.emit(\"GreetEvent\", name=\"Pyventus\")\n```\n\n[//]: # (--------------------------------------------------------------------------------------------------------------)\n\n## Success and Error Handling\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;With Pyventus, you can customize how events are handled upon completion, whether they succeed or \n\tencounter errors. This customization is achieved by using either the EventLinker's <code>on()</code> or \n\t<code>once()</code> decorator within a <code>with</code> statement block. Inside this block, you can \n\tdefine not only the event callbacks but also the overall workflow of the event. Now, let\u2019s explore \n\tthis simple yet powerful Pythonic syntax of Pyventus through an example.\n</p>\n\n```Python title=\"Success and Error Handling Example\" linenums=\"1\" hl_lines=\"4 6-7 10-11 14-15\"\nfrom pyventus import EventLinker, EventEmitter, AsyncIOEventEmitter\n\n# Create an event linker for the \"DivisionEvent\"\nwith EventLinker.on(\"DivisionEvent\") as linker:\n    @linker.on_event\n    def divide(a: float, b: float) -> float:\n        return a / b\n\n    @linker.on_success\n    def handle_success(result: float) -> None:\n        print(f\"Division result: {result:.3g}\")\n\n    @linker.on_failure\n    def handle_failure(e: Exception) -> None:\n        print(f\"Oops, something went wrong: {e}\")\n\nevent_emitter: EventEmitter = AsyncIOEventEmitter()  # Create an event emitter\nevent_emitter.emit(\"DivisionEvent\", a=1, b=0)  # Example: Division by zero\nevent_emitter.emit(\"DivisionEvent\", a=1, b=2)  # Example: Valid division\n```\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;As we have seen from the example, Pyventus offers a reliable and Pythonic solution for customizing \n\tevent handling. By utilizing the EventLinker and its decorators within a <code>with</code> statement block, we\n\twere able to define the <code>DivisionEvent</code> and specify the callbacks for division, success, and failure\n\tcases.\n</p>\n\n[//]: # (--------------------------------------------------------------------------------------------------------------)\n\n## Continuous Evolution\n\n<p style='text-align: justify;'>\n\t&emsp;&emsp;Pyventus continuously adapts to support developers across technological and programming domains. Its\n\taim is to remain at the forefront of event-driven design. Future development may introduce new official event \n\temitters, expanding compatibility with different technologies through seamless integration.\n</p>\n\n<p style='text-align: justify;'>\n\t&emsp;&emsp;Current default emitters provide reliable out-of-the-box capabilities for common use cases. They\n\tefficiently handle core event operations and lay the foundation for building event-driven applications.\n</p>\n\n<details markdown=\"1\" class=\"info\">\n<summary>Driving Innovation Through Collaboration</summary>\n\n<p style='text-align: justify;'>\n    &emsp;&emsp;Pyventus is an open source project that welcomes community involvement. If you wish to contribute\n\tadditional event emitters, improvements, or bug fixes, please check the <a href=\"https://mdapena.github.io/pyventus/latest/contributing/\" target=\"_blank\">Contributing</a> \n\tsection for guidelines on collaborating. Together, we can further the possibilities of event-driven development.\n</p>\n\n</details>\n\n[//]: # (--------------------------------------------------------------------------------------------------------------)\n\n## License\n\n<p style='text-align: justify;' markdown>\n    &emsp;&emsp;Pyventus is distributed as open source software and is released under the <a href=\"https://choosealicense.com/licenses/mit/\" target=\"_blank\">MIT License</a>. \n    You can view the full text of the license in the <a href=\"https://github.com/mdapena/pyventus/blob/master/LICENSE\" target=\"_blank\"><code>LICENSE</code></a> \n\tfile located in the Pyventus repository.\n</p>\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A powerful Python package for event-driven programming; define, emit, and orchestrate events with ease.",
    "version": "0.6.0",
    "project_urls": {
        "Changelog": "https://mdapena.github.io/pyventus/release-notes",
        "Documentation": "https://mdapena.github.io/pyventus",
        "Homepage": "https://github.com/mdapena/pyventus",
        "Repository": "https://github.com/mdapena/pyventus"
    },
    "split_keywords": [
        "event-dispatchers",
        " event-driven",
        " event-emitters",
        " event-handlers",
        " event-linkers",
        " events",
        " python"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3eb12519da990457d423e642975d4d5e3fc0c5696c8cb0c20356a74f38f07e08",
                "md5": "4c93a0a44a2acb1faf07e5c9eb18c2a5",
                "sha256": "fab55c04cc1c08c57af60116eb54e309c38245c0a4ef70d19b54f6c6bd44dafa"
            },
            "downloads": -1,
            "filename": "pyventus-0.6.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4c93a0a44a2acb1faf07e5c9eb18c2a5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 37110,
            "upload_time": "2024-10-19T13:52:20",
            "upload_time_iso_8601": "2024-10-19T13:52:20.369542Z",
            "url": "https://files.pythonhosted.org/packages/3e/b1/2519da990457d423e642975d4d5e3fc0c5696c8cb0c20356a74f38f07e08/pyventus-0.6.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "4c9dbe394945ec4cf31f6d49f02866ad3c3db9566b99470ead998580794db4da",
                "md5": "b2a76cb32baa48c2160394810953d1cf",
                "sha256": "1950f8ac7a6591fe3a36c66a79fdceea03ec34f9ad064b2e7e66120c8031cc6a"
            },
            "downloads": -1,
            "filename": "pyventus-0.6.0.tar.gz",
            "has_sig": false,
            "md5_digest": "b2a76cb32baa48c2160394810953d1cf",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 45086,
            "upload_time": "2024-10-19T13:52:22",
            "upload_time_iso_8601": "2024-10-19T13:52:22.315302Z",
            "url": "https://files.pythonhosted.org/packages/4c/9d/be394945ec4cf31f6d49f02866ad3c3db9566b99470ead998580794db4da/pyventus-0.6.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-10-19 13:52:22",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "mdapena",
    "github_project": "pyventus",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "pyventus"
}
        
Elapsed time: 5.05169s