Package: asyncio-signal-bus
Asyncio Signal BusAsyncio Signal Bus is a simple utility for sending publisher subscriber
signals from one python function to another, thus allowing you to simplify asynchronous
applications using event based architecture.InstallationInstall with pip.pipinstallasyncio-signal-busUsageSimply decorate your publishers and subscribers to pass any data type asynchronously
whenever the publisher returns a value.importasynciofromasyncio_signal_busimportSignalBusimportjsonfromtypingimportDictBUS=SignalBus()@BUS.publisher(topic_name="foo")asyncdeffoo_publisher(arg:str):signal={"message":arg}print(f"publishing{json.dumps(signal)}")awaitasyncio.sleep(1)returnsignal@BUS.subscriber(topic_name="foo")asyncdeffoo_subscriber_0(signal:Dict):print(f"foo subscriber 0 received{json.dumps(signal)}")@BUS.subscriber(topic_name="foo")asyncdeffoo_subscriber_1(signal:Dict):print(f"foo subscriber 1 received{json.dumps(signal)}")In order to start the subscribers listening, you use the bus context. When the bus
leaves context, all subscribers stop listening.asyncdefmain():inputs=[f"message:{i}"foriinrange(10)]asyncwithBUS:awaitasyncio.gather(*[foo_publisher(x)forxininputs])That is it! You can string together publishers and subscribers in whatever way you want.
You can also annotate functions and methods used for other purposes as publishers in
order to facilitate post processing and reporting hooks without having to pass them
to the function.Dependency InjectionSubscribers often need additional data beyond the signal, such as URL's, secrets,
usernames and connection pools. The inject decorator can be used to inject additional
data to other arguments as kwargs defaults. Injection only occors when the bus or it's
injector is within context.importasynciofromasyncio_signal_busimportSignalBusBUS=SignalBus()@BUS.publisher(topic_name="greeting")asyncdefgenerate_greeting(arg:str):returnargasyncdefname_factory():return"Frank"@BUS.subscriber(topic_name="greeting")@BUS.inject("name",name_factory)asyncdefprint_greeting(greeting:str,name:str):print(f"{greeting}from{name}")asyncdefmain():asyncwithBUS:awaitgenerate_greeting("hello")asyncio.run(main())Periodic TasksSometimes we need to run tasks periodically such as polling an external database or an
internal queue. We also want to make sure that when our application shuts down, it
gracefully tells the periodic tasks to stop starting new tasks but it waits for the
existing task to complete.importasynciofromasyncio_signal_busimportSignalBusBUS=SignalBus()@BUS.periodic_task(period_seconds=0.5)asyncdefprint_foos():print("foo")asyncdefmain():asyncwithBUS:awaitasyncio.sleep(2)asyncio.run(main())Period Tasks As PublishersGiven that the periodic task is the furthest upstream in a process, we may use it to
drive downstream subscribers. We can combine the periodic task with the injectors and
the publisher to periodically fetch data using configuration information and send it
to subscribers for processing.importasynciofromtypingimportList,Dictfromasyncio_signal_busimportSignalBusimportosBUS=SignalBus()asyncdefget_url():returnos.environ.get("URL")asyncdefget_secret():returnos.environ.get("SECRET")@BUS.periodic_task(period_seconds=1)@BUS.publisher(topic_name="new-data")@BUS.inject("url",get_url)@BUS.inject("secret",get_secret)asyncdefget_data(url:str,secret:str):# Perform some sort of IO here to get your data.return[{"id":0,"value":"cats"},{"id":1,"value":"dogs"}]@BUS.subscriber(topic_name="new-data")asyncdefprocess_values(data:List[Dict]):forrowindata:print(row["value"].upper())@BUS.subscriber(topic_name="new-data")asyncdefprocess_ids(data:List[Dict]):forrowindata:print(row["id"]+1)asyncdefmain():asyncwithBUS:awaitasyncio.sleep(5)asyncio.run(main())