Skip to content

Commit 6238439

Browse files
feat(decorators): attach Hook*Configuration objects on marked functions
Complete design step 03: markers already attach configuration objects since step 02; this finishes the step by storing the spec configuration as HookSpec.config (try-claude naming) with a deprecated .opts alias, reading .config in HookCaller firstresult resolution, and covering decoration-time historic+firstresult validation and configuration attachment with tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 65ee170 commit 6238439

5 files changed

Lines changed: 50 additions & 10 deletions

File tree

changelog/705.trivial.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``HookSpec`` now stores its :class:`pluggy.HookspecConfiguration` as
2+
``config``; the old ``opts`` attribute remains as a deprecated alias
3+
property.

src/pluggy/_caller.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def __call__(self, **kwargs: object) -> Any:
183183
"Cannot directly call a historic hook - use call_historic instead."
184184
)
185185
self._verify_all_args_are_provided(kwargs)
186-
firstresult = self.spec.opts.firstresult if self.spec else False
186+
firstresult = self.spec.config.firstresult if self.spec else False
187187
# Copy because plugins may register other plugins during iteration (#438).
188188
return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
189189

@@ -238,7 +238,7 @@ def call_extra(
238238
):
239239
i -= 1
240240
hookimpls.insert(i + 1, hookimpl)
241-
firstresult = self.spec.opts.firstresult if self.spec else False
241+
firstresult = self.spec.config.firstresult if self.spec else False
242242
return self._hookexec(self.name, hookimpls, kwargs, firstresult)
243243

244244
def _maybe_apply_history(self, method: HookImpl) -> None:

src/pluggy/_decorators.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -332,13 +332,13 @@ class HookSpec:
332332
"name",
333333
"argnames",
334334
"kwargnames",
335-
"opts",
335+
"config",
336336
"warn_on_impl",
337337
"warn_on_impl_args",
338338
)
339339

340340
def __init__(
341-
self, namespace: _Namespace, name: str, opts: HookspecConfiguration
341+
self, namespace: _Namespace, name: str, config: HookspecConfiguration
342342
) -> None:
343343
self.namespace = namespace
344344
self.name = name
@@ -349,6 +349,15 @@ def __init__(
349349
self.argnames, self.kwargnames = varnames(
350350
self.function, legacy_noself=legacy_noself
351351
)
352-
self.opts = opts
353-
self.warn_on_impl = opts.warn_on_impl
354-
self.warn_on_impl_args = opts.warn_on_impl_args
352+
self.config = config
353+
self.warn_on_impl = config.warn_on_impl
354+
self.warn_on_impl_args = config.warn_on_impl_args
355+
356+
@property
357+
def opts(self) -> HookspecConfiguration:
358+
"""Alias for :attr:`config`.
359+
360+
.. deprecated::
361+
Use :attr:`config` instead.
362+
"""
363+
return self.config

testing/test_configuration.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,34 @@ def myimpl(arg: object) -> str:
222222
assert impl_config.tryfirst is True
223223

224224

225+
def test_historic_firstresult_raises_at_decoration_time() -> None:
226+
hookspec = HookspecMarker("test")
227+
228+
with pytest.raises(ValueError, match="cannot have a historic firstresult"):
229+
230+
@hookspec(historic=True, firstresult=True)
231+
def myspec() -> None:
232+
pass
233+
234+
235+
def test_hookspec_stores_configuration() -> None:
236+
pm = PluginManager("test")
237+
hookspec = HookspecMarker("test")
238+
239+
class Spec:
240+
@hookspec(firstresult=True)
241+
def myhook(self, arg: object) -> None:
242+
pass
243+
244+
pm.add_hookspecs(Spec)
245+
spec = pm.hook.myhook.spec
246+
assert spec is not None
247+
assert isinstance(spec.config, HookspecConfiguration)
248+
assert spec.config.firstresult is True
249+
# Deprecated alias.
250+
assert spec.opts is spec.config
251+
252+
225253
def test_config_integration_with_hooks() -> None:
226254
pm = PluginManager("test")
227255
hookspec = HookspecMarker("test")

testing/test_hookcaller.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,11 +317,11 @@ def he_myhook3(self, arg1) -> None:
317317

318318
pm.add_hookspecs(HookSpec)
319319
assert pm.hook.he_myhook1.spec is not None
320-
assert not pm.hook.he_myhook1.spec.opts.firstresult
320+
assert not pm.hook.he_myhook1.spec.config.firstresult
321321
assert pm.hook.he_myhook2.spec is not None
322-
assert pm.hook.he_myhook2.spec.opts.firstresult
322+
assert pm.hook.he_myhook2.spec.config.firstresult
323323
assert pm.hook.he_myhook3.spec is not None
324-
assert not pm.hook.he_myhook3.spec.opts.firstresult
324+
assert not pm.hook.he_myhook3.spec.config.firstresult
325325

326326

327327
@pytest.mark.parametrize("name", ["hookwrapper", "optionalhook", "tryfirst", "trylast"])

0 commit comments

Comments
 (0)