1
1
import sys
2
2
import inspect
3
+ import warnings
3
4
4
5
__version__ = '0.5.0'
5
6
9
10
_py3 = sys .version_info > (3 , 0 )
10
11
11
12
13
+ class PluginValidationError (Exception ):
14
+ """ plugin failed validation. """
15
+
16
+
17
+ class HookCallError (Exception ):
18
+ """ Hook was called wrongly. """
19
+
20
+
12
21
class HookspecMarker (object ):
13
22
""" Decorator helper class for marking functions as hook specifications.
14
23
@@ -266,7 +275,9 @@ def __init__(self, project_name, implprefix=None):
266
275
self .hook = _HookRelay (self .trace .root .get ("hook" ))
267
276
self ._implprefix = implprefix
268
277
self ._inner_hookexec = lambda hook , methods , kwargs : \
269
- _MultiCall (methods , kwargs , hook .spec_opts ).execute ()
278
+ _MultiCall (
279
+ methods , kwargs , specopts = hook .spec_opts , hook = hook
280
+ ).execute ()
270
281
271
282
def _hookexec (self , hook , methods , kwargs ):
272
283
# called from all hookcaller instances.
@@ -412,14 +423,16 @@ def _verify_hook(self, hook, hookimpl):
412
423
"Plugin %r\n hook %r\n historic incompatible to hookwrapper" %
413
424
(hookimpl .plugin_name , hook .name ))
414
425
415
- for arg in hookimpl .argnames :
416
- if arg not in hook .argnames :
417
- raise PluginValidationError (
418
- "Plugin %r\n hook %r\n argument %r not available\n "
419
- "plugin definition: %s\n "
420
- "available hookargs: %s" %
421
- (hookimpl .plugin_name , hook .name , arg ,
422
- _formatdef (hookimpl .function ), ", " .join (hook .argnames )))
426
+ # positional arg checking
427
+ notinspec = set (hookimpl .argnames ) - set (hook .argnames )
428
+ if notinspec :
429
+ raise PluginValidationError (
430
+ "Plugin %r for hook %r\n hookimpl definition: %s\n "
431
+ "Argument(s) %s are declared in the hookimpl but "
432
+ "can not be found in the hookspec" %
433
+ (hookimpl .plugin_name , hook .name ,
434
+ _formatdef (hookimpl .function ), notinspec )
435
+ )
423
436
424
437
def check_pending (self ):
425
438
""" Verify that all hooks which have not been verified against
@@ -526,24 +539,25 @@ class _MultiCall(object):
526
539
# so we can remove it soon, allowing to avoid the below recursion
527
540
# in execute() and simplify/speed up the execute loop.
528
541
529
- def __init__ (self , hook_impls , kwargs , specopts = {}):
542
+ def __init__ (self , hook_impls , kwargs , specopts = {}, hook = None ):
543
+ self .hook = hook
530
544
self .hook_impls = hook_impls
531
- self .kwargs = kwargs
532
- self .kwargs ["__multicall__" ] = self
533
- self .specopts = specopts
545
+ self .caller_kwargs = kwargs # come from _HookCaller.__call__()
546
+ self .caller_kwargs ["__multicall__" ] = self
547
+ self .specopts = hook . spec_opts if hook else specopts
534
548
535
549
def execute (self ):
536
- all_kwargs = self .kwargs
550
+ caller_kwargs = self .caller_kwargs
537
551
self .results = results = []
538
552
firstresult = self .specopts .get ("firstresult" )
539
553
540
554
while self .hook_impls :
541
555
hook_impl = self .hook_impls .pop ()
542
556
try :
543
- args = [all_kwargs [argname ] for argname in hook_impl .argnames ]
557
+ args = [caller_kwargs [argname ] for argname in hook_impl .argnames ]
544
558
except KeyError :
545
559
for argname in hook_impl .argnames :
546
- if argname not in all_kwargs :
560
+ if argname not in caller_kwargs :
547
561
raise HookCallError (
548
562
"hook call must provide argument %r" % (argname ,))
549
563
if hook_impl .hookwrapper :
@@ -561,15 +575,15 @@ def __repr__(self):
561
575
status = "%d meths" % (len (self .hook_impls ),)
562
576
if hasattr (self , "results" ):
563
577
status = ("%d results, " % len (self .results )) + status
564
- return "<_MultiCall %s, kwargs=%r>" % (status , self .kwargs )
578
+ return "<_MultiCall %s, kwargs=%r>" % (status , self .caller_kwargs )
565
579
566
580
567
581
def varnames (func ):
568
- """Return argument name tuple for a function, method, class or callable.
582
+ """Return tuple of positional and keywrord argument names for a function,
583
+ method, class or callable.
569
584
570
- In case of a class, its "__init__" method is considered.
571
- For methods the "self" parameter is not included unless you are passing
572
- an unbound method with Python3 (which has no support for unbound methods)
585
+ In case of a class, its ``__init__`` method is considered.
586
+ For methods the ``self`` parameter is not included.
573
587
"""
574
588
cache = getattr (func , "__dict__" , {})
575
589
try :
@@ -581,7 +595,7 @@ def varnames(func):
581
595
try :
582
596
func = func .__init__
583
597
except AttributeError :
584
- return ()
598
+ return (), ()
585
599
elif not inspect .isroutine (func ): # callable object?
586
600
try :
587
601
func = getattr (func , '__call__' , func )
@@ -591,10 +605,14 @@ def varnames(func):
591
605
try : # func MUST be a function or method here or we won't parse any args
592
606
spec = inspect .getargspec (func )
593
607
except TypeError :
594
- return ()
608
+ return (), ()
595
609
596
- args , defaults = spec .args , spec .defaults
597
- args = args [:- len (defaults )] if defaults else args
610
+ args , defaults = tuple (spec .args ), spec .defaults
611
+ if defaults :
612
+ index = - len (defaults )
613
+ args , defaults = args [:index ], tuple (args [index :])
614
+ else :
615
+ defaults = ()
598
616
599
617
# strip any implicit instance arg
600
618
if args :
@@ -605,10 +623,10 @@ def varnames(func):
605
623
606
624
assert "self" not in args # best naming practises check?
607
625
try :
608
- cache ["_varnames" ] = args
626
+ cache ["_varnames" ] = args , defaults
609
627
except TypeError :
610
628
pass
611
- return tuple ( args )
629
+ return args , defaults
612
630
613
631
614
632
class _HookRelay (object ):
@@ -627,6 +645,8 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
627
645
self ._wrappers = []
628
646
self ._nonwrappers = []
629
647
self ._hookexec = hook_execute
648
+ self .argnames = None
649
+ self .kwargnames = None
630
650
if specmodule_or_class is not None :
631
651
assert spec_opts is not None
632
652
self .set_specification (specmodule_or_class , spec_opts )
@@ -638,7 +658,8 @@ def set_specification(self, specmodule_or_class, spec_opts):
638
658
assert not self .has_spec ()
639
659
self ._specmodule_or_class = specmodule_or_class
640
660
specfunc = getattr (specmodule_or_class , self .name )
641
- argnames = varnames (specfunc )
661
+ # get spec arg signature
662
+ argnames , self .kwargnames = varnames (specfunc )
642
663
self .argnames = ["__multicall__" ] + list (argnames )
643
664
self .spec_opts = spec_opts
644
665
if spec_opts .get ("historic" ):
@@ -658,6 +679,8 @@ def remove(wrappers):
658
679
raise ValueError ("plugin %r not found" % (plugin ,))
659
680
660
681
def _add_hookimpl (self , hookimpl ):
682
+ """A an implementation to the callback chain.
683
+ """
661
684
if hookimpl .hookwrapper :
662
685
methods = self ._wrappers
663
686
else :
@@ -679,6 +702,15 @@ def __repr__(self):
679
702
680
703
def __call__ (self , ** kwargs ):
681
704
assert not self .is_historic ()
705
+ if self .argnames :
706
+ notincall = set (self .argnames ) - set (['__multicall__' ]) - set (
707
+ kwargs .keys ())
708
+ if notincall :
709
+ warnings .warn (
710
+ "Argument(s) {0} which are declared in the hookspec "
711
+ "can not be found in this hook call"
712
+ .format (tuple (notincall ))
713
+ )
682
714
return self ._hookexec (self , self ._nonwrappers + self ._wrappers , kwargs )
683
715
684
716
def call_historic (self , proc = None , kwargs = None ):
@@ -708,6 +740,8 @@ def call_extra(self, methods, kwargs):
708
740
self ._nonwrappers , self ._wrappers = old
709
741
710
742
def _maybe_apply_history (self , method ):
743
+ """Apply call history to a new hookimpl if it is marked as historic.
744
+ """
711
745
if self .is_historic ():
712
746
for kwargs , proc in self ._call_history :
713
747
res = self ._hookexec (self , [method ], kwargs )
@@ -718,21 +752,13 @@ def _maybe_apply_history(self, method):
718
752
class HookImpl (object ):
719
753
def __init__ (self , plugin , plugin_name , function , hook_impl_opts ):
720
754
self .function = function
721
- self .argnames = varnames (self .function )
755
+ self .argnames , self . kwargnames = varnames (self .function )
722
756
self .plugin = plugin
723
757
self .opts = hook_impl_opts
724
758
self .plugin_name = plugin_name
725
759
self .__dict__ .update (hook_impl_opts )
726
760
727
761
728
- class PluginValidationError (Exception ):
729
- """ plugin failed validation. """
730
-
731
-
732
- class HookCallError (Exception ):
733
- """ Hook was called wrongly. """
734
-
735
-
736
762
if hasattr (inspect , 'signature' ):
737
763
def _formatdef (func ):
738
764
return "%s%s" % (
0 commit comments