didiator


Namedidiator JSON
Version 0.3.0 PyPI version JSON
download
home_pagehttps://github.com/SamWarden/didiator
SummaryA library that implements the Mediator pattern and uses DI library
upload_time2023-01-30 23:15:36
maintainerSamWarden
docs_urlNone
authorSamWarden
requires_python>=3.10,<4.0
licenseMIT
keywords didiator mediatr mediator cqrs di events ioc
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ========
Didiator
========

``didiator`` is an asynchronous library that implements the Mediator pattern and
uses the `DI <https://www.adriangb.com/di/>`_ library to help you to inject dependencies to called handlers

This library is inspired by the `MediatR <https://github.com/jbogard/MediatR>`_ used in C#,
follows CQRS principles and implements event publishing

Installation
============

Didiator is available on pypi: https://pypi.org/project/didiator

.. code-block:: bash

    pip install -U "didiator[di]"

It will install ``didiator`` with its optional DI dependency that is necessary to use ``DiMiddleware`` and ``DiBuilderImpl``

Examples
========

You can find more examples in `this folder <https://github.com/SamWarden/didiator/tree/dev/examples>`_

Create Commands and Queries with handlers for them
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    @dataclass
    class CreateUser(Command[int]):
        user_id: int
        username: str

    class CreateUserHandler(CommandHandler[CreateUser, int]):
        def __init__(self, user_repo: UserRepo) -> None:
            self._user_repo = user_repo

        async def __call__(self, command: CreateUser) -> int:
            user = User(id=command.user_id, username=command.username)
            await self._user_repo.add_user(user)
            await self._user_repo.commit()
            return user.id

You can use functions as handlers

.. code-block:: python

    @dataclass
    class GetUserById(Query[User]):
        user_id: int

    async def handle_get_user_by_id(query: GetUserById, user_repo: UserRepo) -> User:
        user = await user_repo.get_user_by_id(query.user_id)
        return user

Create DiBuilder
~~~~~~~~~~~~~~~~

``DiBuilderImpl`` is a facade for Container from DI with caching of `solving <https://www.adriangb.com/di/0.73.0/solving/>`_

``di_scopes`` is a list with the order of `scopes <https://www.adriangb.com/di/0.73.0/scopes/>`_

``di_builder.bind(...)`` will `bind <https://www.adriangb.com/di/0.73.0/binds/>`_ ``UserRepoImpl`` type to ``UserRepo`` protocol

.. code-block:: python

    di_scopes = ["request"]
    di_builder = DiBuilderImpl(Container(), AsyncExecutor(), di_scopes)
    di_builder.bind(bind_by_type(Dependent(UserRepoImpl, scope="request"), UserRepo))

Create Mediator
~~~~~~~~~~~~~~~

Create dispatchers with their middlewares and use them to initialize the ``MediatorImpl``

``cls_scope`` is a scope that will be used to bind class Command/Query handlers initialized during request handling

.. code-block:: python

    middlewares = (LoggingMiddleware(), DiMiddleware(di_builder, scopes=DiScopes("request")))
    command_dispatcher = CommandDispatcherImpl(middlewares=middlewares)
    query_dispatcher = QueryDispatcherImpl(middlewares=middlewares)

    mediator = MediatorImpl(command_dispatcher, query_dispatcher)

Register handlers
~~~~~~~~~~~~~~~~~

.. code-block:: python

    # CreateUserHandler is not initialized during registration
    mediator.register_command_handler(CreateUser, CreateUserHandler)
    mediator.register_query_handler(GetUserById, handle_get_user_by_id)

Main usage
~~~~~~~~~~

Enter the ``"request"`` scope that was registered earlier and create a new Mediator with ``di_state`` bound

Use ``mediator.send(...)`` for commands and ``mediator.query(...)`` for queries

.. code-block:: python

        async with di_builder.enter_scope("request") as di_state:
            scoped_mediator = mediator.bind(di_state=di_state)

            # It will call CreateUserHandler(UserRepoImpl()).__call__(command)
            # UserRepoImpl() created and injected automatically
            user_id = await scoped_mediator.send(CreateUser(1, "Jon"))

            # It will call handle_get_user_by_id(query, user_repo)
            # UserRepoImpl created earlier will be reused in this scope
            user = await scoped_mediator.query(GetUserById(user_id))
            print("User:",  user)
        # Session of UserRepoImpl will be closed after exiting the "request" scope

Events publishing
~~~~~~~~~~~~~~~~~

You can register and publish events using ``Mediator`` and its ``EventObserver``.
Unlike dispatchers, ``EventObserver`` publishes events to multiple event handlers subscribed to it
and doesn't return their result.
All middlewares also work with ``EventObserver``, as in in the case with Dispatchers.

Define event and its handlers
-----------------------------

