def test_get_commands_registry_multiple_same_file(self): """Verify that a single file can contain multiple command workers and their subcommands.""" with dynamic_entrypoint( "nautobot.workers", name="first_command", module="nautobot_chatops.tests.workers.two_commands" ): with dynamic_entrypoint( "nautobot.workers", name="second_command", module="nautobot_chatops.tests.workers.two_commands" ): registry = get_commands_registry() # Make sure both commands and both subcommands were loaded self.assertIn("first_command", registry) self.assertIn("function", registry["first_command"]) self.assertTrue(callable(registry["first_command"]["function"])) self.assertIn("subcommands", registry["first_command"]) self.assertIn("first-subcommand", registry["first_command"]["subcommands"]) self.assertIn("worker", registry["first_command"]["subcommands"]["first-subcommand"]) self.assertTrue(callable(registry["first_command"]["subcommands"]["first-subcommand"]["worker"])) self.assertIn("second_command", registry) self.assertIn("function", registry["second_command"]) self.assertTrue(callable(registry["second_command"]["function"])) self.assertIn("subcommands", registry["second_command"]) self.assertIn("second-subcommand", registry["second_command"]["subcommands"]) self.assertIn("worker", registry["second_command"]["subcommands"]["second-subcommand"]) self.assertTrue(callable(registry["second_command"]["subcommands"]["second-subcommand"]["worker"]))
def test_names_need_not_be_unique_in_different_scopes(): with dynamic_entrypoint('test-group', name='foo', entrypoint=ep_1, scope='a'): with dynamic_entrypoint('test-group', name='foo', entrypoint=ep_2, scope='b'): foos = list(pkg_resources.iter_entry_points('test-group', 'foo')) assert [ep.name for ep in foos] == ['foo', 'foo'] assert [ep.load() for ep in foos] == [ep_1, ep_2]
def test_names_must_be_unique_per_scope(): with dynamic_entrypoint('test-group', name='foo', entrypoint=ep_1): with pytest.raises(ValueError) as excinfo: with dynamic_entrypoint('test-group', name='foo', entrypoint=ep_2): pytest.fail('must not enter') assert (str(excinfo.value) == "'foo' is already registered under 'test-group' in scope " "'prybar.scope.default'")
def test_callable_entrypoint_must_have_module_attr(): assert SomeClass.func_without_module.__module__ is None with pytest.raises(ValueError) as excinfo: dynamic_entrypoint('test-group', entrypoint=SomeClass.func_without_module) msg = str(excinfo.value) assert msg.startswith('callable entrypoint has no __module__: ') assert f'SomeClass.func_without_module' in msg
def test_scopes_are_normalised_by_pkg_resources(): # scopes are pkg_resources distributions, and underscores are normalised # to hyphens in distribution names. with dynamic_entrypoint('test-group', entrypoint=ep_1, scope='foo-bar'): with pytest.raises(ValueError) as excinfo: with dynamic_entrypoint('test-group', entrypoint=ep_1, scope='foo_bar'): pass assert str(excinfo.value) == ( "'ep_1' is already registered under 'test-group' in scope " "'foo_bar' ('foo-bar')")
def test_get_commands_registry_dynamic_subcommands(self): """Verify Dynamic Commands.""" with dynamic_entrypoint( "nautobot.workers", name="dynamic_command", module="nautobot_chatops.tests.workers.dynamic_commands" ): with dynamic_entrypoint( "nautobot.workers", name="third_command", module="nautobot_chatops.tests.workers.dynamic_commands" ): subcommand_spec = { "worker": dynamic_subcommand, "params": ["param1", "param2"], "doc": "Do Something Dynamically", } add_subcommand( command_name="dynamic_command", command_func=dynamic_command, subcommand_name="dynamic-subcommand-name", subcommand_spec=subcommand_spec, ) registry = get_commands_registry() # Make sure the dynamic command is loaded self.assertIn("dynamic_command", registry) self.assertIn("function", registry["dynamic_command"]) self.assertTrue(callable(registry["dynamic_command"]["function"])) self.assertIn("subcommands", registry["dynamic_command"]) self.assertIn("dynamic-subcommand-name", registry["dynamic_command"]["subcommands"]) self.assertIn("worker", registry["dynamic_command"]["subcommands"]["dynamic-subcommand-name"]) self.assertIn("param1", registry["dynamic_command"]["subcommands"]["dynamic-subcommand-name"]["params"]) self.assertIn("param2", registry["dynamic_command"]["subcommands"]["dynamic-subcommand-name"]["params"]) self.assertTrue( callable(registry["dynamic_command"]["subcommands"]["dynamic-subcommand-name"]["worker"]) ) # Make sure the static command is also loaded self.assertIn("third_command", registry) self.assertIn("function", registry["third_command"]) self.assertTrue(callable(registry["third_command"]["function"])) self.assertIn("subcommands", registry["third_command"]) self.assertIn("third-subcommand", registry["third_command"]["subcommands"]) self.assertIn("worker", registry["third_command"]["subcommands"]["third-subcommand"]) self.assertTrue(callable(registry["third_command"]["subcommands"]["third-subcommand"]["worker"])) # Make sure the default nautobot command is still loaded self.assertIn("nautobot", registry)
def test_cant_use_start_with_context_manager(): dep = dynamic_entrypoint('test-group', ep_1) with pytest.raises(RuntimeError) as excinfo: with dep: dep.start() assert (str(excinfo.value) == 'can\'t start() while active via __enter__()')
def test_scope_cant_shadow_existing_distribution(): with pytest.raises(ValueError) as excinfo: # Try to shadow pytest with dynamic_entrypoint('test-group', ep_1, scope='pytest'): pytest.fail('must not enter') assert str(excinfo.value).startswith( "scope 'pytest' already exists in working set at location /")
def test_cant_use_start_with_decorator(): dep = dynamic_entrypoint('test-group', ep_1) @dep def block(): with pytest.raises(RuntimeError) as excinfo: dep.start() assert (str(excinfo.value) == 'can\'t start() while active via __enter__()') block()
def test_cant_use_context_manager_with_start(): dep = dynamic_entrypoint('test-group', ep_1) dep.start() with pytest.raises(RuntimeError) as excinfo: with dep: pytest.fail('should not enter block') dep.stop() assert str(excinfo.value) == 'can\'t __enter__() while active via start()'
def test_dynamic_entrypoint_registers_entrypoint_via_with(): assert list(pkg_resources.iter_entry_points('test-group')) == [] with dynamic_entrypoint('test-group', ep_1): eps = list(pkg_resources.iter_entry_points('test-group')) assert len(eps) == 1 assert eps[0].name == 'ep_1' assert eps[0].load() is ep_1 assert list(pkg_resources.iter_entry_points('test-group')) == []
def test_multiple_entrypoint_registration(): names = ['ep_1', 'ep_2', 'ep_3'] with contextlib.ExitStack() as stack: for name in ['ep_1', 'ep_2', 'ep_3']: stack.enter_context(dynamic_entrypoint('test-group', entrypoint=globals()[name])) eps = list(pkg_resources.iter_entry_points('test-group')) assert [ep.name for ep in eps] == names
def test_dynamic_entrypoint_registers_entrypoint_via_start(): assert list(pkg_resources.iter_entry_points('test-group')) == [] dep = dynamic_entrypoint('test-group', ep_1) dep.start() eps = list(pkg_resources.iter_entry_points('test-group')) assert len(eps) == 1 assert eps[0].name == 'ep_1' assert eps[0].load() is ep_1 dep.stop() assert list(pkg_resources.iter_entry_points('test-group')) == []
def test_start_stop_can_be_called_when_already_started_or_stopped(): dep = dynamic_entrypoint('test-group', ep_1) assert list(pkg_resources.iter_entry_points('test-group')) == [] dep.stop() dep.stop() dep.start() assert len(list(pkg_resources.iter_entry_points('test-group'))) == 1 dep.start() dep.start() assert len(list(pkg_resources.iter_entry_points('test-group'))) == 1 dep.stop() dep.stop() assert list(pkg_resources.iter_entry_points('test-group')) == []
def test_cant_use_decorator_with_start(): dep = dynamic_entrypoint('test-group', ep_1) dep.start() @dep def foo(): pytest.fail('should not enter function') with pytest.raises(RuntimeError) as excinfo: foo() dep.stop() assert str(excinfo.value) == 'can\'t __enter__() while active via start()'
def test_entrypoint_can_be_entered_multiple_times(): dyn_ep = dynamic_entrypoint('test-group', ep_1) def assert_ep_1_active(): eps = list(pkg_resources.iter_entry_points('test-group')) assert len(eps) == 1 assert eps[0].load() is ep_1 @dyn_ep def func_with_ep_1_active(): assert_ep_1_active() # we can enter the context manager again from a decorated function with dyn_ep: assert_ep_1_active() # It's still active now, even though this context manager has stopped assert_ep_1_active() # Nothing's used the entry point yet, so it doesn't exist assert list(pkg_resources.iter_entry_points('test-group')) == [] with dyn_ep: assert_ep_1_active() # we can use a decorated function with entry point active func_with_ep_1_active() # we can enter the manager again with dyn_ep: assert_ep_1_active() # We can't use the manual API while a context manager is active with pytest.raises(RuntimeError): dyn_ep.stop() with pytest.raises(RuntimeError): dyn_ep.start() # and again, it's still active now assert_ep_1_active() # Now there are no more users, so it's gone assert list(pkg_resources.iter_entry_points('test-group')) == [] # And we can re-activate it again: with dyn_ep: assert_ep_1_active() assert list(pkg_resources.iter_entry_points('test-group')) == []
def test_invalid_arguments(args, kwargs, exc, msg): with pytest.raises(exc) as excinfo: with dynamic_entrypoint(*args, **kwargs): pass assert msg in str(excinfo.value)
def test_entrypoints_are_loadable(func_name): with dynamic_entrypoint('test-group', name=func_name, module=__name__): ep = next(pkg_resources.iter_entry_points('test-group')) assert ep.name == func_name assert ep.load() is globals()[func_name]
def test_entrypoints_with_multiple_attributes_load_nested_objects( kwargs, expected): with dynamic_entrypoint('test-group', **kwargs): ep = next(pkg_resources.iter_entry_points('test-group')) assert ep.load() is expected
def test_valid_arguments(args, kwargs): with dynamic_entrypoint(*args, **kwargs): assert ['ep_1'] == [ep.name for ep in pkg_resources.iter_entry_points('test-group')]
def test_cant_exit_without_enter(): with pytest.raises(RuntimeError) as excinfo: dynamic_entrypoint('test-group', ep_1).__exit__(None, None, None) assert str(excinfo.value) == '__exit__() called more than __enter__()'