Package: buslane
buslaneSimple message (event/command) bus.InstallationTo installbuslane, simply usepip(orpipenv):$ pip install buslaneRequirementsMinimum Python version supported bybuslaneis 3.6.Quick startfromdataclassesimportdataclassfrombuslane.commandsimportCommand,CommandHandler,CommandBus@dataclassclassRegisterUserCommand(Command):email:strpassword:strclassRegisterUserCommandHandler(CommandHandler[RegisterUserCommand]):defhandle(self,command:RegisterUserCommand)->None:assertcommand==RegisterUserCommand(email='john@lennon.com',password='secret',)command_bus=CommandBus()command_bus.register(handler=RegisterUserCommandHandler())command_bus.execute(command=RegisterUserCommand(email='john@lennon.com',password='secret',))AboutThis library makes it easier to create solutions based on messages. If you want to split event occurrence from its
handling,buslaneis the way to go. It supports commands (single handler) and events (0 or multiple handlers)
approach.MotivationThis package could be probably replaced with a simple Python dictionary with messages classes as keys and ordinary
functions as values. Python is a dynamic language and we can implement such solution very easy, without any classes,
inheritance, methods overriding and so one. So why you should usebuslane? Is it thepythonicapproach?First of all,buslaneis very simple and tiny project. I was copying these few lines from project to project, so now I
don't have to.Secondly, I'm a huge fan of type annotations. This a game changer in a project with a huge codebase.buslanehas
type hints everywhere and it is based onPython generics. In combination with such tools likemypyyou will be sure that you are doing (from types point of view) everything OK.Message handler is a class instead of function, because you can easily inject your dependencies via__init__parameters. Such class is very easy to test, you don't have tomock.patcheverything. The interface is clear.Last but not least - thebuslaneAPI is simple and well defined. You can extend it easily, e.g. log all of your
messages or store them in a database.It can be used as a foundation of your CQRS-based system.Referencebuslaneuses Python type annotations to properly register handler. To create your message you have to inherit fromEventorCommandclass. Handler should inherit fromEventHandler[T]orCommandHandler[T], whereTis a class
of your message.EventsYou can register multiple or none handlers for a single event.Classes:EventEventHandler[Event]EventBusExceptions:WrongHandlerExceptionExamplefrombuslane.eventsimportEvent,EventHandler,EventBusclassSampleEvent(Event):passclassSampleEventHandler(EventHandler[SampleEvent]):defhandle(self,event:SampleEvent)->None:passevent_bus=EventBus()event_bus.register(handler=SampleEventHandler())event_bus.publish(event=SampleEvent())CommandsYou have to register only single handler for the given command.Classes:CommandCommandHandler[Command]CommandBusExceptions:MissingCommandHandlerExceptionCommandHandlerRegisteredExceptionWrongHandlerExceptionExamplefrombuslane.commandsimportCommand,CommandHandler,CommandBusclassSampleCommand(Command):passclassSampleCommandHandler(CommandHandler[SampleCommand]):defhandle(self,command:SampleCommand)->None:passcommand_bus=CommandBus()command_bus.register(handler=SampleCommandHandler())command_bus.execute(command=SampleCommand())CustomizationsIf you want to customize behaviour of your bus, you can overridehandlemethod fromEventBus/CommandBusclass.The following example shows a bus which logs every event and process it in a thread.importloggingfromconcurrent.futuresimportThreadPoolExecutorclassCustomEventBus(EventBus):def__init__(self,workers:int)->None:super().__init__()self.logger=logging.getLogger()self.executor=ThreadPoolExecutor(max_workers=workers)defhandle(self,event:Event,handler:EventHandler)->None:self.logger.info(f'Handling event{event}by{handler}')self.executor.submit(handler.handle,event)AuthorsCreated byKonrad Hałas.