uplift.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. # -*- coding: utf-8 -*-
  2. from functools import wraps
  3. from typing import Any, Callable, cast, Dict, Optional, Tuple, Type, TypeVar, Union
  4. import warnings
  5. from typing_extensions import Literal
  6. from pyee.base import EventEmitter
  7. UpliftingEventEmitter = TypeVar("UpliftingEventEmitter", bound=EventEmitter)
  8. EMIT_WRAPPERS: Dict[EventEmitter, Callable[[], None]] = dict()
  9. def unwrap(event_emitter: EventEmitter) -> None:
  10. """Unwrap an uplifted EventEmitter, returning it to its prior state."""
  11. if event_emitter in EMIT_WRAPPERS:
  12. EMIT_WRAPPERS[event_emitter]()
  13. def _wrap(
  14. left: EventEmitter,
  15. right: EventEmitter,
  16. error_handler: Any,
  17. proxy_new_listener: bool,
  18. ) -> None:
  19. left_emit = left.emit
  20. left_unwrap: Optional[Callable[[], None]] = EMIT_WRAPPERS.get(left)
  21. @wraps(left_emit)
  22. def wrapped_emit(event: str, *args: Any, **kwargs: Any) -> bool:
  23. left_handled: bool = left._call_handlers(event, args, kwargs)
  24. # Do it for the right side
  25. if proxy_new_listener or event != "new_listener":
  26. right_handled = right._call_handlers(event, args, kwargs)
  27. else:
  28. right_handled = False
  29. handled = left_handled or right_handled
  30. # Use the error handling on `error_handler` (should either be
  31. # `left` or `right`)
  32. if not handled:
  33. error_handler._emit_handle_potential_error(event, args[0] if args else None)
  34. return handled
  35. def _unwrap() -> None:
  36. warnings.warn(
  37. DeprecationWarning(
  38. "Patched ee.unwrap() is deprecated and will be removed in a "
  39. "future release. Use pyee.uplift.unwrap instead."
  40. )
  41. )
  42. unwrap(left)
  43. def unwrap_hook() -> None:
  44. cast(Any, left).emit = left_emit
  45. if left_unwrap:
  46. EMIT_WRAPPERS[left] = left_unwrap
  47. else:
  48. del EMIT_WRAPPERS[left]
  49. del left.unwrap # type: ignore
  50. cast(Any, left).emit = left_emit
  51. unwrap(right)
  52. cast(Any, left).emit = wrapped_emit
  53. EMIT_WRAPPERS[left] = unwrap_hook
  54. left.unwrap = _unwrap # type: ignore
  55. _PROXY_NEW_LISTENER_SETTINGS: Dict[str, Tuple[bool, bool]] = dict(
  56. forward=(False, True),
  57. backward=(True, False),
  58. both=(True, True),
  59. neither=(False, False),
  60. )
  61. ErrorStrategy = Union[Literal["new"], Literal["underlying"], Literal["neither"]]
  62. ProxyStrategy = Union[
  63. Literal["forward"], Literal["backward"], Literal["both"], Literal["neither"]
  64. ]
  65. def uplift(
  66. cls: Type[UpliftingEventEmitter],
  67. underlying: EventEmitter,
  68. error_handling: ErrorStrategy = "new",
  69. proxy_new_listener: ProxyStrategy = "forward",
  70. *args: Any,
  71. **kwargs: Any,
  72. ) -> UpliftingEventEmitter:
  73. """A helper to create instances of an event emitter `cls` that inherits
  74. event behavior from an `underlying` event emitter instance.
  75. This is mostly helpful if you have a simple underlying event emitter
  76. that you don't have direct control over, but you want to use that
  77. event emitter in a new context - for example, you may want to `uplift` a
  78. `EventEmitter` supplied by a third party library into an
  79. `AsyncIOEventEmitter` so that you may register async event handlers
  80. in your `asyncio` app but still be able to receive events from the
  81. underlying event emitter and call the underlying event emitter's existing
  82. handlers.
  83. When called, `uplift` instantiates a new instance of `cls`, passing
  84. along any unrecognized arguments, and overwrites the `emit` method on
  85. the `underlying` event emitter to also emit events on the new event
  86. emitter and vice versa. In both cases, they return whether the `emit`
  87. method was handled by either emitter. Execution order prefers the event
  88. emitter on which `emit` was called.
  89. The `unwrap` function may be called on either instance; this will
  90. unwrap both `emit` methods.
  91. The `error_handling` flag can be configured to control what happens to
  92. unhandled errors:
  93. - 'new': Error handling for the new event emitter is always used and the
  94. underlying library's non-event-based error handling is inert.
  95. - 'underlying': Error handling on the underlying event emitter is always
  96. used and the new event emitter can not implement non-event-based error
  97. handling.
  98. - 'neither': Error handling for the new event emitter is used if the
  99. handler was registered on the new event emitter, and vice versa.
  100. Tuning this option can be useful depending on how the underlying event
  101. emitter does error handling. The default is 'new'.
  102. The `proxy_new_listener` option can be configured to control how
  103. `new_listener` events are treated:
  104. - 'forward': `new_listener` events are propagated from the underlying
  105. event emitter to the new event emitter but not vice versa.
  106. - 'both': `new_listener` events are propagated as with other events.
  107. - 'neither': `new_listener` events are only fired on their respective
  108. event emitters.
  109. - 'backward': `new_listener` events are propagated from the new event
  110. emitter to the underlying event emitter, but not vice versa.
  111. Tuning this option can be useful depending on how the `new_listener`
  112. event is used by the underlying event emitter, if at all. The default is
  113. 'forward', since `underlying` may not know how to handle certain
  114. handlers, such as asyncio coroutines.
  115. Each event emitter tracks its own internal table of handlers.
  116. `remove_listener`, `remove_all_listeners` and `listeners` all
  117. work independently. This means you will have to remember which event
  118. emitter an event handler was added to!
  119. Note that both the new event emitter returned by `cls` and the
  120. underlying event emitter should inherit from `EventEmitter`, or at
  121. least implement the interface for the undocumented `_call_handlers` and
  122. `_emit_handle_potential_error` methods.
  123. """
  124. (
  125. new_proxy_new_listener,
  126. underlying_proxy_new_listener,
  127. ) = _PROXY_NEW_LISTENER_SETTINGS[proxy_new_listener]
  128. new: UpliftingEventEmitter = cls(*args, **kwargs)
  129. uplift_error_handlers: Dict[str, Tuple[EventEmitter, EventEmitter]] = dict(
  130. new=(new, new), underlying=(underlying, underlying), neither=(new, underlying)
  131. )
  132. new_error_handler, underlying_error_handler = uplift_error_handlers[error_handling]
  133. _wrap(new, underlying, new_error_handler, new_proxy_new_listener)
  134. _wrap(underlying, new, underlying_error_handler, underlying_proxy_new_listener)
  135. return new