66
66
"""
67
67
import sys
68
68
import inspect
69
+ import warnings
69
70
70
71
__version__ = '0.5.0'
71
72
75
76
_py3 = sys .version_info > (3 , 0 )
76
77
77
78
79
+ class PluginValidationError (Exception ):
80
+ """ plugin failed validation. """
81
+
82
+
83
+ class HookCallError (Exception ):
84
+ """ Hook was called wrongly. """
85
+
86
+
78
87
class HookspecMarker :
79
88
""" Decorator helper class for marking functions as hook specifications.
80
89
@@ -332,7 +341,9 @@ def __init__(self, project_name, implprefix=None):
332
341
self .hook = _HookRelay (self .trace .root .get ("hook" ))
333
342
self ._implprefix = implprefix
334
343
self ._inner_hookexec = lambda hook , methods , kwargs : \
335
- _MultiCall (methods , kwargs , hook .spec_opts ).execute ()
344
+ _MultiCall (
345
+ methods , kwargs , specopts = hook .spec_opts , hook = hook
346
+ ).execute ()
336
347
337
348
def _hookexec (self , hook , methods , kwargs ):
338
349
# called from all hookcaller instances.
@@ -478,14 +489,16 @@ def _verify_hook(self, hook, hookimpl):
478
489
"Plugin %r\n hook %r\n historic incompatible to hookwrapper" %
479
490
(hookimpl .plugin_name , hook .name ))
480
491
481
- for arg in hookimpl .argnames :
482
- if arg not in hook .argnames :
483
- raise PluginValidationError (
484
- "Plugin %r\n hook %r\n argument %r not available\n "
485
- "plugin definition: %s\n "
486
- "available hookargs: %s" %
487
- (hookimpl .plugin_name , hook .name , arg ,
488
- _formatdef (hookimpl .function ), ", " .join (hook .argnames )))
492
+ # positional arg checking
493
+ notinspec = set (hookimpl .argnames ) - set (hook .argnames )
494
+ if notinspec :
495
+ raise PluginValidationError (
496
+ "Plugin %r for hook %r\n hookimpl definition: %s\n "
497
+ "Positional args %s are declared in the hookimpl but "
498
+ "can not be found in the hookspec" %
499
+ (hookimpl .plugin_name , hook .name ,
500
+ _formatdef (hookimpl .function ), notinspec )
501
+ )
489
502
490
503
def check_pending (self ):
491
504
""" Verify that all hooks which have not been verified against
@@ -592,24 +605,25 @@ class _MultiCall:
592
605
# so we can remove it soon, allowing to avoid the below recursion
593
606
# in execute() and simplify/speed up the execute loop.
594
607
595
- def __init__ (self , hook_impls , kwargs , specopts = {}):
608
+ def __init__ (self , hook_impls , kwargs , specopts = {}, hook = None ):
609
+ self .hook = hook
596
610
self .hook_impls = hook_impls
597
- self .kwargs = kwargs
598
- self .kwargs ["__multicall__" ] = self
599
- self .specopts = specopts
611
+ self .caller_kwargs = kwargs # come from _HookCaller.__call__()
612
+ self .caller_kwargs ["__multicall__" ] = self
613
+ self .specopts = hook . spec_opts if hook else specopts
600
614
601
615
def execute (self ):
602
- all_kwargs = self .kwargs
616
+ caller_kwargs = self .caller_kwargs
603
617
self .results = results = []
604
618
firstresult = self .specopts .get ("firstresult" )
605
619
606
620
while self .hook_impls :
607
621
hook_impl = self .hook_impls .pop ()
608
622
try :
609
- args = [all_kwargs [argname ] for argname in hook_impl .argnames ]
623
+ args = [caller_kwargs [argname ] for argname in hook_impl .argnames ]
610
624
except KeyError :
611
625
for argname in hook_impl .argnames :
612
- if argname not in all_kwargs :
626
+ if argname not in caller_kwargs :
613
627
raise HookCallError (
614
628
"hook call must provide argument %r" % (argname ,))
615
629
if hook_impl .hookwrapper :
@@ -627,7 +641,7 @@ def __repr__(self):
627
641
status = "%d meths" % (len (self .hook_impls ),)
628
642
if hasattr (self , "results" ):
629
643
status = ("%d results, " % len (self .results )) + status
630
- return "<_MultiCall %s, kwargs=%r>" % (status , self .kwargs )
644
+ return "<_MultiCall %s, kwargs=%r>" % (status , self .caller_kwargs )
631
645
632
646
633
647
def varnames (func ):
@@ -647,7 +661,7 @@ def varnames(func):
647
661
try :
648
662
func = func .__init__
649
663
except AttributeError :
650
- return ()
664
+ return (), ()
651
665
elif not inspect .isroutine (func ): # callable object?
652
666
try :
653
667
func = getattr (func , '__call__' , func )
@@ -657,10 +671,14 @@ def varnames(func):
657
671
try : # func MUST be a function or method here or we won't parse any args
658
672
spec = inspect .getargspec (func )
659
673
except TypeError :
660
- return ()
674
+ return (), ()
661
675
662
- args , defaults = spec .args , spec .defaults
663
- args = args [:- len (defaults )] if defaults else args
676
+ args , defaults = tuple (spec .args ), spec .defaults
677
+ if defaults :
678
+ index = - len (defaults )
679
+ args , defaults = args [:index ], tuple (args [index :])
680
+ else :
681
+ defaults = ()
664
682
665
683
# strip any implicit instance arg
666
684
if args :
@@ -671,10 +689,10 @@ def varnames(func):
671
689
672
690
assert "self" not in args # best naming practises check?
673
691
try :
674
- cache ["_varnames" ] = args
692
+ cache ["_varnames" ] = args , defaults
675
693
except TypeError :
676
694
pass
677
- return tuple ( args )
695
+ return args , defaults
678
696
679
697
680
698
class _HookRelay :
@@ -693,6 +711,8 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
693
711
self ._wrappers = []
694
712
self ._nonwrappers = []
695
713
self ._hookexec = hook_execute
714
+ self .argnames = None
715
+ self .kwargnames = None
696
716
if specmodule_or_class is not None :
697
717
assert spec_opts is not None
698
718
self .set_specification (specmodule_or_class , spec_opts )
@@ -704,7 +724,8 @@ def set_specification(self, specmodule_or_class, spec_opts):
704
724
assert not self .has_spec ()
705
725
self ._specmodule_or_class = specmodule_or_class
706
726
specfunc = getattr (specmodule_or_class , self .name )
707
- argnames = varnames (specfunc )
727
+ # get spec arg signature
728
+ argnames , self .kwargnames = varnames (specfunc )
708
729
self .argnames = ["__multicall__" ] + list (argnames )
709
730
self .spec_opts = spec_opts
710
731
if spec_opts .get ("historic" ):
@@ -724,6 +745,8 @@ def remove(wrappers):
724
745
raise ValueError ("plugin %r not found" % (plugin ,))
725
746
726
747
def _add_hookimpl (self , hookimpl ):
748
+ """A an implementation to the callback chain.
749
+ """
727
750
if hookimpl .hookwrapper :
728
751
methods = self ._wrappers
729
752
else :
@@ -745,6 +768,13 @@ def __repr__(self):
745
768
746
769
def __call__ (self , ** kwargs ):
747
770
assert not self .is_historic ()
771
+ notincall = set (self .argnames ) - set (kwargs .keys ())
772
+ if notincall :
773
+ warnings .warn (
774
+ "Positional arg(s) %s are declared in the hookspec "
775
+ "but can not be found in this hook call" % notincall ,
776
+ FutureWarning
777
+ )
748
778
return self ._hookexec (self , self ._nonwrappers + self ._wrappers , kwargs )
749
779
750
780
def call_historic (self , proc = None , kwargs = None ):
@@ -774,6 +804,8 @@ def call_extra(self, methods, kwargs):
774
804
self ._nonwrappers , self ._wrappers = old
775
805
776
806
def _maybe_apply_history (self , method ):
807
+ """Apply call history to a new hookimpl if it is marked as historic.
808
+ """
777
809
if self .is_historic ():
778
810
for kwargs , proc in self ._call_history :
779
811
res = self ._hookexec (self , [method ], kwargs )
@@ -784,21 +816,13 @@ def _maybe_apply_history(self, method):
784
816
class HookImpl :
785
817
def __init__ (self , plugin , plugin_name , function , hook_impl_opts ):
786
818
self .function = function
787
- self .argnames = varnames (self .function )
819
+ self .argnames , self . kwargnames = varnames (self .function )
788
820
self .plugin = plugin
789
821
self .opts = hook_impl_opts
790
822
self .plugin_name = plugin_name
791
823
self .__dict__ .update (hook_impl_opts )
792
824
793
825
794
- class PluginValidationError (Exception ):
795
- """ plugin failed validation. """
796
-
797
-
798
- class HookCallError (Exception ):
799
- """ Hook was called wrongly. """
800
-
801
-
802
826
if hasattr (inspect , 'signature' ):
803
827
def _formatdef (func ):
804
828
return "%s%s" % (
0 commit comments