asyncevents/README.md

85 lines
3.5 KiB
Markdown
Raw Normal View History

2021-12-24 19:06:08 +01:00
# asyncevents - Asynchronous event handling for modern Python
asyncevents is a small library to help developers perform asynchronous event handling in modern Python code.
## Features
- Priority queueing to allow for deterministic event triggering (with a catch, see below)
- Built-in exception handling (optional)
- The public API is fully type hinted for those sweet, sweet editor suggestions
- Public API is fully documented (and some private stuff too): you could write this from scratch in a couple of hours (like I did)
- Very small (~200 CLOC), although it can't fit on a postcard
__Note__: Deterministic event handling can only occur in blocking mode, i.e. when a call to `emit()` blocks until
all event handlers have run. If the non-blocking mode is used, handlers are started according to their priority,
but there's no telling on how they will be further scheduled to run and that depends entirely on the underlying
asyncio event loop
## Limitations
- Only compatible with asyncio due to the fact that other libraries such as trio and curio have wildly different design
goals (structured concurrency, separation of environments, etc.), which makes implementing some functionality
(i.e. the `wait()` method) tricky, if not outright impossible. Also those libraries, especially trio, already have
decent machinery to perform roughly what asyncevent does
- Does not support using any other loop than the currently running one because of some subtleties of modern asyncio
wrappers like `asyncio.run()` which creates its own event loop internally (_Thanks, asyncio_)
- Exceptions are kinda finicky in non-blocking mode due to how `asyncio.gather` works: only the first exception
in a group of handlers is properly raised and log messages might get doubled. Also, exception logging and propagation
is delayed until you `await wait("some_event")` so be careful
## Why?
This library exists because the current alternatives either suck, lack features or are inspired by other languages'
implementations of events like C# and Node.js: asyncevents aims to be a fully Pythonic library that provides just the
features you need and nothing more (nor nothing less).
## Cool! How do I use it?
Like this
```python3
import time
import asyncio
from asyncevents import on_event, emit, wait
@on_event("hello")
async def hello(_, event: str):
print(f"Hello {event!r}!")
@on_event("hi")
async def hi(_, event: str):
print(f"Hi {event!r}! I'm going to sleep for 5 seconds")
await asyncio.sleep(5) # Simulates some work
async def main():
print("Firing blocking event 'hello'")
await emit("hello") # This call blocks until hello() terminates
print("Handlers for event 'hello' have exited")
# Notice how, until here, the output is in order: this is on purpose!
# When using blocking mode, asyncevents even guarantees that handlers
# with different priorities will be executed in order
2021-12-24 19:09:34 +01:00
print("Firing non-blocking event 'hi'")
2021-12-24 19:06:08 +01:00
await emit("hi", block=False) # This one spawns hi() and returns immediately
print("Non-blocking event 'hello' fired")
await emit("event3") # Does nothing: No handlers registered for event3!
# We wait now for the the handler of the "hi" event to complete
t = time.time()
print("Waiting on event 'hi'")
await wait("hi") # Waits until all the handlers triggered by the "hi" event exit
print(f"Waited for {time.time() - t:.2f} seconds") # Should print roughly 5 seconds
if __name__ == "__main__":
asyncio.run(main())
```
## TODOs
- Documentation
- More tests
- Trio/curio backend (maybe)