6666"""
6767import sys
6868import inspect
69+ import warnings
6970
7071__version__ = '0.5.0'
7172
7576_py3 = sys .version_info > (3 , 0 )
7677
7778
79+ class PluginValidationError (Exception ):
80+ """ plugin failed validation. """
81+
82+
83+ class HookCallError (Exception ):
84+ """ Hook was called wrongly. """
85+
86+
7887class HookspecMarker :
7988 """ Decorator helper class for marking functions as hook specifications.
8089
@@ -332,7 +341,9 @@ def __init__(self, project_name, implprefix=None):
332341 self .hook = _HookRelay (self .trace .root .get ("hook" ))
333342 self ._implprefix = implprefix
334343 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 ()
336347
337348 def _hookexec (self , hook , methods , kwargs ):
338349 # called from all hookcaller instances.
@@ -478,14 +489,16 @@ def _verify_hook(self, hook, hookimpl):
478489 "Plugin %r\n hook %r\n historic incompatible to hookwrapper" %
479490 (hookimpl .plugin_name , hook .name ))
480491
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+ )
489502
490503 def check_pending (self ):
491504 """ Verify that all hooks which have not been verified against
@@ -592,24 +605,25 @@ class _MultiCall:
592605 # so we can remove it soon, allowing to avoid the below recursion
593606 # in execute() and simplify/speed up the execute loop.
594607
595- def __init__ (self , hook_impls , kwargs , specopts = {}):
608+ def __init__ (self , hook_impls , kwargs , specopts = {}, hook = None ):
609+ self .hook = hook
596610 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
600614
601615 def execute (self ):
602- all_kwargs = self .kwargs
616+ caller_kwargs = self .caller_kwargs
603617 self .results = results = []
604618 firstresult = self .specopts .get ("firstresult" )
605619
606620 while self .hook_impls :
607621 hook_impl = self .hook_impls .pop ()
608622 try :
609- args = [all_kwargs [argname ] for argname in hook_impl .argnames ]
623+ args = [caller_kwargs [argname ] for argname in hook_impl .argnames ]
610624 except KeyError :
611625 for argname in hook_impl .argnames :
612- if argname not in all_kwargs :
626+ if argname not in caller_kwargs :
613627 raise HookCallError (
614628 "hook call must provide argument %r" % (argname ,))
615629 if hook_impl .hookwrapper :
@@ -627,7 +641,7 @@ def __repr__(self):
627641 status = "%d meths" % (len (self .hook_impls ),)
628642 if hasattr (self , "results" ):
629643 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 )
631645
632646
633647def varnames (func ):
@@ -647,7 +661,7 @@ def varnames(func):
647661 try :
648662 func = func .__init__
649663 except AttributeError :
650- return ()
664+ return (), ()
651665 elif not inspect .isroutine (func ): # callable object?
652666 try :
653667 func = getattr (func , '__call__' , func )
@@ -657,10 +671,14 @@ def varnames(func):
657671 try : # func MUST be a function or method here or we won't parse any args
658672 spec = inspect .getargspec (func )
659673 except TypeError :
660- return ()
674+ return (), ()
661675
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 = ()
664682
665683 # strip any implicit instance arg
666684 if args :
@@ -671,10 +689,10 @@ def varnames(func):
671689
672690 assert "self" not in args # best naming practises check?
673691 try :
674- cache ["_varnames" ] = args
692+ cache ["_varnames" ] = args , defaults
675693 except TypeError :
676694 pass
677- return tuple ( args )
695+ return args , defaults
678696
679697
680698class _HookRelay :
@@ -693,6 +711,8 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
693711 self ._wrappers = []
694712 self ._nonwrappers = []
695713 self ._hookexec = hook_execute
714+ self .argnames = None
715+ self .kwargnames = None
696716 if specmodule_or_class is not None :
697717 assert spec_opts is not None
698718 self .set_specification (specmodule_or_class , spec_opts )
@@ -704,7 +724,8 @@ def set_specification(self, specmodule_or_class, spec_opts):
704724 assert not self .has_spec ()
705725 self ._specmodule_or_class = specmodule_or_class
706726 specfunc = getattr (specmodule_or_class , self .name )
707- argnames = varnames (specfunc )
727+ # get spec arg signature
728+ argnames , self .kwargnames = varnames (specfunc )
708729 self .argnames = ["__multicall__" ] + list (argnames )
709730 self .spec_opts = spec_opts
710731 if spec_opts .get ("historic" ):
@@ -724,6 +745,8 @@ def remove(wrappers):
724745 raise ValueError ("plugin %r not found" % (plugin ,))
725746
726747 def _add_hookimpl (self , hookimpl ):
748+ """A an implementation to the callback chain.
749+ """
727750 if hookimpl .hookwrapper :
728751 methods = self ._wrappers
729752 else :
@@ -745,6 +768,13 @@ def __repr__(self):
745768
746769 def __call__ (self , ** kwargs ):
747770 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+ )
748778 return self ._hookexec (self , self ._nonwrappers + self ._wrappers , kwargs )
749779
750780 def call_historic (self , proc = None , kwargs = None ):
@@ -774,6 +804,8 @@ def call_extra(self, methods, kwargs):
774804 self ._nonwrappers , self ._wrappers = old
775805
776806 def _maybe_apply_history (self , method ):
807+ """Apply call history to a new hookimpl if it is marked as historic.
808+ """
777809 if self .is_historic ():
778810 for kwargs , proc in self ._call_history :
779811 res = self ._hookexec (self , [method ], kwargs )
@@ -784,21 +816,13 @@ def _maybe_apply_history(self, method):
784816class HookImpl :
785817 def __init__ (self , plugin , plugin_name , function , hook_impl_opts ):
786818 self .function = function
787- self .argnames = varnames (self .function )
819+ self .argnames , self . kwargnames = varnames (self .function )
788820 self .plugin = plugin
789821 self .opts = hook_impl_opts
790822 self .plugin_name = plugin_name
791823 self .__dict__ .update (hook_impl_opts )
792824
793825
794- class PluginValidationError (Exception ):
795- """ plugin failed validation. """
796-
797-
798- class HookCallError (Exception ):
799- """ Hook was called wrongly. """
800-
801-
802826if hasattr (inspect , 'signature' ):
803827 def _formatdef (func ):
804828 return "%s%s" % (
0 commit comments