.. code-block:: python

    class UserCreated(Event):
        user_id: int
        username: str

    async def on_user_created1(event: UserCreated, logger: Logger) -> None:
        logger.info("User created1: id=%s,  username=%s", event.user_id, event.username)

    async def on_user_created2(event: UserCreated, logger: Logger) -> None:
        logger.info("User created2: id=%s,  username=%s", event.user_id, event.username)

Create EventObserver and use it for Mediator
--------------------------------------------

.. code-block:: python

    middlewares = (LoggingMiddleware(), DiMiddleware(di_builder, scopes=DiScopes("request")))
    event_observer = EventObserver(middlewares=middlewares)

    mediator = MediatorImpl(command_dispatcher, query_dispatcher, event_observer)

Register event handlers
-----------------------

You can register multiple event handlers for one event

.. code-block:: python

    mediator.register_event_handler(UserCreated, on_user_created1)
    mediator.register_event_handler(UserCreated, on_user_created2)

Publish event
-------------

Event handlers will be executed sequentially

.. code-block:: python

    await mediator.publish(UserCreated(1, "Jon"))
    # User created1: id=1,  username="Jon"
    # User created2: id=1,  username="Jon"

    await mediator.publish([UserCreated(2, "Sam"), UserCreated(3, "Nick")])
    # User created1: id=2,  username="Sam"
    # User created2: id=2,  username="Sam"
    # User created1: id=3,  username="Nick"
    # User created2: id=3,  username="Nick"

⚠️ **Attention: this is a beta version of** ``didiator`` **that depends on** ``DI``, **which is also in beta. Both of them can change their API!**

CQRS
====

CQRS stands for "`Command Query Responsibility Segregation <https://www.martinfowler.com/bliki/CQRS.html>`_".
Its idea about splitting the responsibility of commands (writing) and queries (reading) into different models.

``didiator`` have segregated ``.send(command)``, ``.query(query)`` and ``.publish(events)`` methods in its ``Mediator`` and
assumes that you will separate its handlers.
Use ``CommandMediator``, ``QueryMediator`` and ``EventMediator`` protocols to explicitly define which method you need in ``YourController``

.. code-block:: mermaid

    graph LR;
        YourController-- Query -->Mediator;
        YourController-- Command -->Mediator;
        Mediator-. Query .->QueryDispatcher-.->di2[DiMiddleware]-.->QueryHandler;
        Mediator-. Command .->CommandDispatcher-.->di1[DiMiddleware]-.->CommandHandler;
        CommandHandler-- Event -->Mediator;
        Mediator-. Event .->EventObserver-.->di3[DiMiddleware]-.->EventHandler1;
        EventObserver-.->di4[DiMiddleware]-.->EventHandler2;

``DiMiddleware`` initializes handlers and injects dependencies for them, you can just send a command with the data you need

Why ``didiator``?
=================

- Easy dependency injection to your business logic
- Separating dependencies from your controllers. They can just parse external requests and interact with the ``Mediator``
- CQRS
- Event publishing
- Flexible configuration
- Middlewares support

Why not?
========

