Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cythonize call loop #104

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ matrix:
env: TOXENV=benchmark

install:
- pip install -U setuptools pip
- pip install -U tox
- pip install -U setuptools pip tox cython

script:
- tox
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ include tox.ini
include LICENSE
graft testing
recursive-exclude * *.pyc *.pyo
include pluggy/callers/cythonized.pyx
include pluggy/callers/cythonized.c
201 changes: 0 additions & 201 deletions pluggy/callers.py

This file was deleted.

11 changes: 11 additions & 0 deletions pluggy/callers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'''
Call loop machinery
'''
from .utils import HookCallError, _raise_wrapfail, _Result
from .legacy import _legacymulticall, _LegacyMultiCall
from .cythonized import _multicall

__all__ = [
'_multicall', '_legacymulticall', '_LegacyMultiCall',
'HookCallError', '_Result',
]
60 changes: 60 additions & 0 deletions pluggy/callers/cythonized.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
Cynthonized hook call loop.

NOTE: In order to build this source you must have cython installed.
"""
import sys
from . import _Result, _raise_wrapfail, HookCallError


cpdef _multicall(list hook_impls, dict caller_kwargs, bint firstresult=False):
"""Execute a call into multiple python functions/methods and return the
result(s).

``caller_kwargs`` comes from _HookCaller.__call__().
"""
__tracebackhide__ = True
results = []
excinfo = None
try: # run impl and wrapper setup functions in a loop
teardowns = []
try:
for hook_impl in reversed(hook_impls):
try:
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
except KeyError:
for argname in hook_impl.argnames:
if argname not in caller_kwargs:
raise HookCallError(
"hook call must provide argument %r" % (argname,))

if hook_impl.hookwrapper:
try:
gen = hook_impl.function(*args)
next(gen) # first yield
teardowns.append(gen)
except StopIteration:
_raise_wrapfail(gen, "did not yield")
else:
res = hook_impl.function(*args)
if res is not None:
results.append(res)
if firstresult: # halt further impl calls
break
except BaseException:
excinfo = sys.exc_info()
finally:
if firstresult: # first result hooks return a single value
outcome = _Result(results[0] if results else None, excinfo)
else:
outcome = _Result(results, excinfo)

# run all wrapper post-yield blocks
for gen in reversed(teardowns):
try:
gen.send(outcome)
_raise_wrapfail(gen, "has second yield")
except StopIteration:
pass

return outcome.get_result()
75 changes: 75 additions & 0 deletions pluggy/callers/legacy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
Legacy hook caller
"""
from .utils import HookCallError, _raise_wrapfail, _Result


def _wrapped_call(wrap_controller, func):
""" Wrap calling to a function with a generator which needs to yield
exactly once. The yield point will trigger calling the wrapped function
and return its ``_Result`` to the yield point. The generator then needs
to finish (raise StopIteration) in order for the wrapped call to complete.
"""
try:
next(wrap_controller) # first yield
except StopIteration:
_raise_wrapfail(wrap_controller, "did not yield")
call_outcome = _Result.from_call(func)
try:
wrap_controller.send(call_outcome)
_raise_wrapfail(wrap_controller, "has second yield")
except StopIteration:
pass
return call_outcome.get_result()


class _LegacyMultiCall(object):
""" execute a call into multiple python functions/methods. """

# XXX note that the __multicall__ argument is supported only
# for pytest compatibility reasons. It was never officially
# supported there and is explicitely deprecated since 2.8
# so we can remove it soon, allowing to avoid the below recursion
# in execute() and simplify/speed up the execute loop.

def __init__(self, hook_impls, kwargs, firstresult=False):
self.hook_impls = hook_impls
self.caller_kwargs = kwargs # come from _HookCaller.__call__()
self.caller_kwargs["__multicall__"] = self
self.firstresult = firstresult

def execute(self):
caller_kwargs = self.caller_kwargs
self.results = results = []
firstresult = self.firstresult

while self.hook_impls:
hook_impl = self.hook_impls.pop()
try:
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
except KeyError:
for argname in hook_impl.argnames:
if argname not in caller_kwargs:
raise HookCallError(
"hook call must provide argument %r" % (argname,))
if hook_impl.hookwrapper:
return _wrapped_call(hook_impl.function(*args), self.execute)
res = hook_impl.function(*args)
if res is not None:
if firstresult:
return res
results.append(res)

if not firstresult:
return results

def __repr__(self):
status = "%d meths" % (len(self.hook_impls),)
if hasattr(self, "results"):
status = ("%d results, " % len(self.results)) + status
return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs)


def _legacymulticall(hook_impls, caller_kwargs, firstresult=False):
return _LegacyMultiCall(
hook_impls, caller_kwargs, firstresult=firstresult).execute()
Loading