@@ -77,67 +77,128 @@ def get_result(self):
7777 _reraise (* ex ) # noqa
7878
7979
80- class _MultiCall (object ):
81- """Execute a call into multiple python functions/methods.
80+ def _wrapped_call (wrap_controller , func ):
81+ """ Wrap calling to a function with a generator which needs to yield
82+ exactly once. The yield point will trigger calling the wrapped function
83+ and return its ``_Result`` to the yield point. The generator then needs
84+ to finish (raise StopIteration) in order for the wrapped call to complete.
8285 """
86+ try :
87+ next (wrap_controller ) # first yield
88+ except StopIteration :
89+ _raise_wrapfail (wrap_controller , "did not yield" )
90+ call_outcome = _Result .from_call (func )
91+ try :
92+ wrap_controller .send (call_outcome )
93+ _raise_wrapfail (wrap_controller , "has second yield" )
94+ except StopIteration :
95+ pass
96+ return call_outcome .get_result ()
97+
98+
99+ class _LegacyMultiCall (object ):
100+ """ execute a call into multiple python functions/methods. """
101+
102+ # XXX note that the __multicall__ argument is supported only
103+ # for pytest compatibility reasons. It was never officially
104+ # supported there and is explicitely deprecated since 2.8
105+ # so we can remove it soon, allowing to avoid the below recursion
106+ # in execute() and simplify/speed up the execute loop.
107+
83108 def __init__ (self , hook_impls , kwargs , specopts = {}, hook = None ):
84109 self .hook = hook
85110 self .hook_impls = hook_impls
86111 self .caller_kwargs = kwargs # come from _HookCaller.__call__()
112+ self .caller_kwargs ["__multicall__" ] = self
87113 self .specopts = hook .spec_opts if hook else specopts
88114
89115 def execute (self ):
90- __tracebackhide__ = True
91116 caller_kwargs = self .caller_kwargs
92117 self .results = results = []
93118 firstresult = self .specopts .get ("firstresult" )
94- excinfo = None
95- try : # run impl and wrapper setup functions in a loop
96- teardowns = []
97- try :
98- for hook_impl in reversed (self .hook_impls ):
99- try :
100- args = [caller_kwargs [argname ] for argname in hook_impl .argnames ]
101- # args = operator.itemgetter(hookimpl.argnames)(caller_kwargs)
102- except KeyError :
103- for argname in hook_impl .argnames :
104- if argname not in caller_kwargs :
105- raise HookCallError (
106- "hook call must provide argument %r" % (argname ,))
107-
108- if hook_impl .hookwrapper :
109- try :
110- gen = hook_impl .function (* args )
111- next (gen ) # first yield
112- teardowns .append (gen )
113- except StopIteration :
114- _raise_wrapfail (gen , "did not yield" )
115- else :
116- res = hook_impl .function (* args )
117- if res is not None :
118- results .append (res )
119- if firstresult : # halt further impl calls
120- break
121- except BaseException :
122- excinfo = sys .exc_info ()
123- finally :
124- if firstresult : # first result hooks return a single value
125- outcome = _Result (results [0 ] if results else None , excinfo )
126- else :
127- outcome = _Result (results , excinfo )
128-
129- # run all wrapper post-yield blocks
130- for gen in reversed (teardowns ):
131- try :
132- gen .send (outcome )
133- _raise_wrapfail (gen , "has second yield" )
134- except StopIteration :
135- pass
136119
137- return outcome .get_result ()
120+ while self .hook_impls :
121+ hook_impl = self .hook_impls .pop ()
122+ try :
123+ args = [caller_kwargs [argname ] for argname in hook_impl .argnames ]
124+ except KeyError :
125+ for argname in hook_impl .argnames :
126+ if argname not in caller_kwargs :
127+ raise HookCallError (
128+ "hook call must provide argument %r" % (argname ,))
129+ if hook_impl .hookwrapper :
130+ return _wrapped_call (hook_impl .function (* args ), self .execute )
131+ res = hook_impl .function (* args )
132+ if res is not None :
133+ if firstresult :
134+ return res
135+ results .append (res )
136+
137+ if not firstresult :
138+ return results
138139
139140 def __repr__ (self ):
140141 status = "%d meths" % (len (self .hook_impls ),)
141142 if hasattr (self , "results" ):
142143 status = ("%d results, " % len (self .results )) + status
143144 return "<_MultiCall %s, kwargs=%r>" % (status , self .caller_kwargs )
145+
146+
147+ def _legacymulticall (hook_impls , caller_kwargs , specopts = {}, hook = None ):
148+ return _LegacyMultiCall (
149+ hook_impls , caller_kwargs , specopts = specopts , hook = hook ).execute ()
150+
151+
152+ def _multicall (hook_impls , caller_kwargs , specopts = {}, hook = None ):
153+ """Execute a call into multiple python functions/methods and return the
154+ result(s).
155+
156+ ``caller_kwargs`` comes from _HookCaller.__call__().
157+ """
158+ __tracebackhide__ = True
159+ specopts = hook .spec_opts if hook else specopts
160+ results = []
161+ firstresult = specopts .get ("firstresult" )
162+ excinfo = None
163+ try : # run impl and wrapper setup functions in a loop
164+ teardowns = []
165+ try :
166+ for hook_impl in reversed (hook_impls ):
167+ try :
168+ args = [caller_kwargs [argname ] for argname in hook_impl .argnames ]
169+ except KeyError :
170+ for argname in hook_impl .argnames :
171+ if argname not in caller_kwargs :
172+ raise HookCallError (
173+ "hook call must provide argument %r" % (argname ,))
174+
175+ if hook_impl .hookwrapper :
176+ try :
177+ gen = hook_impl .function (* args )
178+ next (gen ) # first yield
179+ teardowns .append (gen )
180+ except StopIteration :
181+ _raise_wrapfail (gen , "did not yield" )
182+ else :
183+ res = hook_impl .function (* args )
184+ if res is not None :
185+ results .append (res )
186+ if firstresult : # halt further impl calls
187+ break
188+ except BaseException :
189+ excinfo = sys .exc_info ()
190+ finally :
191+ if firstresult : # first result hooks return a single value
192+ outcome = _Result (results [0 ] if results else None , excinfo )
193+ else :
194+ outcome = _Result (results , excinfo )
195+
196+ # run all wrapper post-yield blocks
197+ for gen in reversed (teardowns ):
198+ try :
199+ gen .send (outcome )
200+ _raise_wrapfail (gen , "has second yield" )
201+ except StopIteration :
202+ pass
203+
204+ return outcome .get_result ()
0 commit comments