AsyncEventEmitter now uses property getters and setters for cleaner API usage
This commit is contained in:
parent
234dcb448e
commit
2c3458120f
|
@ -69,7 +69,7 @@ async def main():
|
||||||
await emit("hello") # This call blocks until hello() terminates
|
await emit("hello") # This call blocks until hello() terminates
|
||||||
print("Handlers for event 'hello' have exited")
|
print("Handlers for event 'hello' have exited")
|
||||||
# Notice how, until here, the output is in order: this is on purpose!
|
# Notice how, until here, the output is in order: this is on purpose!
|
||||||
# When using blocking mode, asyncevents even guarantees that handlers
|
# When using blocking _mode, asyncevents even guarantees that handlers
|
||||||
# with different priorities will be executed in order
|
# with different priorities will be executed in order
|
||||||
print("Firing non-blocking event 'hi'")
|
print("Firing non-blocking event 'hi'")
|
||||||
await emit("hi", block=False) # This one spawns hi() and returns immediately
|
await emit("hi", block=False) # This one spawns hi() and returns immediately
|
||||||
|
|
|
@ -75,10 +75,12 @@ def on_event(event: str, priority: int = 0, emitter: AsyncEventEmitter = get_cur
|
||||||
def decorator(corofunc: Callable[[AsyncEventEmitter, str], Coroutine[Any, Any, Any]]):
|
def decorator(corofunc: Callable[[AsyncEventEmitter, str], Coroutine[Any, Any, Any]]):
|
||||||
emitter.register_event(event, corofunc, priority, oneshot)
|
emitter.register_event(event, corofunc, priority, oneshot)
|
||||||
|
|
||||||
@functools.wraps
|
@functools.wraps(corofunc)
|
||||||
async def wrapper(*args, **kwargs):
|
async def wrapper(*args, **kwargs):
|
||||||
return await corofunc(*args, **kwargs)
|
return await corofunc(*args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,18 +8,17 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import asyncio
|
|
||||||
from functools import partial
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from asyncevents.errors import UnknownEvent
|
|
||||||
from heapq import heappush, heapify, heappop
|
from heapq import heappush, heapify, heappop
|
||||||
from logging import Logger, getLogger, INFO, Formatter, StreamHandler
|
from logging import Logger, getLogger, INFO, Formatter, StreamHandler
|
||||||
from typing import Dict, List, Tuple, Coroutine, Callable, Any, Awaitable, Optional
|
from typing import Dict, List, Tuple, Coroutine, Callable, Any, Awaitable, Optional
|
||||||
|
|
||||||
from asyncevents.constants import ExceptionHandling, UnknownEventHandling, ExecutionMode
|
from asyncevents.constants import ExceptionHandling, UnknownEventHandling, ExecutionMode
|
||||||
|
from asyncevents.errors import UnknownEvent
|
||||||
|
|
||||||
|
|
||||||
class AsyncEventEmitter:
|
class AsyncEventEmitter:
|
||||||
|
@ -48,12 +47,13 @@ class AsyncEventEmitter:
|
||||||
prints a log message on the logging.WARNING level, and ERROR which raises an UnknownEvent exception)
|
prints a log message on the logging.WARNING level, and ERROR which raises an UnknownEvent exception)
|
||||||
Note: if the given callable is a coroutine, it is awaited, while it's called normally otherwise
|
Note: if the given callable is a coroutine, it is awaited, while it's called normally otherwise
|
||||||
and its return value is discarded
|
and its return value is discarded
|
||||||
:type on_unknown_event: Union[UnknownEventHandling, Callable[[AsyncEventEmitter, str], Coroutine[Any, Any, Any]]], optional
|
:type on_unknown_event: Union[UnknownEventHandling, Callable[[AsyncEventEmitter, str], Coroutine[Any, Any, Any]]],
|
||||||
|
optional
|
||||||
:param mode: Tells the emitter how event handlers should be spawned. It should be an entry of the
|
:param mode: Tells the emitter how event handlers should be spawned. It should be an entry of the
|
||||||
the asyncevents.ExecutionMode enum. If it is set to ExecutionMode.PAUSE, the default, the event
|
the asyncevents.ExecutionMode enum. If it is set to ExecutionMode.PAUSE, the default, the event
|
||||||
emitter spawns tasks by awaiting each matching handler: this causes it to pause on every handler.
|
emitter spawns tasks by awaiting each matching handler: this causes it to pause on every handler.
|
||||||
If ExecutionMode.NOWAIT is used, the emitter uses asyncio.create_task to spawns all the handlers
|
If ExecutionMode.NOWAIT is used, the emitter uses asyncio.create_task to spawns all the handlers
|
||||||
at the same time (note though that using this mode kind of breaks the priority queueing: the handlers
|
at the same time (note though that using this _mode kind of breaks the priority queueing: the handlers
|
||||||
are started according to their priorities, but once they are started they are handled by asyncio's
|
are started according to their priorities, but once they are started they are handled by asyncio's
|
||||||
event loop which is non-deterministic, so expect some disorder). Using ExecutionMode.NOWAIT allows
|
event loop which is non-deterministic, so expect some disorder). Using ExecutionMode.NOWAIT allows
|
||||||
to call the emitter's wait() method, which pauses until all currently running event handlers have
|
to call the emitter's wait() method, which pauses until all currently running event handlers have
|
||||||
|
@ -155,40 +155,9 @@ class AsyncEventEmitter:
|
||||||
Public object constructor
|
Public object constructor
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not inspect.iscoroutinefunction(on_error) and on_error not in ExceptionHandling:
|
|
||||||
if inspect.iscoroutine(on_unknown_event):
|
|
||||||
raise TypeError(
|
|
||||||
"on_unknown_event should be a coroutine *function*, not a coroutine! Pass the function"
|
|
||||||
" object without calling it!"
|
|
||||||
)
|
|
||||||
raise TypeError(
|
|
||||||
"expected on_error to be a coroutine function or an entry from the ExceptionHandling"
|
|
||||||
f" enum, found {type(on_error).__name__!r} instead"
|
|
||||||
)
|
|
||||||
if not inspect.iscoroutinefunction(on_unknown_event) and on_unknown_event not in UnknownEventHandling:
|
|
||||||
if inspect.iscoroutine(on_unknown_event):
|
|
||||||
raise TypeError(
|
|
||||||
"on_unknown_event should be a coroutine *function*, not a coroutine! Pass the function"
|
|
||||||
" object without calling it!"
|
|
||||||
)
|
|
||||||
raise TypeError(
|
|
||||||
"expected on_unknown_event to be a coroutine function or an entry from the"
|
|
||||||
f" UnknownEventHandling enum, found {type(on_unknown_event).__name__!r} instead"
|
|
||||||
)
|
|
||||||
if mode not in ExecutionMode:
|
|
||||||
raise TypeError(
|
|
||||||
f"expected mode to be an entry from the ExecutionMode enum, found {type(mode).__name__!r}" " instead"
|
|
||||||
)
|
|
||||||
self.on_error = on_error
|
self.on_error = on_error
|
||||||
self.on_unknown_event = on_unknown_event
|
self.on_unknown_event = on_unknown_event
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
# Determines the implementation of emit()
|
|
||||||
# and wait() according to the provided
|
|
||||||
# settings and the current Python version
|
|
||||||
if self.mode == ExecutionMode.PAUSE:
|
|
||||||
self._emit_impl = self._emit_await
|
|
||||||
else:
|
|
||||||
self._emit_impl = self._emit_nowait
|
|
||||||
self.logger: Logger = getLogger("asyncevents")
|
self.logger: Logger = getLogger("asyncevents")
|
||||||
self.logger.handlers = []
|
self.logger.handlers = []
|
||||||
self.logger.setLevel(INFO)
|
self.logger.setLevel(INFO)
|
||||||
|
@ -213,6 +182,122 @@ class AsyncEventEmitter:
|
||||||
str, List[Tuple[int, float, Callable[["AsyncEventEmitter", str], Coroutine[Any, Any, Any]], bool]]
|
str, List[Tuple[int, float, Callable[["AsyncEventEmitter", str], Coroutine[Any, Any, Any]], bool]]
|
||||||
] = defaultdict(list)
|
] = defaultdict(list)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_error(self) -> ExceptionHandling | Callable[["AsyncEventEmitter", str], Coroutine[Any, Any, Any]]:
|
||||||
|
"""
|
||||||
|
Property getter for on_error
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._on_error
|
||||||
|
|
||||||
|
@on_error.setter
|
||||||
|
def on_error(self, on_error: ExceptionHandling | Callable[["AsyncEventEmitter", str], Coroutine[Any, Any, Any]]):
|
||||||
|
"""
|
||||||
|
Property setter for on_error
|
||||||
|
|
||||||
|
:param on_error: Tells the emitter what to do when an exception occurs inside an event
|
||||||
|
handler. This value can either be an entry from the asyncevents.ExceptionHandling
|
||||||
|
enum or a coroutine function. If the passed object is a coroutine function, it is awaited
|
||||||
|
whenever an exception is caught with the AsyncEventEmitter instance, the exception
|
||||||
|
object and the event name as arguments (errors from the exception handler itself are
|
||||||
|
not caught). Defaults to ExceptionHandling.PROPAGATE, which lets exceptions fall trough
|
||||||
|
the execution chain (other enum values are LOG, which prints a log message on the
|
||||||
|
logging.ERROR level, and IGNORE which silences the exception entirely)
|
||||||
|
:type on_error: Union[ExceptionHandling, Callable[[AsyncEventEmitter, Exception, str], Coroutine[Any, Any, Any]]],
|
||||||
|
optional
|
||||||
|
:raises:
|
||||||
|
TypeError: If the provided handler is not valid
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not inspect.iscoroutinefunction(on_error) and on_error not in ExceptionHandling:
|
||||||
|
if inspect.iscoroutine(on_error):
|
||||||
|
raise TypeError(
|
||||||
|
"on_error should be a coroutine *function*, not a coroutine! Pass the function"
|
||||||
|
" object without calling it!"
|
||||||
|
)
|
||||||
|
raise TypeError(
|
||||||
|
"expected on_error to be a coroutine function or an entry from the ExceptionHandling"
|
||||||
|
f" enum, found {type(on_error).__name__!r} instead"
|
||||||
|
)
|
||||||
|
self._on_error = on_error
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_unknown_event(self) -> UnknownEventHandling | Callable[["AsyncEventEmitter", str], Coroutine[Any, Any, Any]]:
|
||||||
|
"""
|
||||||
|
Property getter for on_unknown_event
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._on_unknown_event
|
||||||
|
|
||||||
|
@on_unknown_event.setter
|
||||||
|
def on_unknown_event(
|
||||||
|
self,
|
||||||
|
on_unknown_event: UnknownEventHandling
|
||||||
|
| Callable[["AsyncEventEmitter", str], Coroutine[Any, Any, Any]] = UnknownEventHandling.IGNORE,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Property setter for on_unknown_event
|
||||||
|
|
||||||
|
:param on_unknown_event: Tells the emitter what to do when an unknown event is triggered. An
|
||||||
|
unknown event is an event for which no handler is registered (either because it has never
|
||||||
|
been registered or because all of its handlers have been removed). This value can either be
|
||||||
|
an entry from the asyncevents.UnknownEventHandling enum or a coroutine function. If the argument
|
||||||
|
is a coroutine function, it is awaited with the AsyncEventEmitter instance and the event name as arguments.
|
||||||
|
Defaults to UnknownEventHandling.IGNORE, which does nothing (other enum values are LOG, which
|
||||||
|
prints a log message on the logging.WARNING level, and ERROR which raises an UnknownEvent exception)
|
||||||
|
Note: if the given callable is a coroutine, it is awaited, while it's called normally otherwise
|
||||||
|
and its return value is discarded
|
||||||
|
:type on_unknown_event: Union[UnknownEventHandling, Callable[[AsyncEventEmitter, str], Coroutine[Any, Any, Any]]],
|
||||||
|
optional
|
||||||
|
:raises:
|
||||||
|
TypeError: If the provided handler is not valid
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not inspect.iscoroutinefunction(on_unknown_event) and on_unknown_event not in UnknownEventHandling:
|
||||||
|
if inspect.iscoroutine(on_unknown_event):
|
||||||
|
raise TypeError(
|
||||||
|
"on_unknown_event should be a coroutine *function*, not a coroutine! Pass the function"
|
||||||
|
" object without calling it!"
|
||||||
|
)
|
||||||
|
raise TypeError(
|
||||||
|
"expected on_unknown_event to be a coroutine function or an entry from the"
|
||||||
|
f" UnknownEventHandling enum, found {type(on_unknown_event).__name__!r} instead"
|
||||||
|
)
|
||||||
|
self._on_unknown_event = on_unknown_event
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mode(self):
|
||||||
|
"""
|
||||||
|
Property getter for mode
|
||||||
|
"""
|
||||||
|
return self._mode
|
||||||
|
|
||||||
|
@mode.setter
|
||||||
|
def mode(self, mode: ExecutionMode):
|
||||||
|
"""
|
||||||
|
Property setter for mode
|
||||||
|
|
||||||
|
:param mode: Tells the emitter how event handlers should be spawned. It should be an entry of the
|
||||||
|
the asyncevents.ExecutionMode enum. If it is set to ExecutionMode.PAUSE, the default, the event
|
||||||
|
emitter spawns tasks by awaiting each matching handler: this causes it to pause on every handler.
|
||||||
|
If ExecutionMode.NOWAIT is used, the emitter uses asyncio.create_task to spawns all the handlers
|
||||||
|
at the same time (note though that using this _mode kind of breaks the priority queueing: the handlers
|
||||||
|
are started according to their priorities, but once they are started they are handled by asyncio's
|
||||||
|
event loop which is non-deterministic, so expect some disorder). Using ExecutionMode.NOWAIT allows
|
||||||
|
to call the emitter's wait() method, which pauses until all currently running event handlers have
|
||||||
|
completed executing (when ExecutionMode.PAUSE is used, wait() is a no-op), but note that return
|
||||||
|
values from event handlers are not returned
|
||||||
|
:type mode: ExecutionMode
|
||||||
|
:raises:
|
||||||
|
TypeError: If the given mode is invalid
|
||||||
|
"""
|
||||||
|
|
||||||
|
if mode not in ExecutionMode:
|
||||||
|
raise TypeError(
|
||||||
|
f"expected mode to be an entry from the ExecutionMode enum, found {type(mode).__name__!r}" " instead"
|
||||||
|
)
|
||||||
|
self._mode = mode
|
||||||
|
|
||||||
def exists(self, event: str) -> bool:
|
def exists(self, event: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns if the given event has at least
|
Returns if the given event has at least
|
||||||
|
@ -260,16 +345,13 @@ class AsyncEventEmitter:
|
||||||
"""
|
"""
|
||||||
Unregisters all handlers for the given
|
Unregisters all handlers for the given
|
||||||
event in one go. Does nothing if the
|
event in one go. Does nothing if the
|
||||||
given event is not registered already and
|
given event is not registered already.
|
||||||
raise_on_missing equals False (the default).
|
|
||||||
Note that this does not affect any
|
Note that this does not affect any
|
||||||
already started event handler for the
|
already started event handler for the
|
||||||
given event
|
given event
|
||||||
|
|
||||||
:param event: The event name
|
:param event: The event name
|
||||||
:type event: str
|
:type event: str
|
||||||
:raises:
|
|
||||||
UnknownEvent: If self.on_unknown_error == UnknownEventHandling.ERROR
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.handlers.pop(event, None)
|
self.handlers.pop(event, None)
|
||||||
|
@ -279,10 +361,8 @@ class AsyncEventEmitter:
|
||||||
) -> None | bool | Tuple[int, float, Callable[["AsyncEventEmitter", str], Coroutine[Any, Any, Any]], bool]:
|
) -> None | bool | Tuple[int, float, Callable[["AsyncEventEmitter", str], Coroutine[Any, Any, Any]], bool]:
|
||||||
"""
|
"""
|
||||||
Returns the tuple of (priority, date, corofunc, oneshot) representing the
|
Returns the tuple of (priority, date, corofunc, oneshot) representing the
|
||||||
given handler. Only the first matching entry is returned. If
|
given handler. Only the first matching entry is returned. False is returned
|
||||||
raise_on_missing is False, None is returned if the given
|
if the given handler is not registered for the given event.
|
||||||
event does not exist. False is returned if the given
|
|
||||||
handler is not registered for the given event
|
|
||||||
|
|
||||||
Note: This method is meant mostly for internal use
|
Note: This method is meant mostly for internal use
|
||||||
|
|
||||||
|
@ -369,7 +449,7 @@ class AsyncEventEmitter:
|
||||||
may raise errors or log to stderr
|
may raise errors or log to stderr
|
||||||
:type event: str
|
:type event: str
|
||||||
:param block: Temporarily overrides the emitter's global execution
|
:param block: Temporarily overrides the emitter's global execution
|
||||||
mode. If block is True, the default, this call will pause until
|
_mode. If block is True, the default, this call will pause until
|
||||||
execution of all event handlers has finished, otherwise it returns
|
execution of all event handlers has finished, otherwise it returns
|
||||||
as soon as they're scheduled
|
as soon as they're scheduled
|
||||||
:type block: bool, optional
|
:type block: bool, optional
|
||||||
|
@ -378,12 +458,11 @@ class AsyncEventEmitter:
|
||||||
and the given event is not registered
|
and the given event is not registered
|
||||||
"""
|
"""
|
||||||
|
|
||||||
mode = self.mode
|
mode = self._mode
|
||||||
if block:
|
if block:
|
||||||
self.mode = ExecutionMode.PAUSE
|
self._mode = ExecutionMode.PAUSE
|
||||||
self._emit_impl = self._emit_await
|
await self._emit_await(event)
|
||||||
else:
|
else:
|
||||||
self.mode = ExecutionMode.NOWAIT
|
self._mode = ExecutionMode.NOWAIT
|
||||||
self._emit_impl = self._emit_nowait
|
await self._emit_nowait(event)
|
||||||
await self._emit_impl(event)
|
self._mode = mode
|
||||||
self.mode = mode
|
|
||||||
|
|
Loading…
Reference in New Issue