Skip to content

Commit 32ee0e0

Browse files
author
goodboy
authored
Merge pull request #117 from tgoodlet/split_into_modules
Split into modules
2 parents 50d6626 + 2100bbf commit 32ee0e0

File tree

11 files changed

+716
-704
lines changed

11 files changed

+716
-704
lines changed

pluggy/__init__.py

Lines changed: 4 additions & 679 deletions
Large diffs are not rendered by default.

pluggy/_tracing.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""
2+
Tracing utils
3+
"""
4+
from .callers import _Result
5+
6+
7+
class TagTracer(object):
8+
def __init__(self):
9+
self._tag2proc = {}
10+
self.writer = None
11+
self.indent = 0
12+
13+
def get(self, name):
14+
return TagTracerSub(self, (name,))
15+
16+
def format_message(self, tags, args):
17+
if isinstance(args[-1], dict):
18+
extra = args[-1]
19+
args = args[:-1]
20+
else:
21+
extra = {}
22+
23+
content = " ".join(map(str, args))
24+
indent = " " * self.indent
25+
26+
lines = [
27+
"%s%s [%s]\n" % (indent, content, ":".join(tags))
28+
]
29+
30+
for name, value in extra.items():
31+
lines.append("%s %s: %s\n" % (indent, name, value))
32+
return lines
33+
34+
def processmessage(self, tags, args):
35+
if self.writer is not None and args:
36+
lines = self.format_message(tags, args)
37+
self.writer(''.join(lines))
38+
try:
39+
self._tag2proc[tags](tags, args)
40+
except KeyError:
41+
pass
42+
43+
def setwriter(self, writer):
44+
self.writer = writer
45+
46+
def setprocessor(self, tags, processor):
47+
if isinstance(tags, str):
48+
tags = tuple(tags.split(":"))
49+
else:
50+
assert isinstance(tags, tuple)
51+
self._tag2proc[tags] = processor
52+
53+
54+
class TagTracerSub(object):
55+
def __init__(self, root, tags):
56+
self.root = root
57+
self.tags = tags
58+
59+
def __call__(self, *args):
60+
self.root.processmessage(self.tags, args)
61+
62+
def setmyprocessor(self, processor):
63+
self.root.setprocessor(self.tags, processor)
64+
65+
def get(self, name):
66+
return self.__class__(self.root, self.tags + (name,))
67+
68+
69+
class _TracedHookExecution(object):
70+
def __init__(self, pluginmanager, before, after):
71+
self.pluginmanager = pluginmanager
72+
self.before = before
73+
self.after = after
74+
self.oldcall = pluginmanager._inner_hookexec
75+
assert not isinstance(self.oldcall, _TracedHookExecution)
76+
self.pluginmanager._inner_hookexec = self
77+
78+
def __call__(self, hook, hook_impls, kwargs):
79+
self.before(hook.name, hook_impls, kwargs)
80+
outcome = _Result.from_call(lambda: self.oldcall(hook, hook_impls, kwargs))
81+
self.after(outcome, hook.name, hook_impls, kwargs)
82+
return outcome.get_result()
83+
84+
def undo(self):
85+
self.pluginmanager._inner_hookexec = self.oldcall

pluggy/hooks.py

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
"""
2+
Internal hook annotation, representation and calling machinery.
3+
"""
4+
import inspect
5+
import warnings
6+
from .callers import _legacymulticall, _multicall
7+
8+
9+
class HookspecMarker(object):
10+
""" Decorator helper class for marking functions as hook specifications.
11+
12+
You can instantiate it with a project_name to get a decorator.
13+
Calling PluginManager.add_hookspecs later will discover all marked functions
14+
if the PluginManager uses the same project_name.
15+
"""
16+
17+
def __init__(self, project_name):
18+
self.project_name = project_name
19+
20+
def __call__(self, function=None, firstresult=False, historic=False):
21+
""" if passed a function, directly sets attributes on the function
22+
which will make it discoverable to add_hookspecs(). If passed no
23+
function, returns a decorator which can be applied to a function
24+
later using the attributes supplied.
25+
26+
If firstresult is True the 1:N hook call (N being the number of registered
27+
hook implementation functions) will stop at I<=N when the I'th function
28+
returns a non-None result.
29+
30+
If historic is True calls to a hook will be memorized and replayed
31+
on later registered plugins.
32+
33+
"""
34+
def setattr_hookspec_opts(func):
35+
if historic and firstresult:
36+
raise ValueError("cannot have a historic firstresult hook")
37+
setattr(func, self.project_name + "_spec",
38+
dict(firstresult=firstresult, historic=historic))
39+
return func
40+
41+
if function is not None:
42+
return setattr_hookspec_opts(function)
43+
else:
44+
return setattr_hookspec_opts
45+
46+
47+
class HookimplMarker(object):
48+
""" Decorator helper class for marking functions as hook implementations.
49+
50+
You can instantiate with a project_name to get a decorator.
51+
Calling PluginManager.register later will discover all marked functions
52+
if the PluginManager uses the same project_name.
53+
"""
54+
def __init__(self, project_name):
55+
self.project_name = project_name
56+
57+
def __call__(self, function=None, hookwrapper=False, optionalhook=False,
58+
tryfirst=False, trylast=False):
59+
60+
""" if passed a function, directly sets attributes on the function
61+
which will make it discoverable to register(). If passed no function,
62+
returns a decorator which can be applied to a function later using
63+
the attributes supplied.
64+
65+
If optionalhook is True a missing matching hook specification will not result
66+
in an error (by default it is an error if no matching spec is found).
67+
68+
If tryfirst is True this hook implementation will run as early as possible
69+
in the chain of N hook implementations for a specfication.
70+
71+
If trylast is True this hook implementation will run as late as possible
72+
in the chain of N hook implementations.
73+
74+
If hookwrapper is True the hook implementations needs to execute exactly
75+
one "yield". The code before the yield is run early before any non-hookwrapper
76+
function is run. The code after the yield is run after all non-hookwrapper
77+
function have run. The yield receives a ``_Result`` object representing
78+
the exception or result outcome of the inner calls (including other hookwrapper
79+
calls).
80+
81+
"""
82+
def setattr_hookimpl_opts(func):
83+
setattr(func, self.project_name + "_impl",
84+
dict(hookwrapper=hookwrapper, optionalhook=optionalhook,
85+
tryfirst=tryfirst, trylast=trylast))
86+
return func
87+
88+
if function is None:
89+
return setattr_hookimpl_opts
90+
else:
91+
return setattr_hookimpl_opts(function)
92+
93+
94+
def normalize_hookimpl_opts(opts):
95+
opts.setdefault("tryfirst", False)
96+
opts.setdefault("trylast", False)
97+
opts.setdefault("hookwrapper", False)
98+
opts.setdefault("optionalhook", False)
99+
100+
101+
if hasattr(inspect, 'getfullargspec'):
102+
def _getargspec(func):
103+
return inspect.getfullargspec(func)
104+
else:
105+
def _getargspec(func):
106+
return inspect.getargspec(func)
107+
108+
109+
def varnames(func):
110+
"""Return tuple of positional and keywrord argument names for a function,
111+
method, class or callable.
112+
113+
In case of a class, its ``__init__`` method is considered.
114+
For methods the ``self`` parameter is not included.
115+
"""
116+
cache = getattr(func, "__dict__", {})
117+
try:
118+
return cache["_varnames"]
119+
except KeyError:
120+
pass
121+
122+
if inspect.isclass(func):
123+
try:
124+
func = func.__init__
125+
except AttributeError:
126+
return (), ()
127+
elif not inspect.isroutine(func): # callable object?
128+
try:
129+
func = getattr(func, '__call__', func)
130+
except Exception:
131+
return ()
132+
133+
try: # func MUST be a function or method here or we won't parse any args
134+
spec = _getargspec(func)
135+
except TypeError:
136+
return (), ()
137+
138+
args, defaults = tuple(spec.args), spec.defaults
139+
if defaults:
140+
index = -len(defaults)
141+
args, defaults = args[:index], tuple(args[index:])
142+
else:
143+
defaults = ()
144+
145+
# strip any implicit instance arg
146+
if args:
147+
if inspect.ismethod(func) or (
148+
'.' in getattr(func, '__qualname__', ()) and args[0] == 'self'
149+
):
150+
args = args[1:]
151+
152+
assert "self" not in args # best naming practises check?
153+
try:
154+
cache["_varnames"] = args, defaults
155+
except TypeError:
156+
pass
157+
return args, defaults
158+
159+
160+
class _HookRelay(object):
161+
""" hook holder object for performing 1:N hook calls where N is the number
162+
of registered plugins.
163+
164+
"""
165+
166+
def __init__(self, trace):
167+
self._trace = trace
168+
169+
170+
class _HookCaller(object):
171+
def __init__(self, name, hook_execute, specmodule_or_class=None,
172+
spec_opts=None):
173+
self.name = name
174+
self._wrappers = []
175+
self._nonwrappers = []
176+
self._hookexec = hook_execute
177+
self._specmodule_or_class = None
178+
self.argnames = None
179+
self.kwargnames = None
180+
self.multicall = _multicall
181+
self.spec_opts = spec_opts or {}
182+
if specmodule_or_class is not None:
183+
self.set_specification(specmodule_or_class, spec_opts)
184+
185+
def has_spec(self):
186+
return self._specmodule_or_class is not None
187+
188+
def set_specification(self, specmodule_or_class, spec_opts):
189+
assert not self.has_spec()
190+
self._specmodule_or_class = specmodule_or_class
191+
specfunc = getattr(specmodule_or_class, self.name)
192+
# get spec arg signature
193+
argnames, self.kwargnames = varnames(specfunc)
194+
self.argnames = ["__multicall__"] + list(argnames)
195+
self.spec_opts.update(spec_opts)
196+
if spec_opts.get("historic"):
197+
self._call_history = []
198+
199+
def is_historic(self):
200+
return hasattr(self, "_call_history")
201+
202+
def _remove_plugin(self, plugin):
203+
def remove(wrappers):
204+
for i, method in enumerate(wrappers):
205+
if method.plugin == plugin:
206+
del wrappers[i]
207+
return True
208+
if remove(self._wrappers) is None:
209+
if remove(self._nonwrappers) is None:
210+
raise ValueError("plugin %r not found" % (plugin,))
211+
212+
def _add_hookimpl(self, hookimpl):
213+
"""A an implementation to the callback chain.
214+
"""
215+
if hookimpl.hookwrapper:
216+
methods = self._wrappers
217+
else:
218+
methods = self._nonwrappers
219+
220+
if hookimpl.trylast:
221+
methods.insert(0, hookimpl)
222+
elif hookimpl.tryfirst:
223+
methods.append(hookimpl)
224+
else:
225+
# find last non-tryfirst method
226+
i = len(methods) - 1
227+
while i >= 0 and methods[i].tryfirst:
228+
i -= 1
229+
methods.insert(i + 1, hookimpl)
230+
231+
if '__multicall__' in hookimpl.argnames:
232+
warnings.warn(
233+
"Support for __multicall__ is now deprecated and will be"
234+
"removed in an upcoming release.",
235+
DeprecationWarning
236+
)
237+
self.multicall = _legacymulticall
238+
239+
def __repr__(self):
240+
return "<_HookCaller %r>" % (self.name,)
241+
242+
def __call__(self, *args, **kwargs):
243+
if args:
244+
raise TypeError("hook calling supports only keyword arguments")
245+
assert not self.is_historic()
246+
if self.argnames:
247+
notincall = set(self.argnames) - set(['__multicall__']) - set(
248+
kwargs.keys())
249+
if notincall:
250+
warnings.warn(
251+
"Argument(s) {} which are declared in the hookspec "
252+
"can not be found in this hook call"
253+
.format(tuple(notincall)),
254+
stacklevel=2,
255+
)
256+
return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
257+
258+
def call_historic(self, proc=None, kwargs=None):
259+
""" call the hook with given ``kwargs`` for all registered plugins and
260+
for all plugins which will be registered afterwards.
261+
262+
If ``proc`` is not None it will be called for for each non-None result
263+
obtained from a hook implementation.
264+
"""
265+
self._call_history.append((kwargs or {}, proc))
266+
# historizing hooks don't return results
267+
res = self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
268+
for x in res or []:
269+
proc(x)
270+
271+
def call_extra(self, methods, kwargs):
272+
""" Call the hook with some additional temporarily participating
273+
methods using the specified kwargs as call parameters. """
274+
old = list(self._nonwrappers), list(self._wrappers)
275+
for method in methods:
276+
opts = dict(hookwrapper=False, trylast=False, tryfirst=False)
277+
hookimpl = HookImpl(None, "<temp>", method, opts)
278+
self._add_hookimpl(hookimpl)
279+
try:
280+
return self(**kwargs)
281+
finally:
282+
self._nonwrappers, self._wrappers = old
283+
284+
def _maybe_apply_history(self, method):
285+
"""Apply call history to a new hookimpl if it is marked as historic.
286+
"""
287+
if self.is_historic():
288+
for kwargs, proc in self._call_history:
289+
res = self._hookexec(self, [method], kwargs)
290+
if res and proc is not None:
291+
proc(res[0])
292+
293+
294+
class HookImpl(object):
295+
def __init__(self, plugin, plugin_name, function, hook_impl_opts):
296+
self.function = function
297+
self.argnames, self.kwargnames = varnames(self.function)
298+
self.plugin = plugin
299+
self.opts = hook_impl_opts
300+
self.plugin_name = plugin_name
301+
self.__dict__.update(hook_impl_opts)

0 commit comments

Comments
 (0)