Eventlib-py is a small & simple event framework for Python that can be used to decouple your code.
Feature overview:
- Fast - Pre-computed event chains for fast event emission.
- Lightweight - No extra dependencies.
- Asynchronous - Full support for asynchronous event handlers and context managers.
- Priority Ordering - Control the order in which event handlers are called.
- Monitoring - Use context managers to monitor event processing.
- Compatible - Works with almost anything, use: dataclasses, Pydantic or attrs.
import asyncio
import dataclasses
import eventlib
@dataclasses.dataclass
class MyEvent(eventlib.BaseEvent):
value: str
@eventlib.subscribe()
async def on_my_event(event: MyEvent):
print(f"Received: {event.value}")
asyncio.run(MyEvent("Hello, world!").emit_async()) # Prints: "Received: Hello, world!"
import eventlib
class MyEvent(eventlib.BaseEvent):
pass
class SubEvent(MyEvent):
pass
@eventlib.subscribe()
def on_base_event(event: MyEvent):
print("Received event", event.__class__.__name__)
SubEvent().emit() # Prints: "Received event SubEvent"
import eventlib
class MyEvent(eventlib.BaseEvent):
pass
@eventlib.subscribe(priority=-1)
def first():
print("first")
@eventlib.subscribe() # default: priority = 0
def second():
print("second")
@eventlib.subscribe(priority=1)
def third():
print("third")
MyEvent().emit() # Prints: "first", "second", "third"
import contextlib
import eventlib
class MyEvent(eventlib.BaseEvent):
pass
@eventlib.subscribe(priority=-1000) # Ensure that this is called first
@contextlib.contextmanager
def monitor(event: MyEvent):
print("Event received")
try:
yield
finally:
print("Event processed")
@eventlib.subscribe()
def on_event(event: MyEvent):
print("on_event")
MyEvent().emit() # Prints: "Event received", "on_event", "Event processed"
import asyncio
import contextlib
import eventlib
class MyEvent(eventlib.BaseEvent):
pass
@eventlib.subscribe(priority=-1000) # Ensure that this is called first
@contextlib.asynccontextmanager
async def monitor(event: MyEvent):
print("Event received")
try:
yield
finally:
print("Event processed")
@eventlib.subscribe()
async def async_on_event(event: MyEvent):
print("async_on_event")
@eventlib.subscribe()
def on_event(event: MyEvent):
print("on_event")
asyncio.run(MyEvent().emit_async()) # Prints: "Event received", "async_on_event", "on_event", "Event processed"
The benchmark directory contains code to measure the performance of the eventlib-py library and compare it with a hard-coded reference implementation in Python.
The following table shows the overhead of the eventlib-py library in the case_all benchmark. It's a mixed benchmark with all kinds of sync & async event handlers and context managers.
Quantile | Hardcoded Time/Event | EventLib Time/Event | Overhead per Call | EventLib Setup |
---|---|---|---|---|
0.50 | 42.289μs | 45.373μs | +7% | 135.125μs |
0.90 | 43.560μs | 46.809μs | +10% | 143.000μs |
0.99 | 46.658μs | 50.048μs | +15% | 260.393μs |
The overhead per call is the additional time that is needed to call the event handlers introduced by the eventlib-py library. The setup time is the additional nonrecurring overhead for subscribing the event handlers in the event system. It shows that in the worst case a 15% overhead per call is introduced. The expected median overhead is around 7% versus hard-coded event handling.
Use poetry to setup the development environment.
poetry install --with=dev
poetry shell
Run the auto-formatter, checks and linter:
black .
isort .
mypy .
pylint .
Run the tests with coverage:
pytest --cov
Contributions are welcome.
Please follow the commit convention https://www.conventionalcommits.org/en/v1.0.0/.
Dual-licensed under the terms of either the Apache License 2.0 or the MIT license.
SPDX-License-Identifier: (Apache-2.0 OR MIT)