27
27
from ._hook_markers import varnames
28
28
29
29
30
+ def _insert_hookimpl_into_list (hookimpl : HookImpl , target_list : list [HookImpl ]) -> None :
31
+ """Insert a hookimpl into the target list maintaining proper ordering.
32
+
33
+ The ordering is: [trylast, normal, tryfirst]
34
+ """
35
+ if hookimpl .trylast :
36
+ target_list .insert (0 , hookimpl )
37
+ elif hookimpl .tryfirst :
38
+ target_list .append (hookimpl )
39
+ else :
40
+ # find last non-tryfirst method
41
+ i = len (target_list ) - 1
42
+ while i >= 0 and target_list [i ].tryfirst :
43
+ i -= 1
44
+ target_list .insert (i + 1 , hookimpl )
45
+
46
+
30
47
@runtime_checkable
31
48
class HookCaller (Protocol ):
32
49
"""Protocol defining the interface for hook callers."""
@@ -183,16 +200,7 @@ def get_hookimpls(self) -> list[HookImpl]:
183
200
def _add_hookimpl (self , hookimpl : HookImpl ) -> None :
184
201
"""Add an implementation to the callback chain."""
185
202
# Historic hooks don't support wrappers - simpler ordering
186
- if hookimpl .trylast :
187
- self ._hookimpls .insert (0 , hookimpl )
188
- elif hookimpl .tryfirst :
189
- self ._hookimpls .append (hookimpl )
190
- else :
191
- # find last non-tryfirst method
192
- i = len (self ._hookimpls ) - 1
193
- while i >= 0 and self ._hookimpls [i ].tryfirst :
194
- i -= 1
195
- self ._hookimpls .insert (i + 1 , hookimpl )
203
+ _insert_hookimpl_into_list (hookimpl , self ._hookimpls )
196
204
197
205
# Apply history to the newly added hookimpl
198
206
self ._maybe_apply_history (hookimpl )
@@ -262,12 +270,14 @@ class NormalHookCaller:
262
270
"name" ,
263
271
"spec" ,
264
272
"_hookexec" ,
265
- "_hookimpls" ,
273
+ "_normal_hookimpls" ,
274
+ "_wrapper_hookimpls" ,
266
275
)
267
276
name : Final [str ]
268
277
spec : HookSpec | None
269
278
_hookexec : Final [_HookExec ]
270
- _hookimpls : Final [list [HookImpl ]]
279
+ _normal_hookimpls : Final [list [HookImpl ]]
280
+ _wrapper_hookimpls : Final [list [HookImpl ]]
271
281
272
282
def __init__ (
273
283
self ,
@@ -280,14 +290,12 @@ def __init__(
280
290
#: Name of the hook getting called.
281
291
self .name = name
282
292
self ._hookexec = hook_execute
283
- # The hookimpls list. The caller iterates it *in reverse*. Format:
284
- # 1. trylast nonwrappers
285
- # 2. nonwrappers
286
- # 3. tryfirst nonwrappers
287
- # 4. trylast wrappers
288
- # 5. wrappers
289
- # 6. tryfirst wrappers
290
- self ._hookimpls = []
293
+ # Split hook implementations into two lists for simpler management:
294
+ # Normal hooks: [trylast, normal, tryfirst]
295
+ # Wrapper hooks: [trylast, normal, tryfirst]
296
+ # Combined execution order: normal_hooks + wrapper_hooks (reversed)
297
+ self ._normal_hookimpls = []
298
+ self ._wrapper_hookimpls = []
291
299
# TODO: Document, or make private.
292
300
self .spec : HookSpec | None = None
293
301
if specmodule_or_class is not None :
@@ -345,39 +353,34 @@ def is_historic(self) -> bool:
345
353
return False # HookCaller is never historic
346
354
347
355
def _remove_plugin (self , plugin : _Plugin ) -> None :
348
- for i , method in enumerate (self ._hookimpls ):
356
+ # Try to remove from normal hookimpls first
357
+ for i , method in enumerate (self ._normal_hookimpls ):
349
358
if method .plugin == plugin :
350
- del self ._hookimpls [i ]
359
+ del self ._normal_hookimpls [i ]
360
+ return
361
+ # Then try wrapper hookimpls
362
+ for i , method in enumerate (self ._wrapper_hookimpls ):
363
+ if method .plugin == plugin :
364
+ del self ._wrapper_hookimpls [i ]
351
365
return
352
366
raise ValueError (f"plugin { plugin !r} not found" )
353
367
354
368
def get_hookimpls (self ) -> list [HookImpl ]:
355
369
"""Get all registered hook implementations for this hook."""
356
- return self ._hookimpls .copy ()
370
+ # Combine normal hooks and wrapper hooks in the correct order
371
+ # Normal hooks come first, then wrapper hooks (execution order is reversed)
372
+ return [* self ._normal_hookimpls , * self ._wrapper_hookimpls ]
357
373
358
374
def _add_hookimpl (self , hookimpl : HookImpl ) -> None :
359
375
"""Add an implementation to the callback chain."""
360
- for i , method in enumerate (self ._hookimpls ):
361
- if method .hookwrapper or method .wrapper :
362
- splitpoint = i
363
- break
364
- else :
365
- splitpoint = len (self ._hookimpls )
376
+ # Choose the appropriate list based on wrapper type
366
377
if hookimpl .hookwrapper or hookimpl .wrapper :
367
- start , end = splitpoint , len ( self ._hookimpls )
378
+ target_list = self ._wrapper_hookimpls
368
379
else :
369
- start , end = 0 , splitpoint
380
+ target_list = self . _normal_hookimpls
370
381
371
- if hookimpl .trylast :
372
- self ._hookimpls .insert (start , hookimpl )
373
- elif hookimpl .tryfirst :
374
- self ._hookimpls .insert (end , hookimpl )
375
- else :
376
- # find last non-tryfirst method
377
- i = end - 1
378
- while i >= start and self ._hookimpls [i ].tryfirst :
379
- i -= 1
380
- self ._hookimpls .insert (i + 1 , hookimpl )
382
+ # Insert in the correct position within the chosen list
383
+ _insert_hookimpl_into_list (hookimpl , target_list )
381
384
382
385
def __repr__ (self ) -> str :
383
386
return f"<NormalHookCaller { self .name !r} >"
@@ -395,7 +398,9 @@ def __call__(self, **kwargs: object) -> Any:
395
398
self .spec .verify_all_args_are_provided (kwargs )
396
399
firstresult = self .spec .config .firstresult if self .spec else False
397
400
# Copy because plugins may register other plugins during iteration (#438).
398
- return self ._hookexec (self .name , self ._hookimpls .copy (), kwargs , firstresult )
401
+ # Combine normal and wrapper hookimpls in correct order
402
+ all_hookimpls = [* self ._normal_hookimpls , * self ._wrapper_hookimpls ]
403
+ return self ._hookexec (self .name , all_hookimpls , kwargs , firstresult )
399
404
400
405
def call_historic (
401
406
self ,
@@ -421,19 +426,16 @@ def call_extra(
421
426
if self .spec :
422
427
self .spec .verify_all_args_are_provided (kwargs )
423
428
config = HookimplConfiguration ()
424
- hookimpls = self ._hookimpls .copy ()
429
+ # Start with copies of our separate lists
430
+ normal_hookimpls = self ._normal_hookimpls .copy ()
431
+
425
432
for method in methods :
426
433
hookimpl = HookImpl (None , "<temp>" , method , config )
427
- # Find last non-tryfirst nonwrapper method.
428
- i = len (hookimpls ) - 1
429
- while i >= 0 and (
430
- # Skip wrappers.
431
- (hookimpls [i ].hookwrapper or hookimpls [i ].wrapper )
432
- # Skip tryfirst nonwrappers.
433
- or hookimpls [i ].tryfirst
434
- ):
435
- i -= 1
436
- hookimpls .insert (i + 1 , hookimpl )
434
+ # Add temporary methods to the normal hookimpls list
435
+ _insert_hookimpl_into_list (hookimpl , normal_hookimpls )
436
+
437
+ # Combine the lists
438
+ hookimpls = [* normal_hookimpls , * self ._wrapper_hookimpls ]
437
439
firstresult = self .spec .config .firstresult if self .spec else False
438
440
return self ._hookexec (self .name , hookimpls , kwargs , firstresult )
439
441
0 commit comments