diff --git a/dramatiq/registry.py b/dramatiq/registry.py new file mode 100644 index 00000000..1eb15f7f --- /dev/null +++ b/dramatiq/registry.py @@ -0,0 +1,65 @@ +from .actor import Actor, _queue_name_re + + +class Registry: + """ + A Registry allows defining a collection of Actors not directly bound to a Broker. + + This allows your code to declar Actors before configuring a Broker. + """ + def __init__(self): + self.actors = {} + self.broker = None + + def actor(self, fn=None, *, actor_class=Actor, actor_name=None, queue_name="default", priority=0, **options): + """ + Mimics `actor.actor` decorator, but skips the actor options check, and passes `self` as broker. + """ + + def decorator(fn): + if not _queue_name_re.fullmatch(queue_name): + raise ValueError( + "Queue names must start with a letter or an underscore followed " + "by any number of letters, digits, dashes or underscores." + ) + + return actor_class( + fn, + actor_name=actor_name or fn.__name__, + queue_name=queue_name, + priority=priority, + broker=self, + options=options, + ) + + if fn is None: + return decorator + return decorator(fn) + + def __getattr__(self, name): + # Proxy everything else to our Broker, if set. + return getattr(self.broker, name) + + def declare_actor(self, actor): + """ + Intercept when Actor class tries to register itself. + """ + if self.broker: + self.broker.declare_actor(actor) + else: + self.actors[actor.actor_name] = actor + + def bind_broker(self, broker): + self.broker = broker + + for actor_name, actor in self.actors.items(): + invalid_options = set(actor.options) - broker.actor_options + if invalid_options: + invalid_options_list = ", ".join(invalid_options) + raise ValueError(( + "Actor %s specified the following options which are not " + "supported by this broker: %s. Did you forget to add a " + "middleware to your Broker?" + ) % (actor_name, invalid_options_list)) + + broker.declar_actor(actor) diff --git a/tests/test_registry.py b/tests/test_registry.py new file mode 100644 index 00000000..de505195 --- /dev/null +++ b/tests/test_registry.py @@ -0,0 +1,22 @@ +import dramatiq +from dramatiq.registry import Registry + + +def test_registry_can_be_declared(): + + Registry() + + +def test_actor_can_be_declared_on_registry(): + # When I have a Registry + reg = Registry() + + # Given that I've decorated a function with @registry.actor + @reg.actor + def add(x, y): + return x + y + + # I expect that function to become an instance of Actor + assert isinstance(add, dramatiq.Actor) + assert add.broker is reg + assert len(reg.actors) == 1