_hooks.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. """
  2. Internal hook annotation, representation and calling machinery.
  3. """
  4. from __future__ import annotations
  5. from collections.abc import Generator
  6. from collections.abc import Mapping
  7. from collections.abc import Sequence
  8. from collections.abc import Set
  9. import inspect
  10. import sys
  11. from types import ModuleType
  12. from typing import Any
  13. from typing import Callable
  14. from typing import Final
  15. from typing import final
  16. from typing import Optional
  17. from typing import overload
  18. from typing import TYPE_CHECKING
  19. from typing import TypedDict
  20. from typing import TypeVar
  21. from typing import Union
  22. import warnings
  23. from ._result import Result
  24. _T = TypeVar("_T")
  25. _F = TypeVar("_F", bound=Callable[..., object])
  26. _Namespace = Union[ModuleType, type]
  27. _Plugin = object
  28. _HookExec = Callable[
  29. [str, Sequence["HookImpl"], Mapping[str, object], bool],
  30. Union[object, list[object]],
  31. ]
  32. _HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]]
  33. class HookspecOpts(TypedDict):
  34. """Options for a hook specification."""
  35. #: Whether the hook is :ref:`first result only <firstresult>`.
  36. firstresult: bool
  37. #: Whether the hook is :ref:`historic <historic>`.
  38. historic: bool
  39. #: Whether the hook :ref:`warns when implemented <warn_on_impl>`.
  40. warn_on_impl: Warning | None
  41. #: Whether the hook warns when :ref:`certain arguments are requested
  42. #: <warn_on_impl>`.
  43. #:
  44. #: .. versionadded:: 1.5
  45. warn_on_impl_args: Mapping[str, Warning] | None
  46. class HookimplOpts(TypedDict):
  47. """Options for a hook implementation."""
  48. #: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
  49. wrapper: bool
  50. #: Whether the hook implementation is an :ref:`old-style wrapper
  51. #: <old_style_hookwrappers>`.
  52. hookwrapper: bool
  53. #: Whether validation against a hook specification is :ref:`optional
  54. #: <optionalhook>`.
  55. optionalhook: bool
  56. #: Whether to try to order this hook implementation :ref:`first
  57. #: <callorder>`.
  58. tryfirst: bool
  59. #: Whether to try to order this hook implementation :ref:`last
  60. #: <callorder>`.
  61. trylast: bool
  62. #: The name of the hook specification to match, see :ref:`specname`.
  63. specname: str | None
  64. @final
  65. class HookspecMarker:
  66. """Decorator for marking functions as hook specifications.
  67. Instantiate it with a project_name to get a decorator.
  68. Calling :meth:`PluginManager.add_hookspecs` later will discover all marked
  69. functions if the :class:`PluginManager` uses the same project name.
  70. """
  71. __slots__ = ("project_name",)
  72. def __init__(self, project_name: str) -> None:
  73. self.project_name: Final = project_name
  74. @overload
  75. def __call__(
  76. self,
  77. function: _F,
  78. firstresult: bool = False,
  79. historic: bool = False,
  80. warn_on_impl: Warning | None = None,
  81. warn_on_impl_args: Mapping[str, Warning] | None = None,
  82. ) -> _F: ...
  83. @overload # noqa: F811
  84. def __call__( # noqa: F811
  85. self,
  86. function: None = ...,
  87. firstresult: bool = ...,
  88. historic: bool = ...,
  89. warn_on_impl: Warning | None = ...,
  90. warn_on_impl_args: Mapping[str, Warning] | None = ...,
  91. ) -> Callable[[_F], _F]: ...
  92. def __call__( # noqa: F811
  93. self,
  94. function: _F | None = None,
  95. firstresult: bool = False,
  96. historic: bool = False,
  97. warn_on_impl: Warning | None = None,
  98. warn_on_impl_args: Mapping[str, Warning] | None = None,
  99. ) -> _F | Callable[[_F], _F]:
  100. """If passed a function, directly sets attributes on the function
  101. which will make it discoverable to :meth:`PluginManager.add_hookspecs`.
  102. If passed no function, returns a decorator which can be applied to a
  103. function later using the attributes supplied.
  104. :param firstresult:
  105. If ``True``, the 1:N hook call (N being the number of registered
  106. hook implementation functions) will stop at I<=N when the I'th
  107. function returns a non-``None`` result. See :ref:`firstresult`.
  108. :param historic:
  109. If ``True``, every call to the hook will be memorized and replayed
  110. on plugins registered after the call was made. See :ref:`historic`.
  111. :param warn_on_impl:
  112. If given, every implementation of this hook will trigger the given
  113. warning. See :ref:`warn_on_impl`.
  114. :param warn_on_impl_args:
  115. If given, every implementation of this hook which requests one of
  116. the arguments in the dict will trigger the corresponding warning.
  117. See :ref:`warn_on_impl`.
  118. .. versionadded:: 1.5
  119. """
  120. def setattr_hookspec_opts(func: _F) -> _F:
  121. if historic and firstresult:
  122. raise ValueError("cannot have a historic firstresult hook")
  123. opts: HookspecOpts = {
  124. "firstresult": firstresult,
  125. "historic": historic,
  126. "warn_on_impl": warn_on_impl,
  127. "warn_on_impl_args": warn_on_impl_args,
  128. }
  129. setattr(func, self.project_name + "_spec", opts)
  130. return func
  131. if function is not None:
  132. return setattr_hookspec_opts(function)
  133. else:
  134. return setattr_hookspec_opts
  135. @final
  136. class HookimplMarker:
  137. """Decorator for marking functions as hook implementations.
  138. Instantiate it with a ``project_name`` to get a decorator.
  139. Calling :meth:`PluginManager.register` later will discover all marked
  140. functions if the :class:`PluginManager` uses the same project name.
  141. """
  142. __slots__ = ("project_name",)
  143. def __init__(self, project_name: str) -> None:
  144. self.project_name: Final = project_name
  145. @overload
  146. def __call__(
  147. self,
  148. function: _F,
  149. hookwrapper: bool = ...,
  150. optionalhook: bool = ...,
  151. tryfirst: bool = ...,
  152. trylast: bool = ...,
  153. specname: str | None = ...,
  154. wrapper: bool = ...,
  155. ) -> _F: ...
  156. @overload # noqa: F811
  157. def __call__( # noqa: F811
  158. self,
  159. function: None = ...,
  160. hookwrapper: bool = ...,
  161. optionalhook: bool = ...,
  162. tryfirst: bool = ...,
  163. trylast: bool = ...,
  164. specname: str | None = ...,
  165. wrapper: bool = ...,
  166. ) -> Callable[[_F], _F]: ...
  167. def __call__( # noqa: F811
  168. self,
  169. function: _F | None = None,
  170. hookwrapper: bool = False,
  171. optionalhook: bool = False,
  172. tryfirst: bool = False,
  173. trylast: bool = False,
  174. specname: str | None = None,
  175. wrapper: bool = False,
  176. ) -> _F | Callable[[_F], _F]:
  177. """If passed a function, directly sets attributes on the function
  178. which will make it discoverable to :meth:`PluginManager.register`.
  179. If passed no function, returns a decorator which can be applied to a
  180. function later using the attributes supplied.
  181. :param optionalhook:
  182. If ``True``, a missing matching hook specification will not result
  183. in an error (by default it is an error if no matching spec is
  184. found). See :ref:`optionalhook`.
  185. :param tryfirst:
  186. If ``True``, this hook implementation will run as early as possible
  187. in the chain of N hook implementations for a specification. See
  188. :ref:`callorder`.
  189. :param trylast:
  190. If ``True``, this hook implementation will run as late as possible
  191. in the chain of N hook implementations for a specification. See
  192. :ref:`callorder`.
  193. :param wrapper:
  194. If ``True`` ("new-style hook wrapper"), the hook implementation
  195. needs to execute exactly one ``yield``. The code before the
  196. ``yield`` is run early before any non-hook-wrapper function is run.
  197. The code after the ``yield`` is run after all non-hook-wrapper
  198. functions have run. The ``yield`` receives the result value of the
  199. inner calls, or raises the exception of inner calls (including
  200. earlier hook wrapper calls). The return value of the function
  201. becomes the return value of the hook, and a raised exception becomes
  202. the exception of the hook. See :ref:`hookwrapper`.
  203. :param hookwrapper:
  204. If ``True`` ("old-style hook wrapper"), the hook implementation
  205. needs to execute exactly one ``yield``. The code before the
  206. ``yield`` is run early before any non-hook-wrapper function is run.
  207. The code after the ``yield`` is run after all non-hook-wrapper
  208. function have run The ``yield`` receives a :class:`Result` object
  209. representing the exception or result outcome of the inner calls
  210. (including earlier hook wrapper calls). This option is mutually
  211. exclusive with ``wrapper``. See :ref:`old_style_hookwrapper`.
  212. :param specname:
  213. If provided, the given name will be used instead of the function
  214. name when matching this hook implementation to a hook specification
  215. during registration. See :ref:`specname`.
  216. .. versionadded:: 1.2.0
  217. The ``wrapper`` parameter.
  218. """
  219. def setattr_hookimpl_opts(func: _F) -> _F:
  220. opts: HookimplOpts = {
  221. "wrapper": wrapper,
  222. "hookwrapper": hookwrapper,
  223. "optionalhook": optionalhook,
  224. "tryfirst": tryfirst,
  225. "trylast": trylast,
  226. "specname": specname,
  227. }
  228. setattr(func, self.project_name + "_impl", opts)
  229. return func
  230. if function is None:
  231. return setattr_hookimpl_opts
  232. else:
  233. return setattr_hookimpl_opts(function)
  234. def normalize_hookimpl_opts(opts: HookimplOpts) -> None:
  235. opts.setdefault("tryfirst", False)
  236. opts.setdefault("trylast", False)
  237. opts.setdefault("wrapper", False)
  238. opts.setdefault("hookwrapper", False)
  239. opts.setdefault("optionalhook", False)
  240. opts.setdefault("specname", None)
  241. _PYPY = hasattr(sys, "pypy_version_info")
  242. def varnames(func: object) -> tuple[tuple[str, ...], tuple[str, ...]]:
  243. """Return tuple of positional and keywrord argument names for a function,
  244. method, class or callable.
  245. In case of a class, its ``__init__`` method is considered.
  246. For methods the ``self`` parameter is not included.
  247. """
  248. if inspect.isclass(func):
  249. try:
  250. func = func.__init__
  251. except AttributeError: # pragma: no cover - pypy special case
  252. return (), ()
  253. elif not inspect.isroutine(func): # callable object?
  254. try:
  255. func = getattr(func, "__call__", func)
  256. except Exception: # pragma: no cover - pypy special case
  257. return (), ()
  258. try:
  259. # func MUST be a function or method here or we won't parse any args.
  260. sig = inspect.signature(
  261. func.__func__ if inspect.ismethod(func) else func # type:ignore[arg-type]
  262. )
  263. except TypeError: # pragma: no cover
  264. return (), ()
  265. _valid_param_kinds = (
  266. inspect.Parameter.POSITIONAL_ONLY,
  267. inspect.Parameter.POSITIONAL_OR_KEYWORD,
  268. )
  269. _valid_params = {
  270. name: param
  271. for name, param in sig.parameters.items()
  272. if param.kind in _valid_param_kinds
  273. }
  274. args = tuple(_valid_params)
  275. defaults = (
  276. tuple(
  277. param.default
  278. for param in _valid_params.values()
  279. if param.default is not param.empty
  280. )
  281. or None
  282. )
  283. if defaults:
  284. index = -len(defaults)
  285. args, kwargs = args[:index], tuple(args[index:])
  286. else:
  287. kwargs = ()
  288. # strip any implicit instance arg
  289. # pypy3 uses "obj" instead of "self" for default dunder methods
  290. if not _PYPY:
  291. implicit_names: tuple[str, ...] = ("self",)
  292. else: # pragma: no cover
  293. implicit_names = ("self", "obj")
  294. if args:
  295. qualname: str = getattr(func, "__qualname__", "")
  296. if inspect.ismethod(func) or ("." in qualname and args[0] in implicit_names):
  297. args = args[1:]
  298. return args, kwargs
  299. @final
  300. class HookRelay:
  301. """Hook holder object for performing 1:N hook calls where N is the number
  302. of registered plugins."""
  303. __slots__ = ("__dict__",)
  304. def __init__(self) -> None:
  305. """:meta private:"""
  306. if TYPE_CHECKING:
  307. def __getattr__(self, name: str) -> HookCaller: ...
  308. # Historical name (pluggy<=1.2), kept for backward compatibility.
  309. _HookRelay = HookRelay
  310. _CallHistory = list[tuple[Mapping[str, object], Optional[Callable[[Any], None]]]]
  311. class HookCaller:
  312. """A caller of all registered implementations of a hook specification."""
  313. __slots__ = (
  314. "name",
  315. "spec",
  316. "_hookexec",
  317. "_hookimpls",
  318. "_call_history",
  319. )
  320. def __init__(
  321. self,
  322. name: str,
  323. hook_execute: _HookExec,
  324. specmodule_or_class: _Namespace | None = None,
  325. spec_opts: HookspecOpts | None = None,
  326. ) -> None:
  327. """:meta private:"""
  328. #: Name of the hook getting called.
  329. self.name: Final = name
  330. self._hookexec: Final = hook_execute
  331. # The hookimpls list. The caller iterates it *in reverse*. Format:
  332. # 1. trylast nonwrappers
  333. # 2. nonwrappers
  334. # 3. tryfirst nonwrappers
  335. # 4. trylast wrappers
  336. # 5. wrappers
  337. # 6. tryfirst wrappers
  338. self._hookimpls: Final[list[HookImpl]] = []
  339. self._call_history: _CallHistory | None = None
  340. # TODO: Document, or make private.
  341. self.spec: HookSpec | None = None
  342. if specmodule_or_class is not None:
  343. assert spec_opts is not None
  344. self.set_specification(specmodule_or_class, spec_opts)
  345. # TODO: Document, or make private.
  346. def has_spec(self) -> bool:
  347. return self.spec is not None
  348. # TODO: Document, or make private.
  349. def set_specification(
  350. self,
  351. specmodule_or_class: _Namespace,
  352. spec_opts: HookspecOpts,
  353. ) -> None:
  354. if self.spec is not None:
  355. raise ValueError(
  356. f"Hook {self.spec.name!r} is already registered "
  357. f"within namespace {self.spec.namespace}"
  358. )
  359. self.spec = HookSpec(specmodule_or_class, self.name, spec_opts)
  360. if spec_opts.get("historic"):
  361. self._call_history = []
  362. def is_historic(self) -> bool:
  363. """Whether this caller is :ref:`historic <historic>`."""
  364. return self._call_history is not None
  365. def _remove_plugin(self, plugin: _Plugin) -> None:
  366. for i, method in enumerate(self._hookimpls):
  367. if method.plugin == plugin:
  368. del self._hookimpls[i]
  369. return
  370. raise ValueError(f"plugin {plugin!r} not found")
  371. def get_hookimpls(self) -> list[HookImpl]:
  372. """Get all registered hook implementations for this hook."""
  373. return self._hookimpls.copy()
  374. def _add_hookimpl(self, hookimpl: HookImpl) -> None:
  375. """Add an implementation to the callback chain."""
  376. for i, method in enumerate(self._hookimpls):
  377. if method.hookwrapper or method.wrapper:
  378. splitpoint = i
  379. break
  380. else:
  381. splitpoint = len(self._hookimpls)
  382. if hookimpl.hookwrapper or hookimpl.wrapper:
  383. start, end = splitpoint, len(self._hookimpls)
  384. else:
  385. start, end = 0, splitpoint
  386. if hookimpl.trylast:
  387. self._hookimpls.insert(start, hookimpl)
  388. elif hookimpl.tryfirst:
  389. self._hookimpls.insert(end, hookimpl)
  390. else:
  391. # find last non-tryfirst method
  392. i = end - 1
  393. while i >= start and self._hookimpls[i].tryfirst:
  394. i -= 1
  395. self._hookimpls.insert(i + 1, hookimpl)
  396. def __repr__(self) -> str:
  397. return f"<HookCaller {self.name!r}>"
  398. def _verify_all_args_are_provided(self, kwargs: Mapping[str, object]) -> None:
  399. # This is written to avoid expensive operations when not needed.
  400. if self.spec:
  401. for argname in self.spec.argnames:
  402. if argname not in kwargs:
  403. notincall = ", ".join(
  404. repr(argname)
  405. for argname in self.spec.argnames
  406. # Avoid self.spec.argnames - kwargs.keys()
  407. # it doesn't preserve order.
  408. if argname not in kwargs.keys()
  409. )
  410. warnings.warn(
  411. f"Argument(s) {notincall} which are declared in the hookspec "
  412. "cannot be found in this hook call",
  413. stacklevel=2,
  414. )
  415. break
  416. def __call__(self, **kwargs: object) -> Any:
  417. """Call the hook.
  418. Only accepts keyword arguments, which should match the hook
  419. specification.
  420. Returns the result(s) of calling all registered plugins, see
  421. :ref:`calling`.
  422. """
  423. assert not self.is_historic(), (
  424. "Cannot directly call a historic hook - use call_historic instead."
  425. )
  426. self._verify_all_args_are_provided(kwargs)
  427. firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
  428. # Copy because plugins may register other plugins during iteration (#438).
  429. return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  430. def call_historic(
  431. self,
  432. result_callback: Callable[[Any], None] | None = None,
  433. kwargs: Mapping[str, object] | None = None,
  434. ) -> None:
  435. """Call the hook with given ``kwargs`` for all registered plugins and
  436. for all plugins which will be registered afterwards, see
  437. :ref:`historic`.
  438. :param result_callback:
  439. If provided, will be called for each non-``None`` result obtained
  440. from a hook implementation.
  441. """
  442. assert self._call_history is not None
  443. kwargs = kwargs or {}
  444. self._verify_all_args_are_provided(kwargs)
  445. self._call_history.append((kwargs, result_callback))
  446. # Historizing hooks don't return results.
  447. # Remember firstresult isn't compatible with historic.
  448. # Copy because plugins may register other plugins during iteration (#438).
  449. res = self._hookexec(self.name, self._hookimpls.copy(), kwargs, False)
  450. if result_callback is None:
  451. return
  452. if isinstance(res, list):
  453. for x in res:
  454. result_callback(x)
  455. def call_extra(
  456. self, methods: Sequence[Callable[..., object]], kwargs: Mapping[str, object]
  457. ) -> Any:
  458. """Call the hook with some additional temporarily participating
  459. methods using the specified ``kwargs`` as call parameters, see
  460. :ref:`call_extra`."""
  461. assert not self.is_historic(), (
  462. "Cannot directly call a historic hook - use call_historic instead."
  463. )
  464. self._verify_all_args_are_provided(kwargs)
  465. opts: HookimplOpts = {
  466. "wrapper": False,
  467. "hookwrapper": False,
  468. "optionalhook": False,
  469. "trylast": False,
  470. "tryfirst": False,
  471. "specname": None,
  472. }
  473. hookimpls = self._hookimpls.copy()
  474. for method in methods:
  475. hookimpl = HookImpl(None, "<temp>", method, opts)
  476. # Find last non-tryfirst nonwrapper method.
  477. i = len(hookimpls) - 1
  478. while i >= 0 and (
  479. # Skip wrappers.
  480. (hookimpls[i].hookwrapper or hookimpls[i].wrapper)
  481. # Skip tryfirst nonwrappers.
  482. or hookimpls[i].tryfirst
  483. ):
  484. i -= 1
  485. hookimpls.insert(i + 1, hookimpl)
  486. firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
  487. return self._hookexec(self.name, hookimpls, kwargs, firstresult)
  488. def _maybe_apply_history(self, method: HookImpl) -> None:
  489. """Apply call history to a new hookimpl if it is marked as historic."""
  490. if self.is_historic():
  491. assert self._call_history is not None
  492. for kwargs, result_callback in self._call_history:
  493. res = self._hookexec(self.name, [method], kwargs, False)
  494. if res and result_callback is not None:
  495. # XXX: remember firstresult isn't compat with historic
  496. assert isinstance(res, list)
  497. result_callback(res[0])
  498. # Historical name (pluggy<=1.2), kept for backward compatibility.
  499. _HookCaller = HookCaller
  500. class _SubsetHookCaller(HookCaller):
  501. """A proxy to another HookCaller which manages calls to all registered
  502. plugins except the ones from remove_plugins."""
  503. # This class is unusual: in inhertits from `HookCaller` so all of
  504. # the *code* runs in the class, but it delegates all underlying *data*
  505. # to the original HookCaller.
  506. # `subset_hook_caller` used to be implemented by creating a full-fledged
  507. # HookCaller, copying all hookimpls from the original. This had problems
  508. # with memory leaks (#346) and historic calls (#347), which make a proxy
  509. # approach better.
  510. # An alternative implementation is to use a `_getattr__`/`__getattribute__`
  511. # proxy, however that adds more overhead and is more tricky to implement.
  512. __slots__ = (
  513. "_orig",
  514. "_remove_plugins",
  515. )
  516. def __init__(self, orig: HookCaller, remove_plugins: Set[_Plugin]) -> None:
  517. self._orig = orig
  518. self._remove_plugins = remove_plugins
  519. self.name = orig.name # type: ignore[misc]
  520. self._hookexec = orig._hookexec # type: ignore[misc]
  521. @property # type: ignore[misc]
  522. def _hookimpls(self) -> list[HookImpl]:
  523. return [
  524. impl
  525. for impl in self._orig._hookimpls
  526. if impl.plugin not in self._remove_plugins
  527. ]
  528. @property
  529. def spec(self) -> HookSpec | None: # type: ignore[override]
  530. return self._orig.spec
  531. @property
  532. def _call_history(self) -> _CallHistory | None: # type: ignore[override]
  533. return self._orig._call_history
  534. def __repr__(self) -> str:
  535. return f"<_SubsetHookCaller {self.name!r}>"
  536. @final
  537. class HookImpl:
  538. """A hook implementation in a :class:`HookCaller`."""
  539. __slots__ = (
  540. "function",
  541. "argnames",
  542. "kwargnames",
  543. "plugin",
  544. "opts",
  545. "plugin_name",
  546. "wrapper",
  547. "hookwrapper",
  548. "optionalhook",
  549. "tryfirst",
  550. "trylast",
  551. )
  552. def __init__(
  553. self,
  554. plugin: _Plugin,
  555. plugin_name: str,
  556. function: _HookImplFunction[object],
  557. hook_impl_opts: HookimplOpts,
  558. ) -> None:
  559. """:meta private:"""
  560. #: The hook implementation function.
  561. self.function: Final = function
  562. argnames, kwargnames = varnames(self.function)
  563. #: The positional parameter names of ``function```.
  564. self.argnames: Final = argnames
  565. #: The keyword parameter names of ``function```.
  566. self.kwargnames: Final = kwargnames
  567. #: The plugin which defined this hook implementation.
  568. self.plugin: Final = plugin
  569. #: The :class:`HookimplOpts` used to configure this hook implementation.
  570. self.opts: Final = hook_impl_opts
  571. #: The name of the plugin which defined this hook implementation.
  572. self.plugin_name: Final = plugin_name
  573. #: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
  574. self.wrapper: Final = hook_impl_opts["wrapper"]
  575. #: Whether the hook implementation is an :ref:`old-style wrapper
  576. #: <old_style_hookwrappers>`.
  577. self.hookwrapper: Final = hook_impl_opts["hookwrapper"]
  578. #: Whether validation against a hook specification is :ref:`optional
  579. #: <optionalhook>`.
  580. self.optionalhook: Final = hook_impl_opts["optionalhook"]
  581. #: Whether to try to order this hook implementation :ref:`first
  582. #: <callorder>`.
  583. self.tryfirst: Final = hook_impl_opts["tryfirst"]
  584. #: Whether to try to order this hook implementation :ref:`last
  585. #: <callorder>`.
  586. self.trylast: Final = hook_impl_opts["trylast"]
  587. def __repr__(self) -> str:
  588. return f"<HookImpl plugin_name={self.plugin_name!r}, plugin={self.plugin!r}>"
  589. @final
  590. class HookSpec:
  591. __slots__ = (
  592. "namespace",
  593. "function",
  594. "name",
  595. "argnames",
  596. "kwargnames",
  597. "opts",
  598. "warn_on_impl",
  599. "warn_on_impl_args",
  600. )
  601. def __init__(self, namespace: _Namespace, name: str, opts: HookspecOpts) -> None:
  602. self.namespace = namespace
  603. self.function: Callable[..., object] = getattr(namespace, name)
  604. self.name = name
  605. self.argnames, self.kwargnames = varnames(self.function)
  606. self.opts = opts
  607. self.warn_on_impl = opts.get("warn_on_impl")
  608. self.warn_on_impl_args = opts.get("warn_on_impl_args")