Package: async-signals
async-signalsEasy library to implement the observer pattern in async code.Note:This library is a copy of the signals library fromDjango. I always felt
like using the observer pattern in Django is pretty well crafted and liked
the way Django did implement this. But when switching toFastAPII missed this feature. So I decided
to copy the signals library from Django and implement it for FastAPI and other
async frameworks.A big thanks to the nice people why built Django! And for using a BSD license
to make this possible.Changes from the original Django signals librarySignal.send(...)andSignal.send_robust(...)are now async functions 🚀I added type annotations to all functions and classes, mypy is happy now 🧐I created tests for the signals library - without using any Django models 😎InstallationJust usepip install async-signalsto install the library.Usagefromasync_signalsimportSignal# Create a signalmy_signal=Signal()# Connect a function to the signal (can be async or sync, needs to receive **kwargs)asyncdefmy_handler(sender,**kwargs):print("Signal received!")my_signal.connect(my_handler)# Send the signalawaitmy_signal.send("sender")signal.send(...)will return a list of all called receivers and their return
values.About **kwargsThe**kwargsare mandatory for your receivers. This is because the signal
will pass any arguments it receives to the receivers. This is useful if you
want to pass additional information to the receivers. To allow adding
additional arguments to the signal in the future, the receivers should is
required to accept**kwargs.About weak signalsThe signal class will automatically remove signals when the receiver is
garbage collected. This is done by using weak references. This means that
you can use signals in long running applications without having to worry
about memory leaks.If you want to disable this behaviour you can set theweakparameter toFalsewhen connecting the receiver.my_signal.connect(my_handler,weak=False)# ormy_signal.connect(my_handler,weak=True)# the defaultAbout async signalsThe signal class will automatically await async receivers. If your receiver
is sync it will be executed normally.About the senderThe sender is the object that sends the signal. It can be anything. It is
passed to the receiver as the first argument. This is useful if you want to
have multiple signals in your application and you want to know which signal
was sent. Normally the sender is the object that triggers the signal.You may also pass the sender when connecting a receiver. This is useful if
you want to connect a receiver to a specific sender. If you do this the
receiver will only be called when the sender is the same as the one you
passed when connecting the receiver.Note:I normally tend to use Pydantic models as the sender in FastAPI. But
feel free to use whatever you want.my_signal.connect(my_handler,sender="sender")# This will not call the receiverawaitmy_signal.send("other_sender")Using the receiver decoratorYou can also use thereceiverdecorator to connect a receiver to a signal.@receiver(my_signal)asyncdefmy_handler(sender,**kwargs):print("Signal received!")Or if you want to limit the receiver to a specific sender.@receiver(my_signal,sender="sender")asyncdefmy_handler(sender,**kwargs):print("Signal received!")Handle exceptionsBy default the signal class will raise exceptions raised by receivers. If
you want the signal to catch the exceptions and continue to call the other
receivers you can usesend_robust(..)instead ofsend(). The return value
will be a list of tuples containing the receiver and the return or the
exception raised by the receiver. You will need to check the type of the
return value to see if it is an exception or not.awaitmy_signal.send_robust("sender")ContributingIf you want to contribute to this project, feel free to just fork the project,
create a dev branch in your fork and then create a pull request (PR). If you
are unsure about whether your changes really suit the project please create an
issue first, to talk about this.