{"id":13486625,"url":"https://github.com/konradhalas/buslane","last_synced_at":"2025-07-18T15:35:03.396Z","repository":{"id":52703957,"uuid":"149031960","full_name":"konradhalas/buslane","owner":"konradhalas","description":"Simple message (event/command) bus.","archived":false,"fork":false,"pushed_at":"2021-04-20T17:47:55.000Z","size":15,"stargazers_count":30,"open_issues_count":3,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-27T10:08:36.097Z","etag":null,"topics":["bus","commands","events"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/konradhalas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-16T20:12:25.000Z","updated_at":"2023-10-07T14:08:42.000Z","dependencies_parsed_at":"2022-09-26T17:11:27.901Z","dependency_job_id":null,"html_url":"https://github.com/konradhalas/buslane","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konradhalas%2Fbuslane","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konradhalas%2Fbuslane/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konradhalas%2Fbuslane/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/konradhalas%2Fbuslane/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/konradhalas","download_url":"https://codeload.github.com/konradhalas/buslane/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248845938,"owners_count":21170867,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bus","commands","events"],"created_at":"2024-07-31T18:00:49.239Z","updated_at":"2025-04-14T08:32:11.493Z","avatar_url":"https://github.com/konradhalas.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# buslane\n\n[![Build Status](https://travis-ci.org/konradhalas/buslane.svg?branch=master)](https://travis-ci.org/konradhalas/buslane)\n[![License](https://img.shields.io/pypi/l/buslane.svg)](https://pypi.python.org/pypi/buslane/)\n[![Version](https://img.shields.io/pypi/v/buslane.svg)](https://pypi.python.org/pypi/buslane/)\n[![Python versions](https://img.shields.io/pypi/pyversions/buslane.svg)](https://pypi.python.org/pypi/buslane/)\n\nSimple message (event/command) bus.\n\n## Installation\n\nTo install `buslane`, simply use `pip` (or `pipenv`):\n\n```\n$ pip install buslane\n```\n\n## Requirements\n\nMinimum Python version supported by `buslane` is 3.6.\n\n## Quick start\n\n```python\nfrom dataclasses import dataclass\n\nfrom buslane.commands import Command, CommandHandler, CommandBus\n\n\n@dataclass(frozen=True)\nclass RegisterUserCommand(Command):\n    email: str\n    password: str\n\n\nclass RegisterUserCommandHandler(CommandHandler[RegisterUserCommand]):\n\n    def handle(self, command: RegisterUserCommand) -\u003e None:\n        assert command == RegisterUserCommand(\n            email='john@lennon.com',\n            password='secret',\n        )\n\n\ncommand_bus = CommandBus()\ncommand_bus.register(handler=RegisterUserCommandHandler())\ncommand_bus.execute(command=RegisterUserCommand(\n    email='john@lennon.com',\n    password='secret',\n))\n```\n\n## About\n\nThis library makes it easier to create solutions based on messages. If you want to split event occurrence from its\nhandling, `buslane` is the way to go. It supports commands (single handler) and events (0 or multiple handlers)\napproach.\n\n## Motivation\n\nThis package could be probably replaced with a simple Python dictionary with messages classes as keys and ordinary\nfunctions as values. Python is a dynamic language and we can implement such solution very easy, without any classes,\ninheritance, methods overriding and so one. So why you should use `buslane`? Is it the *pythonic* approach?\n\nFirst of all, `buslane` is very simple and tiny project. I was copying these few lines from project to project, so now I\ndon't have to.\n\nSecondly, I'm a huge fan of type annotations. This a game changer in a project with a huge codebase. `buslane` has\ntype hints everywhere and it is based on [Python generics][python-generics]. In combination with such tools like\n[`mypy`][mypy] you will be sure that you are doing (from types point of view) everything OK.\n\nMessage handler is a class instead of function, because you can easily inject your dependencies via `__init__`\nparameters. Such class is very easy to test, you don't have to `mock.patch` everything. The interface is clear.\n\nLast but not least - the `buslane` API is simple and well defined. You can extend it easily, e.g. log all of your\nmessages or store them in a database.\n\nIt can be used as a foundation of your CQRS-based system.\n\n## Reference\n\n`buslane` uses Python type annotations to properly register handler. To create your message you have to inherit from\n`Event` or `Command` class. I recommend to use `dataclasses` module from Python 3.7 (or from PyPI) - command/event\nshould be just a simple bundle of immutable data, `dataclass` decorator makes it easy to create such class.\n\nHandler should inherit from `EventHandler[T]` or `CommandHandler[T]`, where `T` is a class of your message.\n\n### Events\n\nYou can register multiple or none handlers for a single event.\n\nClasses:\n\n- `Event`\n- `EventHandler[Event]`\n- `EventBus`\n\nExceptions:\n\n- `WrongHandlerException`\n\n#### Example\n\n```python\nfrom buslane.events import Event, EventHandler, EventBus\n\n\nclass SampleEvent(Event):\n    pass\n\n\nclass SampleEventHandler(EventHandler[SampleEvent]):\n\n    def handle(self, event: SampleEvent) -\u003e None:\n        pass\n\n\nevent_bus = EventBus()\nevent_bus.register(handler=SampleEventHandler())\nevent_bus.publish(event=SampleEvent())\n```\n\n### Commands\n\nYou have to register only single handler for the given command.\n\nClasses:\n\n- `Command`\n- `CommandHandler[Command]`\n- `CommandBus`\n\nExceptions:\n\n- `MissingCommandHandlerException`\n- `CommandHandlerRegisteredException`\n- `WrongHandlerException`\n\n#### Example\n\n```python\nfrom buslane.commands import Command, CommandHandler, CommandBus\n\n\nclass SampleCommand(Command):\n    pass\n\n\nclass SampleCommandHandler(CommandHandler[SampleCommand]):\n\n    def handle(self, command: SampleCommand) -\u003e None:\n        pass\n\n\ncommand_bus = CommandBus()\ncommand_bus.register(handler=SampleCommandHandler())\ncommand_bus.execute(command=SampleCommand())\n```\n\n### Customizations\n\nIf you want to customize behaviour of your bus, you can override `handle` method from `EventBus` / `CommandBus` class.\n\nThe following example shows a bus which logs every event and process it in a thread.\n\n```python\nimport logging\nfrom concurrent.futures import ThreadPoolExecutor\n\n\nclass CustomEventBus(EventBus):\n\n    def __init__(self, workers: int) -\u003e None:\n        super().__init__()\n        self.logger = logging.getLogger()\n        self.executor = ThreadPoolExecutor(max_workers=workers)\n\n    def handle(self, event: Event, handler: EventHandler) -\u003e None:\n        self.logger.info(f'Handling event {event} by {handler}')\n        self.executor.submit(handler.handle, event)\n```\n\n**Note**: This type of customization will be deprecated in the next release in favor of a plugins architecture.\n\n## Authors\n\nCreated by [Konrad Hałas][halas-homepage].\n\n[halas-homepage]: https://konradhalas.pl\n[python-generics]: https://docs.python.org/3/library/typing.html#generics\n[mypy]: https://github.com/python/mypy/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkonradhalas%2Fbuslane","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkonradhalas%2Fbuslane","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkonradhalas%2Fbuslane/lists"}