cls.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. from dataclasses import dataclass
  2. from functools import wraps
  3. from typing import Any, Callable, Iterator, List, Type, TypeVar
  4. from pyee import EventEmitter
  5. @dataclass
  6. class Handler:
  7. event: str
  8. method: Callable
  9. class Handlers:
  10. def __init__(self) -> None:
  11. self._handlers: List[Handler] = []
  12. def append(self, handler) -> None:
  13. self._handlers.append(handler)
  14. def __iter__(self) -> Iterator[Handler]:
  15. return iter(self._handlers)
  16. def reset(self):
  17. self._handlers = []
  18. _handlers = Handlers()
  19. def on(event: str) -> Callable[[Callable], Callable]:
  20. """
  21. Register an event handler on an evented class. See the `evented` class
  22. decorator for a full example.
  23. """
  24. def decorator(method: Callable) -> Callable:
  25. _handlers.append(Handler(event=event, method=method))
  26. return method
  27. return decorator
  28. def _bind(self: Any, method: Any) -> Any:
  29. @wraps(method)
  30. def bound(*args, **kwargs) -> Any:
  31. return method(self, *args, **kwargs)
  32. return bound
  33. Cls = TypeVar("Cls", bound=Type)
  34. def evented(cls: Cls) -> Cls:
  35. """
  36. Configure an evented class.
  37. Evented classes are classes which use an EventEmitter to call instance
  38. methods during runtime. To achieve this without this helper, you would
  39. instantiate an `EventEmitter` in the `__init__` method and then call
  40. `event_emitter.on` for every method on `self`.
  41. This decorator and the `on` function help make things look a little nicer
  42. by defining the event handler on the method in the class and then adding
  43. the `__init__` hook in a wrapper:
  44. ```py
  45. from pyee.cls import evented, on
  46. @evented
  47. class Evented:
  48. @on("event")
  49. def event_handler(self, *args, **kwargs):
  50. print(self, args, kwargs)
  51. evented_obj = Evented()
  52. evented_obj.event_emitter.emit(
  53. "event", "hello world", numbers=[1, 2, 3]
  54. )
  55. ```
  56. The `__init__` wrapper will create a `self.event_emitter: EventEmitter`
  57. automatically but you can also define your own event_emitter inside your
  58. class's unwrapped `__init__` method. For example, to use this
  59. decorator with a `TwistedEventEmitter`::
  60. ```py
  61. @evented
  62. class Evented:
  63. def __init__(self):
  64. self.event_emitter = TwistedEventEmitter()
  65. @on("event")
  66. async def event_handler(self, *args, **kwargs):
  67. await self.some_async_action(*args, **kwargs)
  68. ```
  69. """
  70. handlers: List[Handler] = list(_handlers)
  71. _handlers.reset()
  72. og_init: Callable = cls.__init__
  73. @wraps(cls.__init__)
  74. def init(self: Any, *args: Any, **kwargs: Any) -> None:
  75. og_init(self, *args, **kwargs)
  76. if not hasattr(self, "event_emitter"):
  77. self.event_emitter = EventEmitter()
  78. for h in handlers:
  79. self.event_emitter.on(h.event, _bind(self, h.method))
  80. cls.__init__ = init
  81. return cls