fix issue732: make sure removed plugins remove all hook callers.
--HG-- branch : more_plugin
This commit is contained in:
parent
4e116ed503
commit
7364647f2f
|
@ -36,6 +36,9 @@
|
||||||
- write/refine docs for "writing plugins" which now have their
|
- write/refine docs for "writing plugins" which now have their
|
||||||
own page and are separate from the "using/installing plugins`` page.
|
own page and are separate from the "using/installing plugins`` page.
|
||||||
|
|
||||||
|
- fix issue732: properly unregister plugins from any hook calling
|
||||||
|
sites allowing to have temporary plugins during test execution.
|
||||||
|
|
||||||
|
|
||||||
2.7.1.dev (compared to 2.7.0)
|
2.7.1.dev (compared to 2.7.0)
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
|
@ -233,7 +233,14 @@ 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)
|
||||||
return caller.clone(plugins=plugins)
|
hc = HookCaller(caller.name, caller._specmodule_or_class)
|
||||||
|
for plugin in plugins:
|
||||||
|
if hasattr(plugin, name):
|
||||||
|
hc._add_plugin(plugin)
|
||||||
|
# we also keep track of this hook caller so it
|
||||||
|
# gets properly removed on plugin unregistration
|
||||||
|
self._plugin2hookcallers.setdefault(plugin, []).append(hc)
|
||||||
|
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
|
||||||
|
@ -247,9 +254,8 @@ class PluginManager(object):
|
||||||
if self.hasplugin(name):
|
if self.hasplugin(name):
|
||||||
raise ValueError("Plugin already registered: %s=%s\n%s" %(
|
raise ValueError("Plugin already registered: %s=%s\n%s" %(
|
||||||
name, plugin, self._name2plugin))
|
name, plugin, self._name2plugin))
|
||||||
#self.trace("registering", name, plugin)
|
|
||||||
self._plugin2hookcallers[plugin] = self._scan_plugin(plugin)
|
|
||||||
self._name2plugin[name] = plugin
|
self._name2plugin[name] = plugin
|
||||||
|
self._scan_plugin(plugin)
|
||||||
self._plugins.append(plugin)
|
self._plugins.append(plugin)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -260,9 +266,8 @@ class PluginManager(object):
|
||||||
for name, value in list(self._name2plugin.items()):
|
for name, value in list(self._name2plugin.items()):
|
||||||
if value == plugin:
|
if value == plugin:
|
||||||
del self._name2plugin[name]
|
del self._name2plugin[name]
|
||||||
hookcallers = self._plugin2hookcallers.pop(plugin)
|
for hookcaller in self._plugin2hookcallers.pop(plugin):
|
||||||
for hookcaller in hookcallers:
|
hookcaller._remove_plugin(plugin)
|
||||||
hookcaller.remove_plugin(plugin)
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -276,7 +281,7 @@ class PluginManager(object):
|
||||||
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(module_or_class)
|
hc.set_specification(module_or_class)
|
||||||
for plugin in hc._plugins:
|
for plugin in hc._plugins:
|
||||||
self._verify_hook(hc, plugin)
|
self._verify_hook(hc, plugin)
|
||||||
names.append(name)
|
names.append(name)
|
||||||
|
@ -305,7 +310,7 @@ class PluginManager(object):
|
||||||
return self._name2plugin.get(name)
|
return self._name2plugin.get(name)
|
||||||
|
|
||||||
def _scan_plugin(self, plugin):
|
def _scan_plugin(self, plugin):
|
||||||
hookcallers = []
|
self._plugin2hookcallers[plugin] = hookcallers = []
|
||||||
for name in dir(plugin):
|
for name in dir(plugin):
|
||||||
if name[0] == "_" or not name.startswith(self._prefix):
|
if name[0] == "_" or not name.startswith(self._prefix):
|
||||||
continue
|
continue
|
||||||
|
@ -319,9 +324,8 @@ class PluginManager(object):
|
||||||
elif hook.has_spec():
|
elif hook.has_spec():
|
||||||
self._verify_hook(hook, plugin)
|
self._verify_hook(hook, plugin)
|
||||||
hook._apply_history(method)
|
hook._apply_history(method)
|
||||||
hook.add_plugin(plugin)
|
|
||||||
hookcallers.append(hook)
|
hookcallers.append(hook)
|
||||||
return hookcallers
|
hook._add_plugin(plugin)
|
||||||
|
|
||||||
def _verify_hook(self, hook, plugin):
|
def _verify_hook(self, hook, plugin):
|
||||||
method = getattr(plugin, hook.name)
|
method = getattr(plugin, hook.name)
|
||||||
|
@ -434,29 +438,12 @@ class HookCaller(object):
|
||||||
self._wrappers = []
|
self._wrappers = []
|
||||||
self._nonwrappers = []
|
self._nonwrappers = []
|
||||||
if specmodule_or_class is not None:
|
if specmodule_or_class is not None:
|
||||||
self.setspec(specmodule_or_class)
|
self.set_specification(specmodule_or_class)
|
||||||
|
|
||||||
def has_spec(self):
|
def has_spec(self):
|
||||||
return hasattr(self, "_specmodule_or_class")
|
return hasattr(self, "_specmodule_or_class")
|
||||||
|
|
||||||
def clone(self, plugins=None):
|
def set_specification(self, specmodule_or_class):
|
||||||
assert not self.is_historic()
|
|
||||||
hc = object.__new__(HookCaller)
|
|
||||||
hc.name = self.name
|
|
||||||
hc.argnames = self.argnames
|
|
||||||
hc.firstresult = self.firstresult
|
|
||||||
if plugins is None:
|
|
||||||
hc._plugins = self._plugins
|
|
||||||
hc._wrappers = list(self._wrappers)
|
|
||||||
hc._nonwrappers = list(self._nonwrappers)
|
|
||||||
else:
|
|
||||||
hc._plugins, hc._wrappers, hc._nonwrappers = [], [], []
|
|
||||||
for plugin in plugins:
|
|
||||||
if hasattr(plugin, hc.name):
|
|
||||||
hc.add_plugin(plugin)
|
|
||||||
return hc
|
|
||||||
|
|
||||||
def setspec(self, specmodule_or_class):
|
|
||||||
assert not self.has_spec()
|
assert not self.has_spec()
|
||||||
self._specmodule_or_class = specmodule_or_class
|
self._specmodule_or_class = specmodule_or_class
|
||||||
specfunc = getattr(specmodule_or_class, self.name)
|
specfunc = getattr(specmodule_or_class, self.name)
|
||||||
|
@ -470,7 +457,7 @@ class HookCaller(object):
|
||||||
def is_historic(self):
|
def is_historic(self):
|
||||||
return hasattr(self, "_call_history")
|
return hasattr(self, "_call_history")
|
||||||
|
|
||||||
def remove_plugin(self, plugin):
|
def _remove_plugin(self, plugin):
|
||||||
self._plugins.remove(plugin)
|
self._plugins.remove(plugin)
|
||||||
meth = getattr(plugin, self.name)
|
meth = getattr(plugin, self.name)
|
||||||
try:
|
try:
|
||||||
|
@ -478,11 +465,11 @@ class HookCaller(object):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._wrappers.remove(meth)
|
self._wrappers.remove(meth)
|
||||||
|
|
||||||
def add_plugin(self, plugin):
|
def _add_plugin(self, plugin):
|
||||||
self._plugins.append(plugin)
|
self._plugins.append(plugin)
|
||||||
self.add_method(getattr(plugin, self.name))
|
self._add_method(getattr(plugin, self.name))
|
||||||
|
|
||||||
def add_method(self, meth):
|
def _add_method(self, meth):
|
||||||
if hasattr(meth, 'hookwrapper'):
|
if hasattr(meth, 'hookwrapper'):
|
||||||
assert not self.is_historic()
|
assert not self.is_historic()
|
||||||
self._wrappers.append(meth)
|
self._wrappers.append(meth)
|
||||||
|
@ -511,11 +498,15 @@ class HookCaller(object):
|
||||||
return self._docall(self._nonwrappers + self._wrappers, kwargs)
|
return self._docall(self._nonwrappers + self._wrappers, kwargs)
|
||||||
|
|
||||||
def call_extra(self, methods, kwargs):
|
def call_extra(self, methods, kwargs):
|
||||||
assert not self.is_historic()
|
""" Call the hook with some additional temporarily participating
|
||||||
hc = self.clone()
|
methods using the specified kwargs as call parameters. """
|
||||||
|
old = list(self._nonwrappers), list(self._wrappers)
|
||||||
for method in methods:
|
for method in methods:
|
||||||
hc.add_method(method)
|
self._add_method(method)
|
||||||
return hc(**kwargs)
|
try:
|
||||||
|
return self(**kwargs)
|
||||||
|
finally:
|
||||||
|
self._nonwrappers, self._wrappers = old
|
||||||
|
|
||||||
def call_historic(self, proc=None, kwargs=None):
|
def call_historic(self, proc=None, kwargs=None):
|
||||||
self._call_history.append((kwargs or {}, proc))
|
self._call_history.append((kwargs or {}, proc))
|
||||||
|
|
|
@ -143,6 +143,25 @@ class TestPluginManager:
|
||||||
l = pm.hook.he_method1.call_extra([he_method1], dict(arg=1))
|
l = pm.hook.he_method1.call_extra([he_method1], dict(arg=1))
|
||||||
assert l == [10]
|
assert l == [10]
|
||||||
|
|
||||||
|
def test_make_hook_caller_unregistered(self, pm):
|
||||||
|
class Hooks:
|
||||||
|
def he_method1(self, arg):
|
||||||
|
pass
|
||||||
|
pm.addhooks(Hooks)
|
||||||
|
|
||||||
|
l = []
|
||||||
|
class Plugin:
|
||||||
|
def he_method1(self, arg):
|
||||||
|
l.append(arg * 10)
|
||||||
|
plugin = Plugin()
|
||||||
|
pm.register(plugin)
|
||||||
|
hc = pm.make_hook_caller("he_method1", [plugin])
|
||||||
|
hc(arg=1)
|
||||||
|
assert l == [10]
|
||||||
|
pm.unregister(plugin)
|
||||||
|
hc(arg=2)
|
||||||
|
assert l == [10]
|
||||||
|
|
||||||
|
|
||||||
class TestAddMethodOrdering:
|
class TestAddMethodOrdering:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -163,7 +182,7 @@ class TestAddMethodOrdering:
|
||||||
func.trylast = True
|
func.trylast = True
|
||||||
if hookwrapper:
|
if hookwrapper:
|
||||||
func.hookwrapper = True
|
func.hookwrapper = True
|
||||||
hc.add_method(func)
|
hc._add_method(func)
|
||||||
return func
|
return func
|
||||||
return wrap
|
return wrap
|
||||||
return addmeth
|
return addmeth
|
||||||
|
|
Loading…
Reference in New Issue