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
@@ -331,7 +340,9 @@ def __init__(self, project_name, implprefix=None):
331340 self .hook = _HookRelay (self .trace .root .get ("hook" ))
332341 self ._implprefix = implprefix
333342 self ._inner_hookexec = lambda hook , methods , kwargs : \
334- _MultiCall (methods , kwargs , hook .spec_opts ).execute ()
343+ _MultiCall (
344+ methods , kwargs , specopts = hook .spec_opts , hook = hook
345+ ).execute ()
335346
336347 def _hookexec (self , hook , methods , kwargs ):
337348 # called from all hookcaller instances.
@@ -477,14 +488,16 @@ def _verify_hook(self, hook, hookimpl):
477488 "Plugin %r\n hook %r\n historic incompatible to hookwrapper" %
478489 (hookimpl .plugin_name , hook .name ))
479490
480- for arg in hookimpl .argnames :
481- if arg not in hook .argnames :
482- raise PluginValidationError (
483- "Plugin %r\n hook %r\n argument %r not available\n "
484- "plugin definition: %s\n "
485- "available hookargs: %s" %
486- (hookimpl .plugin_name , hook .name , arg ,
487- _formatdef (hookimpl .function ), ", " .join (hook .argnames )))
491+ # positional arg checking
492+ notinspec = set (hookimpl .argnames ), set (hook .argnames )
493+ if notinspec :
494+ raise PluginValidationError (
495+ "Plugin %r for hook %r\n hookimpl definition: %s\n "
496+ "Positional args %s are declared in the hookimpl but "
497+ "can not be found in the hookspec" %
498+ (hookimpl .plugin_name , hook .name ,
499+ _formatdef (hookimpl .function ), notinspec )
500+ )
488501
489502 def check_pending (self ):
490503 """ Verify that all hooks which have not been verified against
@@ -591,24 +604,25 @@ class _MultiCall:
591604 # so we can remove it soon, allowing to avoid the below recursion
592605 # in execute() and simplify/speed up the execute loop.
593606
594- def __init__ (self , hook_impls , kwargs , specopts = {}):
607+ def __init__ (self , hook_impls , kwargs , specopts = {}, hook = None ):
608+ self .hook = hook
595609 self .hook_impls = hook_impls
596- self .kwargs = kwargs
597- self .kwargs ["__multicall__" ] = self
598- self .specopts = specopts
610+ self .caller_kwargs = kwargs # come from _HookCaller.__call__()
611+ self .caller_kwargs ["__multicall__" ] = self
612+ self .specopts = hook . spec_opts if hook else specopts
599613
600614 def execute (self ):
601- all_kwargs = self .kwargs
615+ caller_kwargs = self .caller_kwargs
602616 self .results = results = []
603617 firstresult = self .specopts .get ("firstresult" )
604618
605619 while self .hook_impls :
606620 hook_impl = self .hook_impls .pop ()
607621 try :
608- args = [all_kwargs [argname ] for argname in hook_impl .argnames ]
622+ args = [caller_kwargs [argname ] for argname in hook_impl .argnames ]
609623 except KeyError :
610624 for argname in hook_impl .argnames :
611- if argname not in all_kwargs :
625+ if argname not in caller_kwargs :
612626 raise HookCallError (
613627 "hook call must provide argument %r" % (argname ,))
614628 if hook_impl .hookwrapper :
@@ -626,7 +640,7 @@ def __repr__(self):
626640 status = "%d meths" % (len (self .hook_impls ),)
627641 if hasattr (self , "results" ):
628642 status = ("%d results, " % len (self .results )) + status
629- return "<_MultiCall %s, kwargs=%r>" % (status , self .kwargs )
643+ return "<_MultiCall %s, kwargs=%r>" % (status , self .caller_kwargs )
630644
631645
632646def varnames (func ):
@@ -659,7 +673,11 @@ def varnames(func):
659673 return ()
660674
661675 args , defaults = spec .args , spec .defaults
662- args = args [:- len (defaults )] if defaults else args
676+ if defaults :
677+ index = - len (defaults )
678+ args , defaults = args [:index ], args [index :]
679+ else :
680+ defaults = []
663681
664682 # strip any implicit instance arg
665683 if args :
@@ -670,10 +688,10 @@ def varnames(func):
670688
671689 assert "self" not in args # best naming practises check?
672690 try :
673- cache ["_varnames" ] = args
691+ cache ["_varnames" ] = args , defaults
674692 except TypeError :
675693 pass
676- return tuple ( args )
694+ return args , defaults
677695
678696
679697class _HookRelay :
@@ -692,6 +710,8 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
692710 self ._wrappers = []
693711 self ._nonwrappers = []
694712 self ._hookexec = hook_execute
713+ self .argnames = None
714+ self .kwargnames = None
695715 if specmodule_or_class is not None :
696716 assert spec_opts is not None
697717 self .set_specification (specmodule_or_class , spec_opts )
@@ -703,7 +723,8 @@ def set_specification(self, specmodule_or_class, spec_opts):
703723 assert not self .has_spec ()
704724 self ._specmodule_or_class = specmodule_or_class
705725 specfunc = getattr (specmodule_or_class , self .name )
706- argnames = varnames (specfunc )
726+ # get spec arg signature
727+ argnames , self .kwargnames = varnames (specfunc )
707728 self .argnames = ["__multicall__" ] + list (argnames )
708729 self .spec_opts = spec_opts
709730 if spec_opts .get ("historic" ):
@@ -723,6 +744,8 @@ def remove(wrappers):
723744 raise ValueError ("plugin %r not found" % (plugin ,))
724745
725746 def _add_hookimpl (self , hookimpl ):
747+ """A an implementation to the callback chain.
748+ """
726749 if hookimpl .hookwrapper :
727750 methods = self ._wrappers
728751 else :
@@ -744,6 +767,13 @@ def __repr__(self):
744767
745768 def __call__ (self , ** kwargs ):
746769 assert not self .is_historic ()
770+ notincall = set (self .argnames ) - set (kwargs .keys ())
771+ if notincall :
772+ warnings .warn (
773+ "Positional arg(s) %s are declared in the hookspec "
774+ "but can not be found in this hook call" % notincall ,
775+ FutureWarning
776+ )
747777 return self ._hookexec (self , self ._nonwrappers + self ._wrappers , kwargs )
748778
749779 def call_historic (self , proc = None , kwargs = None ):
@@ -773,6 +803,8 @@ def call_extra(self, methods, kwargs):
773803 self ._nonwrappers , self ._wrappers = old
774804
775805 def _maybe_apply_history (self , method ):
806+ """Apply call history to a new hookimpl if it is marked as historic.
807+ """
776808 if self .is_historic ():
777809 for kwargs , proc in self ._call_history :
778810 res = self ._hookexec (self , [method ], kwargs )
@@ -783,21 +815,13 @@ def _maybe_apply_history(self, method):
783815class HookImpl :
784816 def __init__ (self , plugin , plugin_name , function , hook_impl_opts ):
785817 self .function = function
786- self .argnames = varnames (self .function )
818+ self .argnames , self . kwargnames = varnames (self .function )
787819 self .plugin = plugin
788820 self .opts = hook_impl_opts
789821 self .plugin_name = plugin_name
790822 self .__dict__ .update (hook_impl_opts )
791823
792824
793- class PluginValidationError (Exception ):
794- """ plugin failed validation. """
795-
796-
797- class HookCallError (Exception ):
798- """ Hook was called wrongly. """
799-
800-
801825if hasattr (inspect , 'signature' ):
802826 def _formatdef (func ):
803827 return "%s%s" % (
0 commit comments