events.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """Infrastructure for registering and firing callbacks on application events.
  2. Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to
  3. be called at specific times, or a collection of alternative methods to try,
  4. callbacks are designed to be used by extension authors. A number of callbacks
  5. can be registered for the same event without needing to be aware of one another.
  6. The functions defined in this module are no-ops indicating the names of available
  7. events and the arguments which will be passed to them.
  8. .. note::
  9. This API is experimental in IPython 2.0, and may be revised in future versions.
  10. """
  11. from __future__ import annotations
  12. from typing import TYPE_CHECKING, Any, Callable, Iterable, TypeVar
  13. if TYPE_CHECKING:
  14. from IPython.core.interactiveshell import (
  15. ExecutionInfo,
  16. ExecutionResult,
  17. InteractiveShell,
  18. )
  19. class EventManager:
  20. """Manage a collection of events and a sequence of callbacks for each.
  21. This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell`
  22. instances as an ``events`` attribute.
  23. .. note::
  24. This API is experimental in IPython 2.0, and may be revised in future versions.
  25. """
  26. def __init__(
  27. self,
  28. shell: InteractiveShell,
  29. available_events: Iterable[str],
  30. print_on_error: bool = True,
  31. ) -> None:
  32. """Initialise the :class:`CallbackManager`.
  33. Parameters
  34. ----------
  35. shell
  36. The :class:`~IPython.core.interactiveshell.InteractiveShell` instance
  37. available_events
  38. An iterable of names for callback events.
  39. print_on_error:
  40. A boolean flag to set whether the EventManager will print a warning which a event errors.
  41. """
  42. self.shell = shell
  43. self.callbacks: dict[str, list[Callable[..., Any]]] = {
  44. n: [] for n in available_events
  45. }
  46. self.print_on_error = print_on_error
  47. def register(self, event: str, function: Callable[..., Any]) -> None:
  48. """Register a new event callback.
  49. Parameters
  50. ----------
  51. event : str
  52. The event for which to register this callback.
  53. function : callable
  54. A function to be called on the given event. It should take the same
  55. parameters as the appropriate callback prototype.
  56. Raises
  57. ------
  58. TypeError
  59. If ``function`` is not callable.
  60. KeyError
  61. If ``event`` is not one of the known events.
  62. """
  63. if not callable(function):
  64. raise TypeError('Need a callable, got %r' % function)
  65. if function not in self.callbacks[event]:
  66. self.callbacks[event].append(function)
  67. def unregister(self, event: str, function: Callable[..., Any]) -> None:
  68. """Remove a callback from the given event."""
  69. if function in self.callbacks[event]:
  70. return self.callbacks[event].remove(function)
  71. raise ValueError('Function {!r} is not registered as a {} callback'.format(function, event))
  72. def trigger(self, event: str, *args: Any, **kwargs: Any) -> None:
  73. """Call callbacks for ``event``.
  74. Any additional arguments are passed to all callbacks registered for this
  75. event. Exceptions raised by callbacks are caught, and a message printed.
  76. """
  77. for func in self.callbacks[event][:]:
  78. try:
  79. func(*args, **kwargs)
  80. except (Exception, KeyboardInterrupt):
  81. if self.print_on_error:
  82. print(
  83. "Error in callback {} (for {}), with arguments args {},kwargs {}:".format(
  84. func, event, args, kwargs
  85. )
  86. )
  87. self.shell.showtraceback()
  88. # event_name -> prototype mapping
  89. available_events: dict[str, Callable[..., Any]] = {}
  90. _CallbackT = TypeVar("_CallbackT", bound=Callable[..., Any])
  91. def _define_event(callback_function: _CallbackT) -> _CallbackT:
  92. """Decorator to register a function as an available event prototype."""
  93. available_events[callback_function.__name__] = callback_function
  94. return callback_function
  95. # ------------------------------------------------------------------------------
  96. # Callback prototypes
  97. #
  98. # No-op functions which describe the names of available events and the
  99. # signatures of callbacks for those events.
  100. # ------------------------------------------------------------------------------
  101. @_define_event
  102. def pre_execute() -> None:
  103. """Fires before code is executed in response to user/frontend action.
  104. This includes comm and widget messages and silent execution, as well as user
  105. code cells.
  106. """
  107. pass
  108. @_define_event
  109. def pre_run_cell(info: ExecutionInfo) -> None:
  110. """Fires before user-entered code runs.
  111. Parameters
  112. ----------
  113. info : :class:`~IPython.core.interactiveshell.ExecutionInfo`
  114. An object containing information used for the code execution.
  115. """
  116. pass
  117. @_define_event
  118. def post_execute() -> None:
  119. """Fires after code is executed in response to user/frontend action.
  120. This includes comm and widget messages and silent execution, as well as user
  121. code cells.
  122. """
  123. pass
  124. @_define_event
  125. def post_run_cell(result: ExecutionResult) -> None:
  126. """Fires after user-entered code runs.
  127. Parameters
  128. ----------
  129. result : :class:`~IPython.core.interactiveshell.ExecutionResult`
  130. The object which will be returned as the execution result.
  131. """
  132. pass
  133. @_define_event
  134. def shell_initialized(ip: InteractiveShell) -> None:
  135. """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
  136. This is before extensions and startup scripts are loaded, so it can only be
  137. set by subclassing.
  138. Parameters
  139. ----------
  140. ip : :class:`~IPython.core.interactiveshell.InteractiveShell`
  141. The newly initialised shell.
  142. """
  143. pass