24
24
25
25
if TYPE_CHECKING :
26
26
from typing_extensions import TypedDict
27
+ from typing_extensions import Final
27
28
28
29
29
30
_T = TypeVar ("_T" )
@@ -58,8 +59,10 @@ class HookspecMarker:
58
59
if the :py:class:`.PluginManager` uses the same project_name.
59
60
"""
60
61
62
+ __slots__ = ("project_name" ,)
63
+
61
64
def __init__ (self , project_name : str ) -> None :
62
- self .project_name = project_name
65
+ self .project_name : "Final" = project_name
63
66
64
67
@overload
65
68
def __call__ (
@@ -127,8 +130,10 @@ class HookimplMarker:
127
130
if the :py:class:`.PluginManager` uses the same project_name.
128
131
"""
129
132
133
+ __slots__ = ("project_name" ,)
134
+
130
135
def __init__ (self , project_name : str ) -> None :
131
- self .project_name = project_name
136
+ self .project_name : "Final" = project_name
132
137
133
138
@overload
134
139
def __call__ (
@@ -263,31 +268,41 @@ def varnames(func: object) -> Tuple[Tuple[str, ...], Tuple[str, ...]]:
263
268
264
269
class _HookRelay :
265
270
"""hook holder object for performing 1:N hook calls where N is the number
266
- of registered plugins.
271
+ of registered plugins."""
267
272
268
- """
273
+ __slots__ = ( "__dict__" ,)
269
274
270
275
if TYPE_CHECKING :
271
276
272
277
def __getattr__ (self , name : str ) -> "_HookCaller" :
273
278
...
274
279
275
280
281
+ _CallHistory = List [Tuple [Mapping [str , object ], Optional [Callable [[Any ], None ]]]]
282
+
283
+
276
284
class _HookCaller :
285
+ __slots__ = (
286
+ "name" ,
287
+ "spec" ,
288
+ "_hookexec" ,
289
+ "_wrappers" ,
290
+ "_nonwrappers" ,
291
+ "_call_history" ,
292
+ )
293
+
277
294
def __init__ (
278
295
self ,
279
296
name : str ,
280
297
hook_execute : _HookExec ,
281
298
specmodule_or_class : Optional [_Namespace ] = None ,
282
299
spec_opts : Optional ["_HookSpecOpts" ] = None ,
283
300
) -> None :
284
- self .name = name
285
- self ._wrappers : List [HookImpl ] = []
286
- self ._nonwrappers : List [HookImpl ] = []
287
- self ._hookexec = hook_execute
288
- self ._call_history : Optional [
289
- List [Tuple [Mapping [str , object ], Optional [Callable [[Any ], None ]]]]
290
- ] = None
301
+ self .name : "Final" = name
302
+ self ._hookexec : "Final" = hook_execute
303
+ self ._wrappers : "Final[List[HookImpl]]" = []
304
+ self ._nonwrappers : "Final[List[HookImpl]]" = []
305
+ self ._call_history : Optional [_CallHistory ] = None
291
306
self .spec : Optional [HookSpec ] = None
292
307
if specmodule_or_class is not None :
293
308
assert spec_opts is not None
@@ -346,27 +361,32 @@ def _add_hookimpl(self, hookimpl: "HookImpl") -> None:
346
361
def __repr__ (self ) -> str :
347
362
return f"<_HookCaller { self .name !r} >"
348
363
349
- def __call__ (self , * args : object , ** kwargs : object ) -> Any :
350
- if args :
351
- raise TypeError ("hook calling supports only keyword arguments" )
352
- assert not self .is_historic ()
353
-
364
+ def _verify_all_args_are_provided (self , kwargs : Mapping [str , object ]) -> None :
354
365
# This is written to avoid expensive operations when not needed.
355
366
if self .spec :
356
367
for argname in self .spec .argnames :
357
368
if argname not in kwargs :
358
- notincall = tuple (set (self .spec .argnames ) - kwargs .keys ())
369
+ notincall = ", " .join (
370
+ repr (argname )
371
+ for argname in self .spec .argnames
372
+ # Avoid self.spec.argnames - kwargs.keys() - doesn't preserve order.
373
+ if argname not in kwargs .keys ()
374
+ )
359
375
warnings .warn (
360
376
"Argument(s) {} which are declared in the hookspec "
361
- "can not be found in this hook call" .format (notincall ),
377
+ "cannot be found in this hook call" .format (notincall ),
362
378
stacklevel = 2 ,
363
379
)
364
380
break
365
381
366
- firstresult = self .spec .opts .get ("firstresult" , False )
367
- else :
368
- firstresult = False
369
-
382
+ def __call__ (self , * args : object , ** kwargs : object ) -> Any :
383
+ if args :
384
+ raise TypeError ("hook calling supports only keyword arguments" )
385
+ assert (
386
+ not self .is_historic ()
387
+ ), "Cannot directly call a historic hook - use call_historic instead."
388
+ self ._verify_all_args_are_provided (kwargs )
389
+ firstresult = self .spec .opts .get ("firstresult" , False ) if self .spec else False
370
390
return self ._hookexec (self .name , self .get_hookimpls (), kwargs , firstresult )
371
391
372
392
def call_historic (
@@ -382,6 +402,7 @@ def call_historic(
382
402
"""
383
403
assert self ._call_history is not None
384
404
kwargs = kwargs or {}
405
+ self ._verify_all_args_are_provided (kwargs )
385
406
self ._call_history .append ((kwargs , result_callback ))
386
407
# Historizing hooks don't return results.
387
408
# Remember firstresult isn't compatible with historic.
@@ -397,21 +418,28 @@ def call_extra(
397
418
) -> Any :
398
419
"""Call the hook with some additional temporarily participating
399
420
methods using the specified ``kwargs`` as call parameters."""
400
- old = list (self ._nonwrappers ), list (self ._wrappers )
421
+ assert (
422
+ not self .is_historic ()
423
+ ), "Cannot directly call a historic hook - use call_historic instead."
424
+ self ._verify_all_args_are_provided (kwargs )
425
+ opts : "_HookImplOpts" = {
426
+ "hookwrapper" : False ,
427
+ "optionalhook" : False ,
428
+ "trylast" : False ,
429
+ "tryfirst" : False ,
430
+ "specname" : None ,
431
+ }
432
+ hookimpls = self .get_hookimpls ()
401
433
for method in methods :
402
- opts : "_HookImplOpts" = {
403
- "hookwrapper" : False ,
404
- "optionalhook" : False ,
405
- "trylast" : False ,
406
- "tryfirst" : False ,
407
- "specname" : None ,
408
- }
409
434
hookimpl = HookImpl (None , "<temp>" , method , opts )
410
- self ._add_hookimpl (hookimpl )
411
- try :
412
- return self (** kwargs )
413
- finally :
414
- self ._nonwrappers , self ._wrappers = old
435
+ # Find last non-tryfirst nonwrapper method.
436
+ i = len (hookimpls ) - 1
437
+ until = len (self ._nonwrappers )
438
+ while i >= until and hookimpls [i ].tryfirst :
439
+ i -= 1
440
+ hookimpls .insert (i + 1 , hookimpl )
441
+ firstresult = self .spec .opts .get ("firstresult" , False ) if self .spec else False
442
+ return self ._hookexec (self .name , hookimpls , kwargs , firstresult )
415
443
416
444
def _maybe_apply_history (self , method : "HookImpl" ) -> None :
417
445
"""Apply call history to a new hookimpl if it is marked as historic."""
@@ -426,14 +454,27 @@ def _maybe_apply_history(self, method: "HookImpl") -> None:
426
454
427
455
428
456
class HookImpl :
457
+ __slots__ = (
458
+ "function" ,
459
+ "argnames" ,
460
+ "kwargnames" ,
461
+ "plugin" ,
462
+ "opts" ,
463
+ "plugin_name" ,
464
+ "hookwrapper" ,
465
+ "optionalhook" ,
466
+ "tryfirst" ,
467
+ "trylast" ,
468
+ )
469
+
429
470
def __init__ (
430
471
self ,
431
472
plugin : _Plugin ,
432
473
plugin_name : str ,
433
474
function : _HookImplFunction [object ],
434
475
hook_impl_opts : "_HookImplOpts" ,
435
476
) -> None :
436
- self .function = function
477
+ self .function : "Final" = function
437
478
self .argnames , self .kwargnames = varnames (self .function )
438
479
self .plugin = plugin
439
480
self .opts = hook_impl_opts
@@ -448,6 +489,16 @@ def __repr__(self) -> str:
448
489
449
490
450
491
class HookSpec :
492
+ __slots__ = (
493
+ "namespace" ,
494
+ "function" ,
495
+ "name" ,
496
+ "argnames" ,
497
+ "kwargnames" ,
498
+ "opts" ,
499
+ "warn_on_impl" ,
500
+ )
501
+
451
502
def __init__ (self , namespace : _Namespace , name : str , opts : "_HookSpecOpts" ) -> None :
452
503
self .namespace = namespace
453
504
self .function : Callable [..., object ] = getattr (namespace , name )
0 commit comments