11import sys
22import inspect
3+ import warnings
34
45__version__ = '0.5.0'
56
910_py3 = sys .version_info > (3 , 0 )
1011
1112
13+ class PluginValidationError (Exception ):
14+ """ plugin failed validation. """
15+
16+
17+ class HookCallError (Exception ):
18+ """ Hook was called wrongly. """
19+
20+
1221class HookspecMarker (object ):
1322 """ Decorator helper class for marking functions as hook specifications.
1423
@@ -266,7 +275,9 @@ def __init__(self, project_name, implprefix=None):
266275 self .hook = _HookRelay (self .trace .root .get ("hook" ))
267276 self ._implprefix = implprefix
268277 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 ()
270281
271282 def _hookexec (self , hook , methods , kwargs ):
272283 # called from all hookcaller instances.
@@ -412,14 +423,16 @@ def _verify_hook(self, hook, hookimpl):
412423 "Plugin %r\n hook %r\n historic incompatible to hookwrapper" %
413424 (hookimpl .plugin_name , hook .name ))
414425
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+ )
423436
424437 def check_pending (self ):
425438 """ Verify that all hooks which have not been verified against
@@ -526,24 +539,25 @@ class _MultiCall(object):
526539 # so we can remove it soon, allowing to avoid the below recursion
527540 # in execute() and simplify/speed up the execute loop.
528541
529- def __init__ (self , hook_impls , kwargs , specopts = {}):
542+ def __init__ (self , hook_impls , kwargs , specopts = {}, hook = None ):
543+ self .hook = hook
530544 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
534548
535549 def execute (self ):
536- all_kwargs = self .kwargs
550+ caller_kwargs = self .caller_kwargs
537551 self .results = results = []
538552 firstresult = self .specopts .get ("firstresult" )
539553
540554 while self .hook_impls :
541555 hook_impl = self .hook_impls .pop ()
542556 try :
543- args = [all_kwargs [argname ] for argname in hook_impl .argnames ]
557+ args = [caller_kwargs [argname ] for argname in hook_impl .argnames ]
544558 except KeyError :
545559 for argname in hook_impl .argnames :
546- if argname not in all_kwargs :
560+ if argname not in caller_kwargs :
547561 raise HookCallError (
548562 "hook call must provide argument %r" % (argname ,))
549563 if hook_impl .hookwrapper :
@@ -561,15 +575,15 @@ def __repr__(self):
561575 status = "%d meths" % (len (self .hook_impls ),)
562576 if hasattr (self , "results" ):
563577 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 )
565579
566580
567581def 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.
569584
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.
573587 """
574588 cache = getattr (func , "__dict__" , {})
575589 try :
@@ -581,7 +595,7 @@ def varnames(func):
581595 try :
582596 func = func .__init__
583597 except AttributeError :
584- return ()
598+ return (), ()
585599 elif not inspect .isroutine (func ): # callable object?
586600 try :
587601 func = getattr (func , '__call__' , func )
@@ -591,10 +605,14 @@ def varnames(func):
591605 try : # func MUST be a function or method here or we won't parse any args
592606 spec = inspect .getargspec (func )
593607 except TypeError :
594- return ()
608+ return (), ()
595609
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 = ()
598616
599617 # strip any implicit instance arg
600618 if args :
@@ -605,10 +623,10 @@ def varnames(func):
605623
606624 assert "self" not in args # best naming practises check?
607625 try :
608- cache ["_varnames" ] = args
626+ cache ["_varnames" ] = args , defaults
609627 except TypeError :
610628 pass
611- return tuple ( args )
629+ return args , defaults
612630
613631
614632class _HookRelay (object ):
@@ -627,6 +645,8 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
627645 self ._wrappers = []
628646 self ._nonwrappers = []
629647 self ._hookexec = hook_execute
648+ self .argnames = None
649+ self .kwargnames = None
630650 if specmodule_or_class is not None :
631651 assert spec_opts is not None
632652 self .set_specification (specmodule_or_class , spec_opts )
@@ -638,7 +658,8 @@ def set_specification(self, specmodule_or_class, spec_opts):
638658 assert not self .has_spec ()
639659 self ._specmodule_or_class = specmodule_or_class
640660 specfunc = getattr (specmodule_or_class , self .name )
641- argnames = varnames (specfunc )
661+ # get spec arg signature
662+ argnames , self .kwargnames = varnames (specfunc )
642663 self .argnames = ["__multicall__" ] + list (argnames )
643664 self .spec_opts = spec_opts
644665 if spec_opts .get ("historic" ):
@@ -658,6 +679,8 @@ def remove(wrappers):
658679 raise ValueError ("plugin %r not found" % (plugin ,))
659680
660681 def _add_hookimpl (self , hookimpl ):
682+ """A an implementation to the callback chain.
683+ """
661684 if hookimpl .hookwrapper :
662685 methods = self ._wrappers
663686 else :
@@ -679,6 +702,15 @@ def __repr__(self):
679702
680703 def __call__ (self , ** kwargs ):
681704 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+ )
682714 return self ._hookexec (self , self ._nonwrappers + self ._wrappers , kwargs )
683715
684716 def call_historic (self , proc = None , kwargs = None ):
@@ -708,6 +740,8 @@ def call_extra(self, methods, kwargs):
708740 self ._nonwrappers , self ._wrappers = old
709741
710742 def _maybe_apply_history (self , method ):
743+ """Apply call history to a new hookimpl if it is marked as historic.
744+ """
711745 if self .is_historic ():
712746 for kwargs , proc in self ._call_history :
713747 res = self ._hookexec (self , [method ], kwargs )
@@ -718,21 +752,13 @@ def _maybe_apply_history(self, method):
718752class HookImpl (object ):
719753 def __init__ (self , plugin , plugin_name , function , hook_impl_opts ):
720754 self .function = function
721- self .argnames = varnames (self .function )
755+ self .argnames , self . kwargnames = varnames (self .function )
722756 self .plugin = plugin
723757 self .opts = hook_impl_opts
724758 self .plugin_name = plugin_name
725759 self .__dict__ .update (hook_impl_opts )
726760
727761
728- class PluginValidationError (Exception ):
729- """ plugin failed validation. """
730-
731-
732- class HookCallError (Exception ):
733- """ Hook was called wrongly. """
734-
735-
736762if hasattr (inspect , 'signature' ):
737763 def _formatdef (func ):
738764 return "%s%s" % (
0 commit comments