incrementally update hook call lists instead of regenerating the whole
list on each registered plugin --HG-- branch : more_plugin
This commit is contained in:
parent
b03c1342ac
commit
02a4042dca
|
@ -180,9 +180,13 @@ class PluginManager(object):
|
||||||
|
|
||||||
def make_hook_caller(self, name, plugins):
|
def make_hook_caller(self, name, plugins):
|
||||||
caller = getattr(self.hook, name)
|
caller = getattr(self.hook, name)
|
||||||
methods = self.listattr(name, plugins=plugins)
|
hc = HookCaller(caller.name, plugins, firstresult=caller.firstresult,
|
||||||
return HookCaller(caller.name, [plugins], firstresult=caller.firstresult,
|
argnames=caller.argnames)
|
||||||
argnames=caller.argnames, methods=methods)
|
for plugin in hc.plugins:
|
||||||
|
meth = getattr(plugin, name, None)
|
||||||
|
if meth is not None:
|
||||||
|
hc._add_method(meth)
|
||||||
|
return hc
|
||||||
|
|
||||||
def register(self, plugin, name=None):
|
def register(self, plugin, name=None):
|
||||||
""" Register a plugin with the given name and ensure that all its
|
""" Register a plugin with the given name and ensure that all its
|
||||||
|
@ -216,7 +220,7 @@ class PluginManager(object):
|
||||||
hookcallers = self._plugin2hookcallers.pop(plugin)
|
hookcallers = self._plugin2hookcallers.pop(plugin)
|
||||||
for hookcaller in hookcallers:
|
for hookcaller in hookcallers:
|
||||||
hookcaller.plugins.remove(plugin)
|
hookcaller.plugins.remove(plugin)
|
||||||
self._scan_methods(hookcaller)
|
hookcaller._scan_methods()
|
||||||
|
|
||||||
def addhooks(self, module_or_class):
|
def addhooks(self, module_or_class):
|
||||||
""" add new hook definitions from the given module_or_class using
|
""" add new hook definitions from the given module_or_class using
|
||||||
|
@ -231,14 +235,14 @@ class PluginManager(object):
|
||||||
argnames = varnames(specfunc, startindex=isclass)
|
argnames = varnames(specfunc, startindex=isclass)
|
||||||
if hc is None:
|
if hc is None:
|
||||||
hc = HookCaller(name, [], firstresult=firstresult,
|
hc = HookCaller(name, [], firstresult=firstresult,
|
||||||
argnames=argnames, methods=[])
|
argnames=argnames)
|
||||||
setattr(self.hook, name, hc)
|
setattr(self.hook, name, hc)
|
||||||
else:
|
else:
|
||||||
# plugins registered this hook without knowing the spec
|
# plugins registered this hook without knowing the spec
|
||||||
hc.setspec(firstresult=firstresult, argnames=argnames)
|
hc.setspec(firstresult=firstresult, argnames=argnames)
|
||||||
self._scan_methods(hc)
|
|
||||||
for plugin in hc.plugins:
|
for plugin in hc.plugins:
|
||||||
self._verify_hook(hc, specfunc, plugin)
|
self._verify_hook(hc, specfunc, plugin)
|
||||||
|
hc._add_method(getattr(plugin, name))
|
||||||
names.append(name)
|
names.append(name)
|
||||||
if not names:
|
if not names:
|
||||||
raise ValueError("did not find new %r hooks in %r"
|
raise ValueError("did not find new %r hooks in %r"
|
||||||
|
@ -264,35 +268,10 @@ class PluginManager(object):
|
||||||
""" Return a plugin or None for the given name. """
|
""" Return a plugin or None for the given name. """
|
||||||
return self._name2plugin.get(name)
|
return self._name2plugin.get(name)
|
||||||
|
|
||||||
def listattr(self, attrname, plugins=None):
|
|
||||||
if plugins is None:
|
|
||||||
plugins = self._plugins
|
|
||||||
l = []
|
|
||||||
last = []
|
|
||||||
wrappers = []
|
|
||||||
for plugin in plugins:
|
|
||||||
try:
|
|
||||||
meth = getattr(plugin, attrname)
|
|
||||||
except AttributeError:
|
|
||||||
continue
|
|
||||||
if hasattr(meth, 'hookwrapper'):
|
|
||||||
wrappers.append(meth)
|
|
||||||
elif hasattr(meth, 'tryfirst'):
|
|
||||||
last.append(meth)
|
|
||||||
elif hasattr(meth, 'trylast'):
|
|
||||||
l.insert(0, meth)
|
|
||||||
else:
|
|
||||||
l.append(meth)
|
|
||||||
l.extend(last)
|
|
||||||
l.extend(wrappers)
|
|
||||||
return l
|
|
||||||
|
|
||||||
def _scan_methods(self, hookcaller):
|
|
||||||
hookcaller.methods = self.listattr(hookcaller.name, hookcaller.plugins)
|
|
||||||
|
|
||||||
def call_plugin(self, plugin, methname, kwargs):
|
def call_plugin(self, plugin, methname, kwargs):
|
||||||
return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
|
meth = getattr(plugin, methname, None)
|
||||||
kwargs=kwargs, firstresult=True).execute()
|
if meth is not None:
|
||||||
|
return MultiCall(methods=[meth], kwargs=kwargs, firstresult=True).execute()
|
||||||
|
|
||||||
def _scan_plugin(self, plugin):
|
def _scan_plugin(self, plugin):
|
||||||
hookcallers = []
|
hookcallers = []
|
||||||
|
@ -313,7 +292,7 @@ class PluginManager(object):
|
||||||
# we have a hook spec, can verify early
|
# we have a hook spec, can verify early
|
||||||
self._verify_hook(hook, method, plugin)
|
self._verify_hook(hook, method, plugin)
|
||||||
hook.plugins.append(plugin)
|
hook.plugins.append(plugin)
|
||||||
self._scan_methods(hook)
|
hook._add_method(method)
|
||||||
hookcallers.append(hook)
|
hookcallers.append(hook)
|
||||||
return hookcallers
|
return hookcallers
|
||||||
|
|
||||||
|
@ -348,7 +327,7 @@ class MultiCall:
|
||||||
""" execute a call into multiple python functions/methods. """
|
""" execute a call into multiple python functions/methods. """
|
||||||
|
|
||||||
def __init__(self, methods, kwargs, firstresult=False):
|
def __init__(self, methods, kwargs, firstresult=False):
|
||||||
self.methods = list(methods)
|
self.methods = methods
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
self.kwargs["__multicall__"] = self
|
self.kwargs["__multicall__"] = self
|
||||||
self.results = []
|
self.results = []
|
||||||
|
@ -421,14 +400,15 @@ class HookRelay:
|
||||||
|
|
||||||
|
|
||||||
class HookCaller:
|
class HookCaller:
|
||||||
def __init__(self, name, plugins, argnames=None, firstresult=None, methods=None):
|
def __init__(self, name, plugins, argnames=None, firstresult=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.plugins = plugins
|
self.plugins = plugins
|
||||||
self.methods = methods
|
|
||||||
if argnames is not None:
|
if argnames is not None:
|
||||||
argnames = ["__multicall__"] + list(argnames)
|
argnames = ["__multicall__"] + list(argnames)
|
||||||
self.argnames = argnames
|
self.argnames = argnames
|
||||||
self.firstresult = firstresult
|
self.firstresult = firstresult
|
||||||
|
self.wrappers = []
|
||||||
|
self.nonwrappers = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pre(self):
|
def pre(self):
|
||||||
|
@ -440,14 +420,41 @@ class HookCaller:
|
||||||
self.argnames = ["__multicall__"] + list(argnames)
|
self.argnames = ["__multicall__"] + list(argnames)
|
||||||
self.firstresult = firstresult
|
self.firstresult = firstresult
|
||||||
|
|
||||||
|
def _scan_methods(self):
|
||||||
|
self.wrappers[:] = []
|
||||||
|
self.nonwrappers[:] = []
|
||||||
|
for plugin in self.plugins:
|
||||||
|
self._add_method(getattr(plugin, self.name))
|
||||||
|
|
||||||
|
def _add_method(self, meth):
|
||||||
|
assert not self.pre
|
||||||
|
if hasattr(meth, 'hookwrapper'):
|
||||||
|
self.wrappers.append(meth)
|
||||||
|
elif hasattr(meth, 'trylast'):
|
||||||
|
self.nonwrappers.insert(0, meth)
|
||||||
|
elif hasattr(meth, 'tryfirst'):
|
||||||
|
self.nonwrappers.append(meth)
|
||||||
|
else:
|
||||||
|
if not self.nonwrappers or not hasattr(self.nonwrappers[-1], "tryfirst"):
|
||||||
|
self.nonwrappers.append(meth)
|
||||||
|
else:
|
||||||
|
for i in reversed(range(len(self.nonwrappers)-1)):
|
||||||
|
if hasattr(self.nonwrappers[i], "tryfirst"):
|
||||||
|
continue
|
||||||
|
self.nonwrappers.insert(i+1, meth)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.nonwrappers.insert(0, meth)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<HookCaller %r>" %(self.name,)
|
return "<HookCaller %r>" %(self.name,)
|
||||||
|
|
||||||
def __call__(self, **kwargs):
|
def __call__(self, **kwargs):
|
||||||
return self._docall(self.methods, kwargs)
|
return self._docall(self.nonwrappers + self.wrappers, kwargs)
|
||||||
|
|
||||||
def callextra(self, methods, **kwargs):
|
def callextra(self, methods, **kwargs):
|
||||||
return self._docall(self.methods + methods, kwargs)
|
return self._docall(self.nonwrappers + methods + self.wrappers,
|
||||||
|
kwargs)
|
||||||
|
|
||||||
def _docall(self, methods, kwargs):
|
def _docall(self, methods, kwargs):
|
||||||
assert not self.pre, self.name
|
assert not self.pre, self.name
|
||||||
|
|
|
@ -355,7 +355,8 @@ def test_load_initial_conftest_last_ordering(testdir):
|
||||||
pass
|
pass
|
||||||
m = My()
|
m = My()
|
||||||
pm.register(m)
|
pm.register(m)
|
||||||
l = pm.listattr("pytest_load_initial_conftests")
|
hc = pm.hook.pytest_load_initial_conftests
|
||||||
|
l = hc.nonwrappers + hc.wrappers
|
||||||
assert l[-1].__module__ == "_pytest.capture"
|
assert l[-1].__module__ == "_pytest.capture"
|
||||||
assert l[-2] == m.pytest_load_initial_conftests
|
assert l[-2] == m.pytest_load_initial_conftests
|
||||||
assert l[-3].__module__ == "_pytest.config"
|
assert l[-3].__module__ == "_pytest.config"
|
||||||
|
|
|
@ -64,20 +64,6 @@ class TestPluginManager:
|
||||||
assert not pm.isregistered(my)
|
assert not pm.isregistered(my)
|
||||||
assert pm.getplugins()[-1:] == [my2]
|
assert pm.getplugins()[-1:] == [my2]
|
||||||
|
|
||||||
def test_listattr(self):
|
|
||||||
plugins = PluginManager("xyz")
|
|
||||||
class api1:
|
|
||||||
x = 41
|
|
||||||
class api2:
|
|
||||||
x = 42
|
|
||||||
class api3:
|
|
||||||
x = 43
|
|
||||||
plugins.register(api1())
|
|
||||||
plugins.register(api2())
|
|
||||||
plugins.register(api3())
|
|
||||||
l = list(plugins.listattr('x'))
|
|
||||||
assert l == [41, 42, 43]
|
|
||||||
|
|
||||||
def test_register_unknown_hooks(self, pm):
|
def test_register_unknown_hooks(self, pm):
|
||||||
class Plugin1:
|
class Plugin1:
|
||||||
def he_method1(self, arg):
|
def he_method1(self, arg):
|
||||||
|
@ -91,6 +77,121 @@ class TestPluginManager:
|
||||||
#assert not pm._unverified_hooks
|
#assert not pm._unverified_hooks
|
||||||
assert pm.hook.he_method1(arg=1) == [2]
|
assert pm.hook.he_method1(arg=1) == [2]
|
||||||
|
|
||||||
|
class TestAddMethodOrdering:
|
||||||
|
@pytest.fixture
|
||||||
|
def hc(self, pm):
|
||||||
|
class Hooks:
|
||||||
|
def he_method1(self, arg):
|
||||||
|
pass
|
||||||
|
pm.addhooks(Hooks)
|
||||||
|
return pm.hook.he_method1
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def addmeth(self, hc):
|
||||||
|
def addmeth(tryfirst=False, trylast=False, hookwrapper=False):
|
||||||
|
def wrap(func):
|
||||||
|
if tryfirst:
|
||||||
|
func.tryfirst = True
|
||||||
|
if trylast:
|
||||||
|
func.trylast = True
|
||||||
|
if hookwrapper:
|
||||||
|
func.hookwrapper = True
|
||||||
|
hc._add_method(func)
|
||||||
|
return func
|
||||||
|
return wrap
|
||||||
|
return addmeth
|
||||||
|
|
||||||
|
def test_adding_nonwrappers(self, hc, addmeth):
|
||||||
|
@addmeth()
|
||||||
|
def he_method1():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth()
|
||||||
|
def he_method2():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth()
|
||||||
|
def he_method3():
|
||||||
|
pass
|
||||||
|
assert hc.nonwrappers == [he_method1, he_method2, he_method3]
|
||||||
|
|
||||||
|
def test_adding_nonwrappers_trylast(self, hc, addmeth):
|
||||||
|
@addmeth()
|
||||||
|
def he_method1_middle():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth(trylast=True)
|
||||||
|
def he_method1():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth()
|
||||||
|
def he_method1_b():
|
||||||
|
pass
|
||||||
|
assert hc.nonwrappers == [he_method1, he_method1_middle, he_method1_b]
|
||||||
|
|
||||||
|
def test_adding_nonwrappers_trylast2(self, hc, addmeth):
|
||||||
|
@addmeth()
|
||||||
|
def he_method1_middle():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth()
|
||||||
|
def he_method1_b():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth(trylast=True)
|
||||||
|
def he_method1():
|
||||||
|
pass
|
||||||
|
assert hc.nonwrappers == [he_method1, he_method1_middle, he_method1_b]
|
||||||
|
|
||||||
|
def test_adding_nonwrappers_tryfirst(self, hc, addmeth):
|
||||||
|
@addmeth(tryfirst=True)
|
||||||
|
def he_method1():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth()
|
||||||
|
def he_method1_middle():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth()
|
||||||
|
def he_method1_b():
|
||||||
|
pass
|
||||||
|
assert hc.nonwrappers == [he_method1_middle, he_method1_b, he_method1]
|
||||||
|
|
||||||
|
def test_adding_nonwrappers_trylast(self, hc, addmeth):
|
||||||
|
@addmeth()
|
||||||
|
def he_method1_a():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth(trylast=True)
|
||||||
|
def he_method1_b():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth()
|
||||||
|
def he_method1_c():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth(trylast=True)
|
||||||
|
def he_method1_d():
|
||||||
|
pass
|
||||||
|
assert hc.nonwrappers == [he_method1_d, he_method1_b, he_method1_a, he_method1_c]
|
||||||
|
|
||||||
|
def test_adding_wrappers_ordering(self, hc, addmeth):
|
||||||
|
@addmeth(hookwrapper=True)
|
||||||
|
def he_method1():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth()
|
||||||
|
def he_method1_middle():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@addmeth(hookwrapper=True)
|
||||||
|
def he_method3():
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert hc.nonwrappers == [he_method1_middle]
|
||||||
|
assert hc.wrappers == [he_method1, he_method3]
|
||||||
|
|
||||||
|
|
||||||
class TestPytestPluginInteractions:
|
class TestPytestPluginInteractions:
|
||||||
|
|
||||||
def test_addhooks_conftestplugin(self, testdir):
|
def test_addhooks_conftestplugin(self, testdir):
|
||||||
|
@ -201,43 +302,6 @@ class TestPytestPluginInteractions:
|
||||||
assert pytestpm.trace.root.indent == indent
|
assert pytestpm.trace.root.indent == indent
|
||||||
assert saveindent[0] > indent
|
assert saveindent[0] > indent
|
||||||
|
|
||||||
# lower level API
|
|
||||||
|
|
||||||
def test_listattr(self):
|
|
||||||
pluginmanager = PluginManager("xyz")
|
|
||||||
class My2:
|
|
||||||
x = 42
|
|
||||||
pluginmanager.register(My2())
|
|
||||||
assert not pluginmanager.listattr("hello")
|
|
||||||
assert pluginmanager.listattr("x") == [42]
|
|
||||||
|
|
||||||
def test_listattr_tryfirst(self):
|
|
||||||
class P1:
|
|
||||||
@pytest.mark.tryfirst
|
|
||||||
def m(self):
|
|
||||||
return 17
|
|
||||||
|
|
||||||
class P2:
|
|
||||||
def m(self):
|
|
||||||
return 23
|
|
||||||
class P3:
|
|
||||||
def m(self):
|
|
||||||
return 19
|
|
||||||
|
|
||||||
pluginmanager = PluginManager("xyz")
|
|
||||||
p1 = P1()
|
|
||||||
p2 = P2()
|
|
||||||
p3 = P3()
|
|
||||||
pluginmanager.register(p1)
|
|
||||||
pluginmanager.register(p2)
|
|
||||||
pluginmanager.register(p3)
|
|
||||||
methods = pluginmanager.listattr('m')
|
|
||||||
assert methods == [p2.m, p3.m, p1.m]
|
|
||||||
del P1.m.__dict__['tryfirst']
|
|
||||||
pytest.mark.trylast(getattr(P2.m, 'im_func', P2.m))
|
|
||||||
methods = pluginmanager.listattr('m')
|
|
||||||
assert methods == [p2.m, p1.m, p3.m]
|
|
||||||
|
|
||||||
|
|
||||||
def test_namespace_has_default_and_env_plugins(testdir):
|
def test_namespace_has_default_and_env_plugins(testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
|
@ -386,35 +450,6 @@ class TestMultiCall:
|
||||||
assert res == []
|
assert res == []
|
||||||
assert l == ["m1 init", "m2 init", "m2 finish", "m1 finish"]
|
assert l == ["m1 init", "m2 init", "m2 finish", "m1 finish"]
|
||||||
|
|
||||||
def test_listattr_hookwrapper_ordering(self):
|
|
||||||
class P1:
|
|
||||||
@pytest.mark.hookwrapper
|
|
||||||
def m(self):
|
|
||||||
return 17
|
|
||||||
|
|
||||||
class P2:
|
|
||||||
def m(self):
|
|
||||||
return 23
|
|
||||||
|
|
||||||
class P3:
|
|
||||||
@pytest.mark.tryfirst
|
|
||||||
def m(self):
|
|
||||||
return 19
|
|
||||||
|
|
||||||
pluginmanager = PluginManager("xyz")
|
|
||||||
p1 = P1()
|
|
||||||
p2 = P2()
|
|
||||||
p3 = P3()
|
|
||||||
pluginmanager.register(p1)
|
|
||||||
pluginmanager.register(p2)
|
|
||||||
pluginmanager.register(p3)
|
|
||||||
methods = pluginmanager.listattr('m')
|
|
||||||
assert methods == [p2.m, p3.m, p1.m]
|
|
||||||
## listattr keeps a cache and deleting
|
|
||||||
## a function attribute requires clearing it
|
|
||||||
#pluginmanager._listattrcache.clear()
|
|
||||||
#del P1.m.__dict__['tryfirst']
|
|
||||||
|
|
||||||
def test_hookwrapper_not_yield(self):
|
def test_hookwrapper_not_yield(self):
|
||||||
def m1():
|
def m1():
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Reference in New Issue