diff --git a/README.md b/README.md index dac3a3b..10a16a9 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ async def main(): # with different priorities will be executed in order print("Firing non-blocking event 'hi'") await emit("hi", block=False) # This one spawns hi() and returns immediately - print("Non-blocking event 'hello' fired") + print("Non-blocking event 'hi' 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() @@ -76,6 +76,11 @@ if __name__ == "__main__": asyncio.run(main()) ``` +__Note__: This example showed that the event names match the functions' names: this is just for explanatory purposes! +It's not compulsory for your event and their respective handlers' names to match. You can also register as many +functions you want for the same or multiple events and asyncevents will call them all when one of them is fired. +For more usage examples (until the documentation is done), check out the tests directory or read the source code: +it's pretty straightforward, I promise. ## TODOs diff --git a/asyncevents/events.py b/asyncevents/events.py index 4881afa..f37d5ba 100644 --- a/asyncevents/events.py +++ b/asyncevents/events.py @@ -72,7 +72,7 @@ class AsyncEventEmitter: :type event: str """ - if self.handlers.get(event, None) is None: + if not self.handlers.get(event): if self.on_unknown_event == UnknownEventHandling.IGNORE: return elif self.on_unknown_event == UnknownEventHandling.ERROR: @@ -83,7 +83,7 @@ class AsyncEventEmitter: # It's a coroutine! Call it await self.on_unknown_event(self, event) - async def _catch_errors_in_awaitable(self, event: str, obj: Awaitable): + async def _handle_errors_in_awaitable(self, event: str, obj: Awaitable): # Thanks to asyncio's *utterly amazing* (HUGE sarcasm there) # exception handling, we have to make this wrapper so we can # catch errors on a per-handler basis @@ -125,7 +125,7 @@ class AsyncEventEmitter: temp[-1][-2], ) ) - self._tasks.append((event, asyncio.create_task(self._catch_errors_in_awaitable(event, task)))) + self._tasks.append((event, asyncio.create_task(self._handle_errors_in_awaitable(event, task)))) # We push back the elements for t in temp: heappush(self.handlers[event], t) @@ -145,7 +145,7 @@ class AsyncEventEmitter: t = heappop(temp) if t[-1]: self.unregister_handler(event, t[-2]) - await self._catch_errors_in_awaitable(event, t[-2](self, event)) + await self._handle_errors_in_awaitable(event, t[-2](self, event)) def __init__( self, diff --git a/tests/blocking.py b/tests/blocking.py new file mode 100644 index 0000000..4e1fcc6 --- /dev/null +++ b/tests/blocking.py @@ -0,0 +1,17 @@ +import asyncio +from asyncevents import on_event, emit + + +@on_event("hello") +async def hello(_, event: str): + print(f"Hello {event!r}!") + + +async def main(): + print("Firing blocking event 'hello'") + await emit("hello") + print("Handlers for event 'hello' have exited") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/multiple_events_one_handler.py b/tests/multiple_events_one_handler.py new file mode 100644 index 0000000..8fa0c05 --- /dev/null +++ b/tests/multiple_events_one_handler.py @@ -0,0 +1,21 @@ +import asyncio +from asyncevents import on_event, emit + + +@on_event("test") +@on_event("hello") +async def hello(_, event: str): + print(f"Hello {event!r}!") + + +async def main(): + print("Firing blocking event 'hello'") + await emit("hello") + print("Handlers for event 'hello' have exited") + print("Firing blocking event 'test'") + await emit("test") + print("Handlers for event 'test' have exited") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/non_blocking.py b/tests/non_blocking.py new file mode 100644 index 0000000..02b32db --- /dev/null +++ b/tests/non_blocking.py @@ -0,0 +1,23 @@ +import time +import asyncio +from asyncevents import on_event, emit, wait + + +@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("Emitting event 'hi'") + await emit("hi", block=False) + print("Event 'hi' fired") + t = time.time() + print("Waiting on event 'hi'") + await wait("hi") + print(f"Waited for {time.time() - t:.2f} seconds") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/on_error.py b/tests/on_error.py index 9e7d72e..2225167 100644 --- a/tests/on_error.py +++ b/tests/on_error.py @@ -4,10 +4,14 @@ from asyncevents import on_event, emit, get_current_emitter, ExceptionHandling @on_event("error") async def oh_no(_, event: str): - print("Goodbye!") + print(f"Goodbye after {event!r}!") raise ValueError("D:") +async def handle_error(_, exc: Exception, event: str): + print(f"Exception {type(exc).__name__!r} from {event!r} handled!") + + async def main(): try: await emit("error") # The error propagates @@ -21,6 +25,10 @@ async def main(): get_current_emitter().on_error = ExceptionHandling.IGNORE # Silences the exception await emit("error") # This won't raise nor log anything to the console. Yay x2! print("We're safe again!") + # Let's try using a coroutine function as an exception handler + get_current_emitter().on_error = handle_error + await emit("error") # This will call handle_error with the exception object and the event name + print("We're safe once again!") if __name__ == "__main__": diff --git a/tests/on_unknown_event.py b/tests/on_unknown_event.py new file mode 100644 index 0000000..a594d2c --- /dev/null +++ b/tests/on_unknown_event.py @@ -0,0 +1,32 @@ +import asyncio +from asyncevents import on_event, emit, get_current_emitter, UnknownEventHandling +from asyncevents.errors import UnknownEvent + + +@on_event("test") +@on_event("test2") +async def oh_no(_, event: str): + print(f"The event {event!r} definitely exists!") + + +async def handle_unknown_event(_, event: str): + print(f"The event {event!r} definitely does not exist!") + + +async def main(): + await emit("test") + await emit("test2") + await emit("test3") # Does nothing by default + get_current_emitter().on_unknown_event = UnknownEventHandling.LOG + await emit("test3") # Logs an error message + get_current_emitter().on_unknown_event = UnknownEventHandling.ERROR # Raises an exception + try: + await emit("test3") + except UnknownEvent: + print("Bang!") + get_current_emitter().on_unknown_event = handle_unknown_event # Calls the function with the event as argument + await emit("test3") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/oneshot.py b/tests/oneshot.py new file mode 100644 index 0000000..45ef4b2 --- /dev/null +++ b/tests/oneshot.py @@ -0,0 +1,18 @@ +import asyncio +from asyncevents import on_event, emit + + +@on_event("hello", oneshot=True) # The handler is removed after it fires once +async def hello(_, event: str): + print(f"Hello {event!r}!") + + +async def main(): + print("Firing blocking event 'hello'") + await emit("hello") + print("Handlers for event 'hello' have exited") + await emit("hello") # Nothing happens + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/simple_example.py b/tests/simple_example.py deleted file mode 100644 index c284e95..0000000 --- a/tests/simple_example.py +++ /dev/null @@ -1,36 +0,0 @@ -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 - print("Firing non-blocking event 'hi'") - 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())