def test_custom_hook_registration(request, is_internal): hook_name = 'some_hook' with pytest.raises(LookupError): gossip.get_hook(hook_name) class MyPlugin(PluginInterface): def get_name(self): return "plugin" @plugins.registers_on(hook_name) def unknown(self): pass p = MyPlugin() plugins.manager.install(p, activate=True, is_internal=is_internal) @request.addfinalizer def cleanup(): # pylint: disable=unused-variable plugins.manager.uninstall(p) registrations = gossip.get_hook(hook_name).get_registrations() assert len(registrations) == 1 [r] = registrations if PY2: assert r.func.__func__ is MyPlugin.unknown.__func__ # pylint: disable=no-member else: assert r.func.__func__ is MyPlugin.unknown # make sure we deactivate properly as well plugins.manager.deactivate(p) assert not gossip.get_hook(hook_name).get_registrations()
def test_custom_hook_registration(): hook_name = 'some_hook' with pytest.raises(LookupError): gossip.get_hook(hook_name) class MyPlugin(PluginInterface): def get_name(self): return "plugin" @plugins.registers_on(hook_name) def unknown(self): pass p = MyPlugin() plugins.manager.install(p, activate=True) registrations = gossip.get_hook(hook_name).get_registrations() assert 1 == len(registrations) [r] = registrations if PY2: assert r.func.__func__ is MyPlugin.unknown.__func__ else: assert r.func.__func__ is MyPlugin.unknown # make sure we deactivate properly as well plugins.manager.deactivate(p) assert not gossip.get_hook(hook_name).get_registrations()
def test_multiple_registers_on(request): hook_names = ['some_hook_{}'.format(i) for i in range(2)] class MyPlugin(PluginInterface): def get_name(self): return "plugin" @plugins.registers_on(hook_names[0]) @plugins.registers_on(hook_names[1]) def unknown(self): pass expected_func = MyPlugin.unknown.__func__ if PY2 else MyPlugin.unknown p = MyPlugin() plugins.manager.install(p, activate=True) @request.addfinalizer def cleanup(): # pylint: disable=unused-variable plugins.manager.uninstall(p) for hook_name in hook_names: registrations = gossip.get_hook(hook_name).get_registrations() assert len(registrations) == 1 assert registrations[0].func.__func__ is expected_func plugins.manager.deactivate(p) for hook_name in hook_names: assert not gossip.get_hook(hook_name).get_registrations()
def test_register_no_op_solves_dependencies(timeline): timeline.register(needs=['1'], provides=['0']) timeline.register(needs=['2'], provides=['1']) assert len(gossip.get_hook(timeline.hook_name)._registrations) == 2 # pylint: disable=protected-access with pytest.raises(CannotResolveDependencies) as caught: timeline.trigger() assert caught.value.unmet_dependencies == set(['2']) timeline.get_hook().register_no_op(provides=['2']) # Make sure "register_no_op" is not registred as "regular" callback assert len(gossip.get_hook(timeline.hook_name)._registrations) == 2 # pylint: disable=protected-access assert len(gossip.get_hook(timeline.hook_name)._empty_regisrations) == 1 # pylint: disable=protected-access timeline.trigger()
def test_undefine(hook_name): gossip.define(hook_name) @gossip.register(hook_name) def handler(): pass hook = gossip.get_hook(hook_name) hook.register_no_op() hook.undefine() gossip.define(hook_name) assert not gossip.get_hook(hook_name).get_registrations() assert not gossip.get_hook(hook_name).get_registrations(include_empty=True)
def _get_plugin_registrations(self, plugin): returned = [] unknown = [] for method_name in dir(type(plugin)): if method_name in _SKIPPED_PLUGIN_METHOD_NAMES: continue if method_name.startswith("_"): continue method = getattr(plugin, method_name) hook_name = try_get_mark(method, "register_on") if hook_name is None: expect_exists = True hook_name = "slash.{0}".format(method_name) else: expect_exists = False try: if expect_exists: hook = gossip.get_hook(hook_name) else: hook = gossip.hooks.get_or_create_hook(hook_name) if not hook.is_defined() and hook.group.is_strict(): raise LookupError() except LookupError: unknown.append(hook_name) continue assert hook is not None returned.append((hook, method)) if unknown: raise IncompatiblePlugin("Unknown hooks: {0}".format(", ".join(unknown))) return returned
def registrations(): @gossip.register("group.hook1") def handler1(): pass @gossip.register("group.hook2", token="token1") def handler2(): pass @gossip.register("group2.subgroup.hook3", token="token1") def handler3(): pass @gossip.register("group3.subgroup.subgroup2.hook4", token="token2") def handler4(): pass handler_no_op = gossip.get_hook( "group3.subgroup.subgroup2.hook4").register_no_op(token="token1") return Munch(handler1=handler1, handler2=handler2, handler3=handler3, handler4=handler4, handler_no_op=handler_no_op)
def ensure_custom_hook(hook_name): """ Like :func:`.add_custom_hook`, only forgives if the hook already exists """ try: return gossip.get_hook("slash.{0}".format(hook_name)) except LookupError: return _define(hook_name)
def ensure_custom_hook(hook_name): """ Like :func:`.add_custom_hook`, only forgives if the hook already exists """ try: return gossip.get_hook("slash.{}".format(hook_name)) except LookupError: return _define(hook_name)
def test_needs_provides_cyclic_complex(timeline): timeline.register(needs=['1'], provides=['0']) timeline.register(needs=['2'], provides=['1']) timeline.register(needs=['3'], provides=['2']) timeline.register(needs=['4'], provides=['3']) with pytest.raises(CannotResolveDependencies): timeline.register(needs=['0'], provides=['4']) # make sure the cyclic registration was not added assert len(gossip.get_hook(timeline.hook_name)._registrations) == 4 # pylint: disable=protected-access
def test_forbid_muting(params): name_for_mute, forbid_name, is_group, is_allowed = params getter = gossip.get_group if is_group else gossip.get_hook forbidden_obj = getter(forbid_name) forbidden_obj.forbid_muting() hook = gossip.get_hook(name_for_mute) assert hook.can_be_muted() == is_allowed forbidden_obj.allow_muting() assert hook.can_be_muted() == is_allowed
def test_get_registrations_include_empty(hook_name, include_empty): gossip.define(hook_name) @gossip.register(hook_name) def handler(): pass hook = gossip.get_hook(hook_name) hook.register_no_op() expected = 2 if include_empty else 1 assert len(hook.get_registrations(include_empty=include_empty)) == expected
def _get_plugin_registrations(self, plugin): returned = [] unknown = [] global_needs = try_get_mark(plugin, 'plugin_needs', []) global_provides = try_get_mark(plugin, 'plugin_provides', []) for method_name in dir(type(plugin)): if method_name in _SKIPPED_PLUGIN_METHOD_NAMES: continue method = getattr(plugin, method_name) if not hasattr(method, '__call__'): continue hook_name = try_get_mark(method, 'register_on', NOTHING) if hook_name is None: # asked not to register for nothing continue if hook_name is not NOTHING: expect_exists = False else: if method_name.startswith('_'): continue expect_exists = True hook_name = "slash.{0}".format(method_name) plugin_needs = try_get_mark(method, 'plugin_needs', []) + global_needs plugin_provides = try_get_mark(method, 'plugin_provides', []) + global_provides try: if expect_exists: hook = gossip.get_hook(hook_name) else: hook = gossip.hooks.get_or_create_hook(hook_name) if not hook.is_defined() and hook.group.is_strict(): raise LookupError() except LookupError: unknown.append(hook_name) continue assert hook is not None returned.append((hook, method, plugin_needs, plugin_provides)) if unknown: raise IncompatiblePlugin("Unknown hooks: {0}".format(", ".join(unknown))) return returned
def registrations(): @gossip.register("group.hook1") def handler1(): pass @gossip.register("group.hook2", token="token1") def handler2(): pass @gossip.register("group2.subgroup.hook3", token="token1") def handler3(): pass @gossip.register("group3.subgroup.subgroup2.hook4", token="token2") def handler4(): pass handler_no_op = gossip.get_hook("group3.subgroup.subgroup2.hook4").register_no_op(token="token1") return Munch(handler1=handler1, handler2=handler2, handler3=handler3, handler4=handler4, handler_no_op=handler_no_op)
def test_registers_on_kwargs(class_level_needs, class_level_provides): needs_decorator = plugins.needs('other_requirement') provides_decorator = plugins.provides('another_provided_requirement') @slash.plugins.active # pylint: disable=unused-variable @maybe_decorate(needs_decorator, class_level_needs) @maybe_decorate(provides_decorator, class_level_provides) class SamplePlugin(PluginInterface): def get_name(self): return 'sample' @plugins.registers_on('some.hook', provides=['provided_requirement'], needs=['some_requirement'], tags=['tag']) @maybe_decorate(needs_decorator, not class_level_needs) @maybe_decorate(provides_decorator, not class_level_provides) def plugin_method(self): pass @gossip.register('some.hook', provides=['some_requirement', 'other_requirement']) def _unused(): pass gossip.trigger('some.hook') hook = gossip.get_hook('some.hook') [registration] = [ reg for reg in hook.get_registrations() if reg.func.__name__ == 'plugin_method' ] assert registration.tags == {'tag'} assert registration.needs == frozenset( ['some_requirement', 'other_requirement']) assert registration.provides == frozenset( ['provided_requirement', 'another_provided_requirement'])
def test_get_hook(hook_name): with pytest.raises(LookupError): gossip.get_hook(hook_name) hook = gossip.define(hook_name) assert gossip.get_hook(hook_name) is hook
def test_hook_documentation(): docstring = "fdkjfkdjfd" gossip.define("some_hook", doc=docstring) assert gossip.get_hook("some_hook").doc == docstring
def _get_plugin_registrations(self, plugin): plugin_name = plugin.get_name() returned = [] unknown = [] global_needs = try_get_mark(plugin, 'plugin_needs', []) global_provides = try_get_mark(plugin, 'plugin_provides', []) has_session_end = has_session_start = False for method_name in dir(type(plugin)): if method_name in _SKIPPED_PLUGIN_METHOD_NAMES: continue method = getattr(plugin, method_name) if not hasattr(method, '__call__'): continue hook_name = try_get_mark(method, 'register_on', NOTHING) if hook_name is None: # asked not to register for nothing continue if not try_get_mark(method, 'register_if', True): continue if hook_name is not NOTHING: expect_exists = False else: if method_name.startswith('_'): continue expect_exists = True hook_name = "slash.{0}".format(method_name) plugin_needs = try_get_mark(method, 'plugin_needs', []) + global_needs plugin_provides = try_get_mark(method, 'plugin_provides', []) + global_provides try: if expect_exists: hook = gossip.get_hook(hook_name) else: hook = gossip.hooks.get_or_create_hook(hook_name) if not hook.is_defined() and hook.group.is_strict(): raise LookupError() except LookupError: unknown.append(hook_name) continue assert hook is not None kwargs = { 'needs': plugin_needs, 'provides': plugin_provides, 'token': self._get_token(plugin_name), } if hook_name == 'slash.session_start': has_session_start = True kwargs['toggles_on'] = plugin.__toggles__['session'] elif hook_name == 'slash.session_end': has_session_end = True kwargs['toggles_off'] = plugin.__toggles__['session'] returned.append((hook, method, kwargs)) if has_session_end and not has_session_start: returned.append( (gossip.get_hook('slash.session_start'), lambda: None, { 'toggles_on': plugin.__toggles__['session'] })) if unknown: raise IncompatiblePlugin("Unknown hooks: {0}".format( ", ".join(unknown))) return returned
def get_hook_by_name(hook_name): """ Returns a hook (if exists) by its name, otherwise returns None """ return gossip.get_hook('slash.{0}'.format(hook_name))
def get_name(self): return 'sample' @plugins.registers_on('some.hook', provides=['provided_requirement'], needs=['some_requirement'], tags=['tag']) @maybe_decorate(needs_decorator, not class_level_needs) @maybe_decorate(provides_decorator, not class_level_provides) def plugin_method(self): pass @gossip.register('some.hook', provides=['some_requirement', 'other_requirement']) def _unused(): pass gossip.trigger('some.hook') hook = gossip.get_hook('some.hook') [registration] = [reg for reg in hook.get_registrations() if reg.func.__name__ == 'plugin_method'] assert registration.tags == {'tag'} assert registration.needs == frozenset(['some_requirement', 'other_requirement']) assert registration.provides == frozenset(['provided_requirement', 'another_provided_requirement']) def test_registers_on_with_private_methods(restore_plugins_on_cleanup, checkpoint): @slash.plugins.active # pylint: disable=unused-variable class SamplePlugin(PluginInterface): def get_name(self): return 'sample' @plugins.registers_on('some_hook')
def get_hook_by_name(hook_name): """ Returns a hook (if exists) by its name, otherwise returns None """ return gossip.get_hook('slash.{}'.format(hook_name))
def remove_custom_hook(hook_name): """ Removes a hook from the set of available hooks """ gossip.get_hook("slash.{}".format(hook_name)).undefine() globals().pop(hook_name)
def _get_plugin_registrations(self, plugin): plugin_name = plugin.get_name() returned = [] unknown = [] global_needs = try_get_mark(plugin, 'plugin_needs', []) global_provides = try_get_mark(plugin, 'plugin_provides', []) has_session_end = has_session_start = False register_no_op_hooks = set() if global_provides: register_no_op_hooks.update( hook.full_name for hook in gossip.get_group('slash').get_hooks()) for method_name in dir(type(plugin)): if method_name in _SKIPPED_PLUGIN_METHOD_NAMES: continue method = getattr(plugin, method_name) if not hasattr(method, '__call__'): continue registration_list = try_get_mark(method, 'register_on', NOTHING) if registration_list is not NOTHING: registration_list = registration_list[:] else: if method_name.startswith('_'): continue registration_list = [ RegistrationInfo("slash.{}".format(method_name), expect_exists=True) ] for registration_info in registration_list: if registration_info.hook_name is None: # asked not to register for nothing continue if not try_get_mark(method, 'register_if', True): continue plugin_needs = list( itertools.chain( try_get_mark(method, 'plugin_needs', []), global_needs, registration_info.register_kwargs.get('needs', []))) plugin_provides = list( itertools.chain( try_get_mark(method, 'plugin_provides', []), global_provides, registration_info.register_kwargs.get('provides', []))) try: if registration_info.expect_exists: hook = gossip.get_hook(registration_info.hook_name) else: hook = gossip.hooks.get_or_create_hook( registration_info.hook_name) if not hook.is_defined() and hook.group.is_strict(): raise LookupError() except LookupError: unknown.append(registration_info.hook_name) continue assert hook is not None register_no_op_hooks.discard(registration_info.hook_name) kwargs = registration_info.register_kwargs.copy() kwargs.update({ 'needs': plugin_needs, 'provides': plugin_provides, 'token': self._get_token(plugin_name), }) if registration_info.hook_name == 'slash.session_start': has_session_start = True kwargs['toggles_on'] = plugin.__toggles__['session'] elif registration_info.hook_name == 'slash.session_end': has_session_end = True kwargs['toggles_off'] = plugin.__toggles__['session'] returned.append((hook, method, kwargs)) if has_session_end and not has_session_start: hook = gossip.get_hook('slash.session_start') returned.append((hook, lambda: None, { 'toggles_on': plugin.__toggles__['session'] })) register_no_op_hooks.discard(hook.full_name) for hook_name in register_no_op_hooks: hook = gossip.get_hook(hook_name) hook.register_no_op(provides=global_provides, token=self._get_token(plugin_name)) if unknown: raise IncompatiblePlugin("Unknown hooks: {}".format( ", ".join(unknown))) return returned
def get_hook(self): return gossip.get_hook(self.hook_name)
def remove_custom_hook(hook_name): """ Removes a hook from the set of available hooks """ gossip.get_hook("slash.{0}".format(hook_name)).undefine() globals().pop(hook_name)
def test_unregister_all_on_hook(registered_hooks): assert all(r.works() for r in registered_hooks) gossip.get_hook(registered_hooks[0].name).unregister_all() assert not registered_hooks[0].works() assert all(r.works() for r in registered_hooks[1:])
def _get_plugin_registrations(self, plugin): plugin_name = plugin.get_name() returned = [] unknown = [] global_needs = try_get_mark(plugin, 'plugin_needs', []) global_provides = try_get_mark(plugin, 'plugin_provides', []) has_session_end = has_session_start = False for method_name in dir(type(plugin)): if method_name in _SKIPPED_PLUGIN_METHOD_NAMES: continue method = getattr(plugin, method_name) if not hasattr(method, '__call__'): continue hook_name = try_get_mark(method, 'register_on', NOTHING) if hook_name is None: # asked not to register for nothing continue if not try_get_mark(method, 'register_if', True): continue if hook_name is not NOTHING: expect_exists = False else: if method_name.startswith('_'): continue expect_exists = True hook_name = "slash.{0}".format(method_name) plugin_needs = try_get_mark(method, 'plugin_needs', []) + global_needs plugin_provides = try_get_mark(method, 'plugin_provides', []) + global_provides try: if expect_exists: hook = gossip.get_hook(hook_name) else: hook = gossip.hooks.get_or_create_hook(hook_name) if not hook.is_defined() and hook.group.is_strict(): raise LookupError() except LookupError: unknown.append(hook_name) continue assert hook is not None kwargs = { 'needs': plugin_needs, 'provides': plugin_provides, 'token': self._get_token(plugin_name), } if hook_name == 'slash.session_start': has_session_start = True kwargs['toggles_on'] = plugin.__toggles__['session'] elif hook_name == 'slash.session_end': has_session_end = True kwargs['toggles_off'] = plugin.__toggles__['session'] returned.append((hook, method, kwargs)) if has_session_end and not has_session_start: returned.append((gossip.get_hook('slash.session_start'), lambda: None, {'toggles_on': plugin.__toggles__['session']})) if unknown: raise IncompatiblePlugin("Unknown hooks: {0}".format(", ".join(unknown))) return returned