- You don't need it
- Maybe too low coupling: navigation becomes more difficult
- Didiator is in beta now
- No support for synchronous handlers


            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/SamWarden/didiator",
    "name": "didiator",
    "maintainer": "SamWarden",
    "docs_url": null,
    "requires_python": ">=3.10,<4.0",
    "maintainer_email": "SamWardenSad@gmail.com",
    "keywords": "didiator,mediatr,mediator,CQRS,DI,events,ioc",
    "author": "SamWarden",
    "author_email": "SamWardenSad@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/20/d7/8f577ae09ed6ee6b5df6cb669e34d7cc848c80a7a2045b3e9f1921c80ded/didiator-0.3.0.tar.gz",
    "platform": null,
    "description": "========\nDidiator\n========\n\n``didiator`` is an asynchronous library that implements the Mediator pattern and\nuses the `DI <https://www.adriangb.com/di/>`_ library to help you to inject dependencies to called handlers\n\nThis library is inspired by the `MediatR <https://github.com/jbogard/MediatR>`_ used in C#,\nfollows CQRS principles and implements event publishing\n\nInstallation\n============\n\nDidiator is available on pypi: https://pypi.org/project/didiator\n\n.. code-block:: bash\n\n    pip install -U \"didiator[di]\"\n\nIt will install ``didiator`` with its optional DI dependency that is necessary to use ``DiMiddleware`` and ``DiBuilderImpl``\n\nExamples\n========\n\nYou can find more examples in `this folder <https://github.com/SamWarden/didiator/tree/dev/examples>`_\n\nCreate Commands and Queries with handlers for them\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n    @dataclass\n    class CreateUser(Command[int]):\n        user_id: int\n        username: str\n\n    class CreateUserHandler(CommandHandler[CreateUser, int]):\n        def __init__(self, user_repo: UserRepo) -> None:\n            self._user_repo = user_repo\n\n        async def __call__(self, command: CreateUser) -> int:\n            user = User(id=command.user_id, username=command.username)\n            await self._user_repo.add_user(user)\n            await self._user_repo.commit()\n            return user.id\n\nYou can use functions as handlers\n\n.. code-block:: python\n\n    @dataclass\n    class GetUserById(Query[User]):\n        user_id: int\n\n    async def handle_get_user_by_id(query: GetUserById, user_repo: UserRepo) -> User:\n        user = await user_repo.get_user_by_id(query.user_id)\n        return user\n\nCreate DiBuilder\n~~~~~~~~~~~~~~~~\n\n``DiBuilderImpl`` is a facade for Container from DI with caching of `solving <https://www.adriangb.com/di/0.73.0/solving/>`_\n\n``di_scopes`` is a list with the order of `scopes <https://www.adriangb.com/di/0.73.0/scopes/>`_\n\n``di_builder.bind(...)`` will `bind <https://www.adriangb.com/di/0.73.0/binds/>`_ ``UserRepoImpl`` type to ``UserRepo`` protocol\n\n.. code-block:: python\n\n    di_scopes = [\"request\"]\n    di_builder = DiBuilderImpl(Container(), AsyncExecutor(), di_scopes)\n    di_builder.bind(bind_by_type(Dependent(UserRepoImpl, scope=\"request\"), UserRepo))\n\nCreate Mediator\n~~~~~~~~~~~~~~~\n\nCreate dispatchers with their middlewares and use them to initialize the ``MediatorImpl``\n\n``cls_scope`` is a scope that will be used to bind class Command/Query handlers initialized during request handling\n\n.. code-block:: python\n\n    middlewares = (LoggingMiddleware(), DiMiddleware(di_builder, scopes=DiScopes(\"request\")))\n    command_dispatcher = CommandDispatcherImpl(middlewares=middlewares)\n    query_dispatcher = QueryDispatcherImpl(middlewares=middlewares)\n\n    mediator = MediatorImpl(command_dispatcher, query_dispatcher)\n\nRegister handlers\n~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n    # CreateUserHandler is not initialized during registration\n    mediator.register_command_handler(CreateUser, CreateUserHandler)\n    mediator.register_query_handler(GetUserById, handle_get_user_by_id)\n\nMain usage\n~~~~~~~~~~\n\nEnter the ``\"request\"`` scope that was registered earlier and create a new Mediator with ``di_state`` bound\n\nUse ``mediator.send(...)`` for commands and ``mediator.query(...)`` for queries\n\n.. code-block:: python\n\n        async with di_builder.enter_scope(\"request\") as di_state:\n            scoped_mediator = mediator.bind(di_state=di_state)\n\n            # It will call CreateUserHandler(UserRepoImpl()).__call__(command)\n            # UserRepoImpl() created and injected automatically\n            user_id = await scoped_mediator.send(CreateUser(1, \"Jon\"))\n\n            # It will call handle_get_user_by_id(query, user_repo)\n            # UserRepoImpl created earlier will be reused in this scope\n            user = await scoped_mediator.query(GetUserById(user_id))\n            print(\"User:\",  user)\n        # Session of UserRepoImpl will be closed after exiting the \"request\" scope\n\nEvents publishing\n~~~~~~~~~~~~~~~~~\n\nYou can register and publish events using ``Mediator`` and its ``EventObserver``.\nUnlike dispatchers, ``EventObserver`` publishes events to multiple event handlers subscribed to it\nand doesn't return their result.\nAll middlewares also work with ``EventObserver``, as in in the case with Dispatchers.\n\nDefine event and its handlers\n-----------------------------\n\n.. code-block:: python\n\n    class UserCreated(Event):\n        user_id: int\n        username: str\n\n    async def on_user_created1(event: UserCreated, logger: Logger) -> None:\n        logger.info(\"User created1: id=%s,  username=%s\", event.user_id, event.username)\n\n    async def on_user_created2(event: UserCreated, logger: Logger) -> None:\n        logger.info(\"User created2: id=%s,  username=%s\", event.user_id, event.username)\n\nCreate EventObserver and use it for Mediator\n--------------------------------------------\n\n.. code-block:: python\n\n    middlewares = (LoggingMiddleware(), DiMiddleware(di_builder, scopes=DiScopes(\"request\")))\n    event_observer = EventObserver(middlewares=middlewares)\n\n    mediator = MediatorImpl(command_dispatcher, query_dispatcher, event_observer)\n\nRegister event handlers\n-----------------------\n\nYou can register multiple event handlers for one event\n\n.. code-block:: python\n\n    mediator.register_event_handler(UserCreated, on_user_created1)\n    mediator.register_event_handler(UserCreated, on_user_created2)\n\nPublish event\n-------------\n\nEvent handlers will be executed sequentially\n\n.. code-block:: python\n\n    await mediator.publish(UserCreated(1, \"Jon\"))\n    # User created1: id=1,  username=\"Jon\"\n    # User created2: id=1,  username=\"Jon\"\n\n    await mediator.publish([UserCreated(2, \"Sam\"), UserCreated(3, \"Nick\")])\n    # User created1: id=2,  username=\"Sam\"\n    # User created2: id=2,  username=\"Sam\"\n    # User created1: id=3,  username=\"Nick\"\n    # User created2: id=3,  username=\"Nick\"\n\n\u26a0\ufe0f **Attention: this is a beta version of** ``didiator`` **that depends on** ``DI``, **which is also in beta. Both of them can change their API!**\n\nCQRS\n====\n\nCQRS stands for \"`Command Query Responsibility Segregation <https://www.martinfowler.com/bliki/CQRS.html>`_\".\nIts idea about splitting the responsibility of commands (writing) and queries (reading) into different models.\n\n``didiator`` have segregated ``.send(command)``, ``.query(query)`` and ``.publish(events)`` methods in its ``Mediator`` and\nassumes that you will separate its handlers.\nUse ``CommandMediator``, ``QueryMediator`` and ``EventMediator`` protocols to explicitly define which method you need in ``YourController``\n\n.. code-block:: mermaid\n\n    graph LR;\n        YourController-- Query -->Mediator;\n        YourController-- Command -->Mediator;\n        Mediator-. Query .->QueryDispatcher-.->di2[DiMiddleware]-.->QueryHandler;\n        Mediator-. Command .->CommandDispatcher-.->di1[DiMiddleware]-.->CommandHandler;\n        CommandHandler-- Event -->Mediator;\n        Mediator-. Event .->EventObserver-.->di3[DiMiddleware]-.->EventHandler1;\n        EventObserver-.->di4[DiMiddleware]-.->EventHandler2;\n\n``DiMiddleware`` initializes handlers and injects dependencies for them, you can just send a command with the data you need\n\nWhy ``didiator``?\n=================\n\n- Easy dependency injection to your business logic\n- Separating dependencies from your controllers. They can just parse external requests and interact with the ``Mediator``\n- CQRS\n- Event publishing\n- Flexible configuration\n- Middlewares support\n\nWhy not?\n========\n\n- You don't need it\n- Maybe too low coupling: navigation becomes more difficult\n- Didiator is in beta now\n- No support for synchronous handlers\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A library that implements the Mediator pattern and uses DI library",
    "version": "0.3.0",
    "split_keywords": [
        "didiator",
        "mediatr",
        "mediator",
        "cqrs",
        "di",
        "events",
        "ioc"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "593a5787e2ec6ad831d0ac80ffc0f97b3c73b627dcd2c62455d4a5deb992a634",
                "md5": "2cad907ec61b12dd9fa0edd20cc98414",
                "sha256": "6cf5af01023e0e5cac45460e02769bb6ad64e1332df346aaefb46686a29297b0"
            },
            "downloads": -1,
            "filename": "didiator-0.3.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2cad907ec61b12dd9fa0edd20cc98414",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10,<4.0",
            "size": 22056,
            "upload_time": "2023-01-30T23:15:34",
            "upload_time_iso_8601": "2023-01-30T23:15:34.811532Z",
            "url": "https://files.pythonhosted.org/packages/59/3a/5787e2ec6ad831d0ac80ffc0f97b3c73b627dcd2c62455d4a5deb992a634/didiator-0.3.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "20d78f577ae09ed6ee6b5df6cb669e34d7cc848c80a7a2045b3e9f1921c80ded",
                "md5": "95f3d798e29eecd82b65dd13fb33d89d",
                "sha256": "69cdbdfdabbe38d02bfbf93abd739098e0bab0f83bf8bc92973800fdc436b78d"
            },
            "downloads": -1,
            "filename": "didiator-0.3.0.tar.gz",
            "has_sig": false,
            "md5_digest": "95f3d798e29eecd82b65dd13fb33d89d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10,<4.0",
            "size": 14207,
            "upload_time": "2023-01-30T23:15:36",
            "upload_time_iso_8601": "2023-01-30T23:15:36.810661Z",
            "url": "https://files.pythonhosted.org/packages/20/d7/8f577ae09ed6ee6b5df6cb669e34d7cc848c80a7a2045b3e9f1921c80ded/didiator-0.3.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-01-30 23:15:36",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": "SamWarden",
    "github_project": "didiator",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "didiator"
}
        
Elapsed time: 0.05314s