{ "info": { "author": "Konrad Ha\u0142as", "author_email": "halas.konrad@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "# 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\nclass RegisterUserCommand(Command):\n email: str\n password: str\n\n\nclass RegisterUserCommandHandler(CommandHandler[RegisterUserCommand]):\n\n def handle(self, command: RegisterUserCommand) -> 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. Handler should inherit from `EventHandler[T]` or `CommandHandler[T]`, where `T` is a class\nof 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) -> 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) -> 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) -> 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) -> None:\n self.logger.info(f'Handling event {event} by {handler}')\n self.executor.submit(handler.handle, event)\n```\n\n## Authors\n\nCreated by [Konrad Ha\u0142as][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\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/konradhalas/buslane", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "buslane", "package_url": "https://pypi.org/project/buslane/", "platform": "", "project_url": "https://pypi.org/project/buslane/", "project_urls": { "Homepage": "https://github.com/konradhalas/buslane" }, "release_url": "https://pypi.org/project/buslane/0.0.5/", "requires_dist": null, "requires_python": ">=3.6", "summary": "", "version": "0.0.5" }, "last_serial": 4325380, "releases": { "0.0.2": [ { "comment_text": "", "digests": { "md5": "6b50a060696484c7ae957b377b2e1f1e", "sha256": "408b034260bf22bfa1ab6809a5f9dc9537a4c3a38735999a82612478b62e9141" }, "downloads": -1, "filename": "buslane-0.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "6b50a060696484c7ae957b377b2e1f1e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 3184, "upload_time": "2018-09-17T07:36:18", "url": "https://files.pythonhosted.org/packages/02/b2/79d9ca7e0786ab4eaff67ee8adacd44a7cc89093ce62550a7ec515f5624d/buslane-0.0.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1797419e6dd061cb8da60fc49137bb58", "sha256": "e9a454bacaa19f032db4ae9f28cb80bb515ec549941e28026c59aa89052bac81" }, "downloads": -1, "filename": "buslane-0.0.2.tar.gz", "has_sig": false, "md5_digest": "1797419e6dd061cb8da60fc49137bb58", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 2296, "upload_time": "2018-09-17T07:36:19", "url": "https://files.pythonhosted.org/packages/78/88/0c75c7584aac91288ec0ecf5bd2c46394444468feb32dd13ee1e0f9e7ddb/buslane-0.0.2.tar.gz" } ], "0.0.5": [ { "comment_text": "", "digests": { "md5": "bad96ea85f2846276be306e9d66c4f5e", "sha256": "48f23c308d319c07c09cc00ff5be768b0e3e8b077c1a01e0c12ada9b1c89fe73" }, "downloads": -1, "filename": "buslane-0.0.5-py3-none-any.whl", "has_sig": false, "md5_digest": "bad96ea85f2846276be306e9d66c4f5e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 5584, "upload_time": "2018-09-30T16:58:48", "url": "https://files.pythonhosted.org/packages/0c/bc/ee4bbb143c5dd55e32d9cef94efe6c866cef2b1acba9b73f55b9608255e3/buslane-0.0.5-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5446a1d7a215abdaad5b4cabee3d5140", "sha256": "deb5d34d41be526007717d72799631a9c75c6e108ad387571de948d10762d38a" }, "downloads": -1, "filename": "buslane-0.0.5.tar.gz", "has_sig": false, "md5_digest": "5446a1d7a215abdaad5b4cabee3d5140", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 4191, "upload_time": "2018-09-30T16:58:49", "url": "https://files.pythonhosted.org/packages/88/85/13516972d5af0ecb889c2ddc71af30074f705da371a5af880f9a873f9995/buslane-0.0.5.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "bad96ea85f2846276be306e9d66c4f5e", "sha256": "48f23c308d319c07c09cc00ff5be768b0e3e8b077c1a01e0c12ada9b1c89fe73" }, "downloads": -1, "filename": "buslane-0.0.5-py3-none-any.whl", "has_sig": false, "md5_digest": "bad96ea85f2846276be306e9d66c4f5e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 5584, "upload_time": "2018-09-30T16:58:48", "url": "https://files.pythonhosted.org/packages/0c/bc/ee4bbb143c5dd55e32d9cef94efe6c866cef2b1acba9b73f55b9608255e3/buslane-0.0.5-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5446a1d7a215abdaad5b4cabee3d5140", "sha256": "deb5d34d41be526007717d72799631a9c75c6e108ad387571de948d10762d38a" }, "downloads": -1, "filename": "buslane-0.0.5.tar.gz", "has_sig": false, "md5_digest": "5446a1d7a215abdaad5b4cabee3d5140", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 4191, "upload_time": "2018-09-30T16:58:49", "url": "https://files.pythonhosted.org/packages/88/85/13516972d5af0ecb889c2ddc71af30074f705da371a5af880f9a873f9995/buslane-0.0.5.tar.gz" } ] }