def test_get_registration(self): registry = Registry(("type", TypeAxis()), ("name", SimpleAxis())) registry.register("one", object) registry.register("two", DummyA, "foo") self.assertEqual(registry.get_registration(object), "one") self.assertEqual(registry.get_registration(DummyA, "foo"), "two") self.assertEqual(registry.get_registration(object, "foo"), None) self.assertEqual(registry.get_registration(DummyA), None)
def test_get_registration(): registry = Registry(("type", TypeAxis()), ("name", SimpleAxis())) registry.register("one", object) registry.register("two", DummyA, "foo") assert registry.get_registration(object) == "one" assert registry.get_registration(DummyA, "foo") == "two" assert registry.get_registration(object, "foo") is None assert registry.get_registration(DummyA) is None
def __init__(self, argspec, params_arity): """ Initialize dispatcher with ``argspec`` of type :class:`inspect.ArgSpec` and ``params_arity`` that represent number params.""" # Check if we have enough positional arguments for number of type params if _arity(argspec) < params_arity: raise TypeError("Not enough positional arguments " "for number of type parameters provided.") self.argspec = argspec self.params_arity = params_arity axis = [("arg_%d" % n, TypeAxis()) for n in range(params_arity)] self.registry = Registry(*axis)
def test_bad_lookup(): registry = Registry(("name", SimpleAxis()), ("grade", SimpleAxis())) with pytest.raises(ValueError): registry.register(1, foo=1) with pytest.raises(ValueError): registry.lookup(foo=1) with pytest.raises(ValueError): registry.register(1, "foo", name="foo")
def test_subtyping_on_axes(): registry = Registry(("type", TypeAxis())) target1 = "one" registry.register(target1, object) target2 = "two" registry.register(target2, DummyA) target3 = "three" registry.register(target3, DummyB) assert registry.lookup(object()) == target1 assert registry.lookup(DummyA()) == target2 assert registry.lookup(DummyB()) == target3
def test_subtyping_on_axes(self): registry = Registry(("type", TypeAxis())) target1 = "one" registry.register(target1, object) target2 = "two" registry.register(target2, DummyA) target3 = "three" registry.register(target3, DummyB) self.assertEqual(registry.lookup(object()), target1) self.assertEqual(registry.lookup(DummyA()), target2) self.assertEqual(registry.lookup(DummyB()), target3)
def test_two_axes(): registry: Registry[Union[str, object]] = Registry(("type", TypeAxis()), ("name", SimpleAxis())) target1 = "one" registry.register(target1, object) target2 = "two" registry.register(target2, DummyA) target3 = "three" registry.register(target3, DummyA, "foo") context1 = object() assert registry.lookup(context1) == target1 context2 = DummyB() assert registry.lookup(context2) == target2 assert registry.lookup(context2, "foo") == target3 target4 = object() registry.register(target4, DummyB) assert registry.lookup(context2) == target4 assert registry.lookup(context2, "foo") == target3
def test_one_axis_no_specificity(): registry = Registry(("foo", SimpleAxis())) a = object() b = object() registry.register(a) registry.register(b, "foo") assert registry.lookup() == a assert registry.lookup("foo") == b assert registry.lookup("bar") is None
def test_one_axis_no_specificity(self): registry = Registry(("foo", SimpleAxis())) a = object() b = object() registry.register(a) registry.register(b, "foo") self.assertEqual(registry.lookup(), a) self.assertEqual(registry.lookup("foo"), b) self.assertEqual(registry.lookup("bar"), None)
def test_query_subtyping_on_axes(): registry: Registry[str] = Registry(("type", TypeAxis())) target1 = "one" registry.register(target1, object) target2 = "two" registry.register(target2, DummyA) target3 = "three" registry.register(target3, DummyB) target4 = "four" registry.register(target4, int) assert list(registry.query(object())) == [target1] assert list(registry.query(DummyA())) == [target2, target1] assert list(registry.query(DummyB())) == [target3, target2, target1] assert list(registry.query(3)) == [target4, target1]
def test_miss(self): registry = Registry( ("one", SimpleAxis()), ("two", SimpleAxis()), ("three", SimpleAxis()) ) registry.register("foo", 1, 2) self.assertEqual(registry.lookup(one=1, three=3), None)
def test_bad_lookup(self): registry = Registry(("name", SimpleAxis()), ("grade", SimpleAxis())) self.assertRaises(ValueError, registry.register, 1, foo=1) self.assertRaises(ValueError, registry.lookup, foo=1) self.assertRaises(ValueError, registry.register, 1, "foo", name="foo")
def test_miss(): registry = Registry( ("one", SimpleAxis()), ("two", SimpleAxis()), ("three", SimpleAxis()) ) registry.register("foo", 1, 2) assert registry.lookup(one=1, three=3) is None
def test_skip_nodes(): registry = Registry( ("one", SimpleAxis()), ("two", SimpleAxis()), ("three", SimpleAxis()) ) registry.register("foo", one=1, three=3) assert registry.lookup(1, three=3) == "foo"
def test_conflict_error(): registry = Registry(("name", SimpleAxis())) registry.register(object(), name="foo") with pytest.raises(ValueError): registry.register(object, "foo")
def test_two_axes(self): registry = Registry(("type", TypeAxis()), ("name", SimpleAxis())) target1 = "one" registry.register(target1, object) target2 = "two" registry.register(target2, DummyA) target3 = "three" registry.register(target3, DummyA, "foo") context1 = object() self.assertEqual(registry.lookup(context1), target1) context2 = DummyB() self.assertEqual(registry.lookup(context2), target2) self.assertEqual(registry.lookup(context2, "foo"), target3) target4 = object() registry.register(target4, DummyB) self.assertEqual(registry.lookup(context2), target4) self.assertEqual(registry.lookup(context2, "foo"), target3)
def test_register_too_many_keys(): registry = Registry(("name", SimpleAxis())) with pytest.raises(ValueError): registry.register(object, "one", "two")
def test_conflict_error(self): registry = Registry(("name", SimpleAxis())) registry.register(object(), name="foo") self.assertRaises(ValueError, registry.register, object(), "foo")
class Manager: """ Event manager Provides API for subscribing for and firing events. There's also global event manager instantiated at module level with functions :func:`.subscribe`, :func:`.handle` and decorator :func:`.subscriber` aliased to corresponding methods of class. """ def __init__(self): axes = (("event_type", TypeAxis()), ) self.registry = Registry(*axes) def subscribe(self, handler, event_type): """ Subscribe ``handler`` to specified ``event_type``""" handler_set = self.registry.get_registration(event_type) if handler_set is None: handler_set = self._register_handler_set(event_type) handler_set.add(handler) def unsubscribe(self, handler, event_type): """ Unsubscribe ``handler`` from ``event_type``""" handler_set = self.registry.get_registration(event_type) if handler_set and handler in handler_set: handler_set.remove(handler) def handle(self, event): """ Fire ``event`` All subscribers will be executed with no determined order. """ handler_sets = self.registry.query(event) for handler_set in handler_sets: for handler in set(handler_set): handler(event) def _register_handler_set(self, event_type): """ Register new handler set for ``event_type``. """ handler_set = set() self.registry.register(handler_set, event_type) return handler_set def subscriber(self, event_type): """ Decorator for subscribing handlers Works like this: >>> mymanager = Manager() >>> class MyEvent(): ... pass >>> @mymanager.subscriber(MyEvent) ... def mysubscriber(evt): ... # handle event ... return >>> mymanager.handle(MyEvent()) """ def registrator(func): self.subscribe(func, event_type) return func return registrator
def test_register_too_many_keys(self): registry = Registry(("name", SimpleAxis())) self.assertRaises(ValueError, registry.register, object(), "one", "two")
def test_skip_nodes(self): registry = Registry( ("one", SimpleAxis()), ("two", SimpleAxis()), ("three", SimpleAxis()) ) registry.register("foo", one=1, three=3) self.assertEqual(registry.lookup(1, three=3), "foo")
def test_lookup_too_many_keys(): registry = Registry(("name", SimpleAxis())) with pytest.raises(ValueError): registry.register(registry.lookup("one", "two"))
class FunctionDispatcher: """ Multidispatcher for functions This object dispatch calls to function by its argument types. Usually it is produced by :func:`.multidispatch` decorator. You should not manually create objects of this type. """ def __init__(self, argspec, params_arity): """ Initialize dispatcher with ``argspec`` of type :class:`inspect.ArgSpec` and ``params_arity`` that represent number params.""" # Check if we have enough positional arguments for number of type params if _arity(argspec) < params_arity: raise TypeError( "Not enough positional arguments " "for number of type parameters provided." ) self.argspec = argspec self.params_arity = params_arity axis = [(f"arg_{n:d}", TypeAxis()) for n in range(params_arity)] self.registry = Registry(*axis) def check_rule(self, rule, *argtypes): # Check if we have the right number of parametrized types if len(argtypes) != self.params_arity: raise TypeError( f"Wrong number of type parameters: have {len(argtypes)}, expected {self.params_arity}." ) # Check if we have the same argspec (by number of args) rule_argspec = inspect.getfullargspec(rule) left_spec = tuple(x and len(x) or 0 for x in rule_argspec[:4]) right_spec = tuple(x and len(x) or 0 for x in self.argspec[:4]) if left_spec != right_spec: raise TypeError("Rule does not conform to previous implementations.") def register_rule(self, rule, *argtypes): """ Register new ``rule`` for ``argtypes``.""" self.check_rule(rule, *argtypes) self.registry.register(rule, *argtypes) def register(self, *argtypes): """ Decorator for registering new case for multidispatch New case will be registered for types identified by ``argtypes``. The length of ``argtypes`` should be equal to the length of ``argtypes`` argument were passed corresponding :func:`.multidispatch` call, which also indicated the number of arguments multidispatch dispatches on. """ def register_rule(func): self.register_rule(func, *argtypes) return func return register_rule def __call__(self, *args, **kwargs): """ Dispatch call to appropriate rule.""" trimmed_args = args[: self.params_arity] rule = self.registry.lookup(*trimmed_args) if not rule: raise TypeError(f"No available rule found for {trimmed_args!r}") return rule(*args, **kwargs)
def test_query_subtyping_on_axes(self): registry = Registry(("type", TypeAxis())) target1 = "one" registry.register(target1, object) target2 = "two" registry.register(target2, DummyA) target3 = "three" registry.register(target3, DummyB) target4 = "four" registry.register(target4, int) self.assertEqual(list(registry.query(object())), [target1]) self.assertEqual(list(registry.query(DummyA())), [target2, target1]) self.assertEqual(list(registry.query(DummyB())), [target3, target2, target1]) self.assertEqual(list(registry.query(3)), [target4, target1])
def __init__(self): axes = (("event_type", TypeAxis()), ) self.registry = Registry(*axes)
def test_lookup_too_many_keys(self): registry = Registry(("name", SimpleAxis())) self.assertRaises(ValueError, registry.lookup, "one", "two")