base.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. # event/base.py
  2. # Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. """Base implementation classes.
  8. The public-facing ``Events`` serves as the base class for an event interface;
  9. its public attributes represent different kinds of events. These attributes
  10. are mirrored onto a ``_Dispatch`` class, which serves as a container for
  11. collections of listener functions. These collections are represented both
  12. at the class level of a particular ``_Dispatch`` class as well as within
  13. instances of ``_Dispatch``.
  14. """
  15. from __future__ import annotations
  16. import typing
  17. from typing import Any
  18. from typing import cast
  19. from typing import Dict
  20. from typing import Generic
  21. from typing import Iterator
  22. from typing import List
  23. from typing import Mapping
  24. from typing import MutableMapping
  25. from typing import Optional
  26. from typing import overload
  27. from typing import Tuple
  28. from typing import Type
  29. from typing import Union
  30. import weakref
  31. from .attr import _ClsLevelDispatch
  32. from .attr import _EmptyListener
  33. from .attr import _InstanceLevelDispatch
  34. from .attr import _JoinedListener
  35. from .registry import _ET
  36. from .registry import _EventKey
  37. from .. import util
  38. from ..util.typing import Literal
  39. _registrars: MutableMapping[
  40. str, List[Type[_HasEventsDispatch[Any]]]
  41. ] = util.defaultdict(list)
  42. def _is_event_name(name: str) -> bool:
  43. # _sa_event prefix is special to support internal-only event names.
  44. # most event names are just plain method names that aren't
  45. # underscored.
  46. return (
  47. not name.startswith("_") and name != "dispatch"
  48. ) or name.startswith("_sa_event")
  49. class _UnpickleDispatch:
  50. """Serializable callable that re-generates an instance of
  51. :class:`_Dispatch` given a particular :class:`.Events` subclass.
  52. """
  53. def __call__(self, _instance_cls: Type[_ET]) -> _Dispatch[_ET]:
  54. for cls in _instance_cls.__mro__:
  55. if "dispatch" in cls.__dict__:
  56. return cast(
  57. "_Dispatch[_ET]", cls.__dict__["dispatch"].dispatch
  58. )._for_class(_instance_cls)
  59. else:
  60. raise AttributeError("No class with a 'dispatch' member present.")
  61. class _DispatchCommon(Generic[_ET]):
  62. __slots__ = ()
  63. _instance_cls: Optional[Type[_ET]]
  64. def _join(self, other: _DispatchCommon[_ET]) -> _JoinedDispatcher[_ET]:
  65. raise NotImplementedError()
  66. def __getattr__(self, name: str) -> _InstanceLevelDispatch[_ET]:
  67. raise NotImplementedError()
  68. @property
  69. def _events(self) -> Type[_HasEventsDispatch[_ET]]:
  70. raise NotImplementedError()
  71. class _Dispatch(_DispatchCommon[_ET]):
  72. """Mirror the event listening definitions of an Events class with
  73. listener collections.
  74. Classes which define a "dispatch" member will return a
  75. non-instantiated :class:`._Dispatch` subclass when the member
  76. is accessed at the class level. When the "dispatch" member is
  77. accessed at the instance level of its owner, an instance
  78. of the :class:`._Dispatch` class is returned.
  79. A :class:`._Dispatch` class is generated for each :class:`.Events`
  80. class defined, by the :meth:`._HasEventsDispatch._create_dispatcher_class`
  81. method. The original :class:`.Events` classes remain untouched.
  82. This decouples the construction of :class:`.Events` subclasses from
  83. the implementation used by the event internals, and allows
  84. inspecting tools like Sphinx to work in an unsurprising
  85. way against the public API.
  86. """
  87. # "active_history" is an ORM case we add here. ideally a better
  88. # system would be in place for ad-hoc attributes.
  89. __slots__ = "_parent", "_instance_cls", "__dict__", "_empty_listeners"
  90. _active_history: bool
  91. _empty_listener_reg: MutableMapping[
  92. Type[_ET], Dict[str, _EmptyListener[_ET]]
  93. ] = weakref.WeakKeyDictionary()
  94. _empty_listeners: Dict[str, _EmptyListener[_ET]]
  95. _event_names: List[str]
  96. _instance_cls: Optional[Type[_ET]]
  97. _joined_dispatch_cls: Type[_JoinedDispatcher[_ET]]
  98. _events: Type[_HasEventsDispatch[_ET]]
  99. """reference back to the Events class.
  100. Bidirectional against _HasEventsDispatch.dispatch
  101. """
  102. def __init__(
  103. self,
  104. parent: Optional[_Dispatch[_ET]],
  105. instance_cls: Optional[Type[_ET]] = None,
  106. ):
  107. self._parent = parent
  108. self._instance_cls = instance_cls
  109. if instance_cls:
  110. assert parent is not None
  111. try:
  112. self._empty_listeners = self._empty_listener_reg[instance_cls]
  113. except KeyError:
  114. self._empty_listeners = self._empty_listener_reg[
  115. instance_cls
  116. ] = {
  117. ls.name: _EmptyListener(ls, instance_cls)
  118. for ls in parent._event_descriptors
  119. }
  120. else:
  121. self._empty_listeners = {}
  122. def __getattr__(self, name: str) -> _InstanceLevelDispatch[_ET]:
  123. # Assign EmptyListeners as attributes on demand
  124. # to reduce startup time for new dispatch objects.
  125. try:
  126. ls = self._empty_listeners[name]
  127. except KeyError:
  128. raise AttributeError(name)
  129. else:
  130. setattr(self, ls.name, ls)
  131. return ls
  132. @property
  133. def _event_descriptors(self) -> Iterator[_ClsLevelDispatch[_ET]]:
  134. for k in self._event_names:
  135. # Yield _ClsLevelDispatch related
  136. # to relevant event name.
  137. yield getattr(self, k)
  138. def _listen(self, event_key: _EventKey[_ET], **kw: Any) -> None:
  139. return self._events._listen(event_key, **kw)
  140. def _for_class(self, instance_cls: Type[_ET]) -> _Dispatch[_ET]:
  141. return self.__class__(self, instance_cls)
  142. def _for_instance(self, instance: _ET) -> _Dispatch[_ET]:
  143. instance_cls = instance.__class__
  144. return self._for_class(instance_cls)
  145. def _join(self, other: _DispatchCommon[_ET]) -> _JoinedDispatcher[_ET]:
  146. """Create a 'join' of this :class:`._Dispatch` and another.
  147. This new dispatcher will dispatch events to both
  148. :class:`._Dispatch` objects.
  149. """
  150. if "_joined_dispatch_cls" not in self.__class__.__dict__:
  151. cls = type(
  152. "Joined%s" % self.__class__.__name__,
  153. (_JoinedDispatcher,),
  154. {"__slots__": self._event_names},
  155. )
  156. self.__class__._joined_dispatch_cls = cls
  157. return self._joined_dispatch_cls(self, other)
  158. def __reduce__(self) -> Union[str, Tuple[Any, ...]]:
  159. return _UnpickleDispatch(), (self._instance_cls,)
  160. def _update(
  161. self, other: _Dispatch[_ET], only_propagate: bool = True
  162. ) -> None:
  163. """Populate from the listeners in another :class:`_Dispatch`
  164. object."""
  165. for ls in other._event_descriptors:
  166. if isinstance(ls, _EmptyListener):
  167. continue
  168. getattr(self, ls.name).for_modify(self)._update(
  169. ls, only_propagate=only_propagate
  170. )
  171. def _clear(self) -> None:
  172. for ls in self._event_descriptors:
  173. ls.for_modify(self).clear()
  174. def _remove_dispatcher(cls: Type[_HasEventsDispatch[_ET]]) -> None:
  175. for k in cls.dispatch._event_names:
  176. _registrars[k].remove(cls)
  177. if not _registrars[k]:
  178. del _registrars[k]
  179. class _HasEventsDispatch(Generic[_ET]):
  180. _dispatch_target: Optional[Type[_ET]]
  181. """class which will receive the .dispatch collection"""
  182. dispatch: _Dispatch[_ET]
  183. """reference back to the _Dispatch class.
  184. Bidirectional against _Dispatch._events
  185. """
  186. if typing.TYPE_CHECKING:
  187. def __getattr__(self, name: str) -> _InstanceLevelDispatch[_ET]:
  188. ...
  189. def __init_subclass__(cls) -> None:
  190. """Intercept new Event subclasses and create associated _Dispatch
  191. classes."""
  192. cls._create_dispatcher_class(cls.__name__, cls.__bases__, cls.__dict__)
  193. @classmethod
  194. def _accept_with(
  195. cls, target: Union[_ET, Type[_ET]], identifier: str
  196. ) -> Optional[Union[_ET, Type[_ET]]]:
  197. raise NotImplementedError()
  198. @classmethod
  199. def _listen(
  200. cls,
  201. event_key: _EventKey[_ET],
  202. *,
  203. propagate: bool = False,
  204. insert: bool = False,
  205. named: bool = False,
  206. asyncio: bool = False,
  207. ) -> None:
  208. raise NotImplementedError()
  209. @staticmethod
  210. def _set_dispatch(
  211. klass: Type[_HasEventsDispatch[_ET]],
  212. dispatch_cls: Type[_Dispatch[_ET]],
  213. ) -> _Dispatch[_ET]:
  214. # This allows an Events subclass to define additional utility
  215. # methods made available to the target via
  216. # "self.dispatch._events.<utilitymethod>"
  217. # @staticmethod to allow easy "super" calls while in a metaclass
  218. # constructor.
  219. klass.dispatch = dispatch_cls(None)
  220. dispatch_cls._events = klass
  221. return klass.dispatch
  222. @classmethod
  223. def _create_dispatcher_class(
  224. cls, classname: str, bases: Tuple[type, ...], dict_: Mapping[str, Any]
  225. ) -> None:
  226. """Create a :class:`._Dispatch` class corresponding to an
  227. :class:`.Events` class."""
  228. # there's all kinds of ways to do this,
  229. # i.e. make a Dispatch class that shares the '_listen' method
  230. # of the Event class, this is the straight monkeypatch.
  231. if hasattr(cls, "dispatch"):
  232. dispatch_base = cls.dispatch.__class__
  233. else:
  234. dispatch_base = _Dispatch
  235. event_names = [k for k in dict_ if _is_event_name(k)]
  236. dispatch_cls = cast(
  237. "Type[_Dispatch[_ET]]",
  238. type(
  239. "%sDispatch" % classname,
  240. (dispatch_base,),
  241. {"__slots__": event_names},
  242. ),
  243. )
  244. dispatch_cls._event_names = event_names
  245. dispatch_inst = cls._set_dispatch(cls, dispatch_cls)
  246. for k in dispatch_cls._event_names:
  247. setattr(dispatch_inst, k, _ClsLevelDispatch(cls, dict_[k]))
  248. _registrars[k].append(cls)
  249. for super_ in dispatch_cls.__bases__:
  250. if issubclass(super_, _Dispatch) and super_ is not _Dispatch:
  251. for ls in super_._events.dispatch._event_descriptors:
  252. setattr(dispatch_inst, ls.name, ls)
  253. dispatch_cls._event_names.append(ls.name)
  254. if getattr(cls, "_dispatch_target", None):
  255. dispatch_target_cls = cls._dispatch_target
  256. assert dispatch_target_cls is not None
  257. if (
  258. hasattr(dispatch_target_cls, "__slots__")
  259. and "_slots_dispatch" in dispatch_target_cls.__slots__
  260. ):
  261. dispatch_target_cls.dispatch = slots_dispatcher(cls)
  262. else:
  263. dispatch_target_cls.dispatch = dispatcher(cls)
  264. class Events(_HasEventsDispatch[_ET]):
  265. """Define event listening functions for a particular target type."""
  266. @classmethod
  267. def _accept_with(
  268. cls, target: Union[_ET, Type[_ET]], identifier: str
  269. ) -> Optional[Union[_ET, Type[_ET]]]:
  270. def dispatch_is(*types: Type[Any]) -> bool:
  271. return all(isinstance(target.dispatch, t) for t in types)
  272. def dispatch_parent_is(t: Type[Any]) -> bool:
  273. return isinstance(
  274. cast("_JoinedDispatcher[_ET]", target.dispatch).parent, t
  275. )
  276. # Mapper, ClassManager, Session override this to
  277. # also accept classes, scoped_sessions, sessionmakers, etc.
  278. if hasattr(target, "dispatch"):
  279. if (
  280. dispatch_is(cls.dispatch.__class__)
  281. or dispatch_is(type, cls.dispatch.__class__)
  282. or (
  283. dispatch_is(_JoinedDispatcher)
  284. and dispatch_parent_is(cls.dispatch.__class__)
  285. )
  286. ):
  287. return target
  288. return None
  289. @classmethod
  290. def _listen(
  291. cls,
  292. event_key: _EventKey[_ET],
  293. *,
  294. propagate: bool = False,
  295. insert: bool = False,
  296. named: bool = False,
  297. asyncio: bool = False,
  298. ) -> None:
  299. event_key.base_listen(
  300. propagate=propagate, insert=insert, named=named, asyncio=asyncio
  301. )
  302. @classmethod
  303. def _remove(cls, event_key: _EventKey[_ET]) -> None:
  304. event_key.remove()
  305. @classmethod
  306. def _clear(cls) -> None:
  307. cls.dispatch._clear()
  308. class _JoinedDispatcher(_DispatchCommon[_ET]):
  309. """Represent a connection between two _Dispatch objects."""
  310. __slots__ = "local", "parent", "_instance_cls"
  311. local: _DispatchCommon[_ET]
  312. parent: _DispatchCommon[_ET]
  313. _instance_cls: Optional[Type[_ET]]
  314. def __init__(
  315. self, local: _DispatchCommon[_ET], parent: _DispatchCommon[_ET]
  316. ):
  317. self.local = local
  318. self.parent = parent
  319. self._instance_cls = self.local._instance_cls
  320. def __getattr__(self, name: str) -> _JoinedListener[_ET]:
  321. # Assign _JoinedListeners as attributes on demand
  322. # to reduce startup time for new dispatch objects.
  323. ls = getattr(self.local, name)
  324. jl = _JoinedListener(self.parent, ls.name, ls)
  325. setattr(self, ls.name, jl)
  326. return jl
  327. def _listen(self, event_key: _EventKey[_ET], **kw: Any) -> None:
  328. return self.parent._listen(event_key, **kw)
  329. @property
  330. def _events(self) -> Type[_HasEventsDispatch[_ET]]:
  331. return self.parent._events
  332. class dispatcher(Generic[_ET]):
  333. """Descriptor used by target classes to
  334. deliver the _Dispatch class at the class level
  335. and produce new _Dispatch instances for target
  336. instances.
  337. """
  338. def __init__(self, events: Type[_HasEventsDispatch[_ET]]):
  339. self.dispatch = events.dispatch
  340. self.events = events
  341. @overload
  342. def __get__(
  343. self, obj: Literal[None], cls: Type[Any]
  344. ) -> Type[_Dispatch[_ET]]:
  345. ...
  346. @overload
  347. def __get__(self, obj: Any, cls: Type[Any]) -> _DispatchCommon[_ET]:
  348. ...
  349. def __get__(self, obj: Any, cls: Type[Any]) -> Any:
  350. if obj is None:
  351. return self.dispatch
  352. disp = self.dispatch._for_instance(obj)
  353. try:
  354. obj.__dict__["dispatch"] = disp
  355. except AttributeError as ae:
  356. raise TypeError(
  357. "target %r doesn't have __dict__, should it be "
  358. "defining _slots_dispatch?" % (obj,)
  359. ) from ae
  360. return disp
  361. class slots_dispatcher(dispatcher[_ET]):
  362. def __get__(self, obj: Any, cls: Type[Any]) -> Any:
  363. if obj is None:
  364. return self.dispatch
  365. if hasattr(obj, "_slots_dispatch"):
  366. return obj._slots_dispatch
  367. disp = self.dispatch._for_instance(obj)
  368. obj._slots_dispatch = disp
  369. return disp