6666"""
6767import sys
6868import inspect
69+ import copy
6970import warnings
7071
7172__version__ = '0.5.0'
@@ -340,10 +341,12 @@ def __init__(self, project_name, implprefix=None):
340341 self .trace = _TagTracer ().get ("pluginmanage" )
341342 self .hook = _HookRelay (self .trace .root .get ("hook" ))
342343 self ._implprefix = implprefix
343- self ._inner_hookexec = lambda hook , methods , kwargs : \
344- _MultiCall (
345- methods , kwargs , specopts = hook .spec_opts , hook = hook
346- ).execute ()
344+ self ._inner_hookexec = lambda hook , methods , kwargs : _MultiCall (
345+ methods ,
346+ kwargs ,
347+ firstresult = hook .spec .opts ['firstresult' ] if hook .spec else False ,
348+ hook = hook
349+ ).execute ()
347350
348351 def _hookexec (self , hook , methods , kwargs ):
349352 # called from all hookcaller instances.
@@ -490,7 +493,7 @@ def _verify_hook(self, hook, hookimpl):
490493 (hookimpl .plugin_name , hook .name ))
491494
492495 # positional arg checking
493- notinspec = set (hookimpl .argnames ) - set (hook .argnames )
496+ notinspec = set (hookimpl .argnames ) - set (hook .spec . argnames )
494497 if notinspec :
495498 raise PluginValidationError (
496499 "Plugin %r for hook %r\n hookimpl definition: %s\n "
@@ -583,8 +586,8 @@ def subset_hook_caller(self, name, remove_plugins):
583586 orig = getattr (self .hook , name )
584587 plugins_to_remove = [plug for plug in remove_plugins if hasattr (plug , name )]
585588 if plugins_to_remove :
586- hc = _HookCaller (orig .name , orig ._hookexec , orig ._specmodule_or_class ,
587- orig .spec_opts )
589+ hc = _HookCaller (orig .name , orig ._hookexec , orig .spec . namespace ,
590+ orig .spec . opts )
588591 for hookimpl in (orig ._wrappers + orig ._nonwrappers ):
589592 plugin = hookimpl .plugin
590593 if plugin not in plugins_to_remove :
@@ -605,29 +608,43 @@ class _MultiCall:
605608 # so we can remove it soon, allowing to avoid the below recursion
606609 # in execute() and simplify/speed up the execute loop.
607610
608- def __init__ (self , hook_impls , kwargs , specopts = {}, hook = None ):
609- self .hook = hook
611+ def __init__ (self , hook_impls , kwargs , firstresult = False , hook = None ):
610612 self .hook_impls = hook_impls
611613 self .caller_kwargs = kwargs # come from _HookCaller.__call__()
612614 self .caller_kwargs ["__multicall__" ] = self
613- self .specopts = hook .spec_opts if hook else specopts
615+ self .firstresult = firstresult
616+ self .hook = hook
617+ self .spec = hook .spec if hook else None
614618
615619 def execute (self ):
616620 caller_kwargs = self .caller_kwargs
617621 self .results = results = []
618- firstresult = self .specopts .get ("firstresult" )
622+ firstresult = self .firstresult
623+ spec = self .spec
619624
620625 while self .hook_impls :
621626 hook_impl = self .hook_impls .pop ()
627+ implkwargs = hook_impl .kwargs
622628 try :
623629 args = [caller_kwargs [argname ] for argname in hook_impl .argnames ]
630+ # get any caller provided kwargs declared in our
631+ # hookimpl and fail over to the spec's value if provided
632+ if implkwargs :
633+ kwargs = copy .copy (implkwargs )
634+ if spec :
635+ kwargs .update (spec .kwargs )
636+
637+ args += [caller_kwargs .get (argname , kwargs [argname ])
638+ for argname in hook_impl .kwargnames ]
624639 except KeyError :
625640 for argname in hook_impl .argnames :
626641 if argname not in caller_kwargs :
627642 raise HookCallError (
628643 "hook call must provide argument %r" % (argname ,))
644+
629645 if hook_impl .hookwrapper :
630646 return _wrapped_call (hook_impl .function (* args ), self .execute )
647+
631648 res = hook_impl .function (* args )
632649 if res is not None :
633650 if firstresult :
@@ -711,28 +728,23 @@ def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None)
711728 self ._wrappers = []
712729 self ._nonwrappers = []
713730 self ._hookexec = hook_execute
714- self .argnames = None
715- self .kwargnames = None
731+ self .spec = None
732+ self ._call_history = None
716733 if specmodule_or_class is not None :
717734 assert spec_opts is not None
718735 self .set_specification (specmodule_or_class , spec_opts )
719736
720737 def has_spec (self ):
721- return hasattr ( self , "_specmodule_or_class" )
738+ return self . spec is not None
722739
723740 def set_specification (self , specmodule_or_class , spec_opts ):
724741 assert not self .has_spec ()
725- self ._specmodule_or_class = specmodule_or_class
726- specfunc = getattr (specmodule_or_class , self .name )
727- # get spec arg signature
728- argnames , self .kwargnames = varnames (specfunc )
729- self .argnames = ["__multicall__" ] + list (argnames )
730- self .spec_opts = spec_opts
742+ self .spec = HookSpec (specmodule_or_class , self .name , spec_opts )
731743 if spec_opts .get ("historic" ):
732744 self ._call_history = []
733745
734746 def is_historic (self ):
735- return hasattr ( self , " _call_history" )
747+ return self . _call_history is not None
736748
737749 def _remove_plugin (self , plugin ):
738750 def remove (wrappers ):
@@ -768,13 +780,14 @@ def __repr__(self):
768780
769781 def __call__ (self , ** kwargs ):
770782 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- )
783+ if self .spec :
784+ notincall = set (self .spec .argnames ) - set (kwargs .keys ())
785+ if notincall :
786+ warnings .warn (
787+ "Positional arg(s) %s are declared in the hookspec "
788+ "but can not be found in this hook call" % notincall ,
789+ FutureWarning
790+ )
778791 return self ._hookexec (self , self ._nonwrappers + self ._wrappers , kwargs )
779792
780793 def call_historic (self , proc = None , kwargs = None ):
@@ -813,10 +826,30 @@ def _maybe_apply_history(self, method):
813826 proc (res [0 ])
814827
815828
829+ class HookSpec :
830+ def __init__ (self , namespace , name , hook_spec_opts ):
831+ self .namespace = namespace
832+ self .function = function = getattr (namespace , name )
833+ self .name = name
834+ self .argnames , self .kwargnames = varnames (function )
835+ self .kwargvalues = inspect .getargspec (function ).defaults
836+ self .kwargs = dict (
837+ ((name , value ) for name , value in
838+ zip (self .kwargnames , inspect .getargspec (function ).defaults ))
839+ ) if self .kwargvalues else {}
840+ self .opts = hook_spec_opts
841+ self .argnames = ["__multicall__" ] + list (self .argnames )
842+
843+
816844class HookImpl :
817845 def __init__ (self , plugin , plugin_name , function , hook_impl_opts ):
818846 self .function = function
819847 self .argnames , self .kwargnames = varnames (self .function )
848+ self .kwargvalues = inspect .getargspec (function ).defaults
849+ self .kwargs = dict (
850+ ((name , value ) for name , value in
851+ zip (self .kwargnames , inspect .getargspec (function ).defaults ))
852+ ) if self .kwargvalues else {}
820853 self .plugin = plugin
821854 self .opts = hook_impl_opts
822855 self .plugin_name = plugin_name
0 commit comments