{"id":13458946,"url":"https://github.com/Finistere/antidote","last_synced_at":"2025-03-24T16:31:13.796Z","repository":{"id":38751967,"uuid":"106470439","full_name":"Finistere/antidote","owner":"Finistere","description":"Dependency injection for Python","archived":true,"fork":false,"pushed_at":"2022-12-19T13:00:55.000Z","size":1525,"stargazers_count":89,"open_issues_count":2,"forks_count":9,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-02T10:16:58.421Z","etag":null,"topics":["dependency","dependency-injection","injection","python"],"latest_commit_sha":null,"homepage":"","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/Finistere.png","metadata":{"files":{"readme":"README.rst","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":"2017-10-10T20:56:56.000Z","updated_at":"2024-12-09T12:45:43.000Z","dependencies_parsed_at":"2023-01-29T21:30:39.024Z","dependency_job_id":null,"html_url":"https://github.com/Finistere/antidote","commit_stats":null,"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Finistere%2Fantidote","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Finistere%2Fantidote/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Finistere%2Fantidote/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Finistere%2Fantidote/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Finistere","download_url":"https://codeload.github.com/Finistere/antidote/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245308535,"owners_count":20594263,"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":["dependency","dependency-injection","injection","python"],"created_at":"2024-07-31T09:01:00.117Z","updated_at":"2025-03-24T16:31:13.404Z","avatar_url":"https://github.com/Finistere.png","language":"Python","funding_links":[],"categories":["Software","Python"],"sub_categories":["Archived or unmaintained DI frameworks","Some projects with more that 1 000 lines of Cython code"],"readme":"########\nAntidote\n########\n\n.. image:: https://img.shields.io/pypi/v/antidote.svg\n  :target: https://pypi.python.org/pypi/antidote\n\n.. image:: https://img.shields.io/pypi/l/antidote.svg\n  :target: https://pypi.python.org/pypi/antidote\n\n.. image:: https://img.shields.io/pypi/pyversions/antidote.svg\n  :target: https://pypi.python.org/pypi/antidote\n\n.. image:: https://github.com/Finistere/antidote/actions/workflows/main.yml/badge.svg?branch=master\n  :target: https://github.com/Finistere/antidote/actions/workflows/main.yml\n\n.. image:: https://codecov.io/gh/Finistere/antidote/branch/master/graph/badge.svg\n  :target: https://codecov.io/gh/Finistere/antidote\n\n.. image:: https://readthedocs.org/projects/antidote/badge/?version=latest\n  :target: http://antidote.readthedocs.io/en/latest/?badge=latest\n\n\nAntidote is a dependency injection micro-framework for Python 3.7+, featuring:\n\n- Strong focus on typing and putting type hints to work\n- Scalable from small/simple usage to very right \"framework frameworks\"\n\nIt is built on the idea of having a **declarative**, **explicit** and **decentralized** definition of dependencies at the type / function / variable definition.\nThese definitions can be easily tracked down, including by static tooling and startup-time analysis.\n\nFeatures are built with a strong focus on **maintainability**, **simplicity** and **ease of use** in mind. Everything is statically typed (mypy \u0026 pyright), documented with tested examples, and can be easily used in existing code and tested in isolation.\n\n\n************\nInstallation\n************\n\n\nTo install Antidote, simply run this command:\n\n.. code-block:: bash\n\n    pip install antidote\n\n\n\n*************\nHelp \u0026 Issues\n*************\n\n\nFeel free to open an `issue \u003chttps://github.com/Finistere/antidote/issues\u003e`_ or a `discussion \u003chttps://github.com/Finistere/antidote/discussions\u003e`_ on `Github \u003chttps://github.com/Finistere/antidote\u003e`_ for questions, issues, proposals, etc. !\n\n\n\n*************\nDocumentation\n*************\n\n\nTutorial, reference and more can be found in the `documentation \u003chttps://antidote.readthedocs.io/en/latest\u003e`_. Some quick links:\n\n- `Guide \u003chttps://antidote.readthedocs.io/en/latest/guide/index.html\u003e`_\n- `Reference \u003chttps://antidote.readthedocs.io/en/latest/reference/index.html\u003e`_\n- `Changelog \u003chttps://antidote.readthedocs.io/en/latest/changelog.html\u003e`_\n\n\n\n********\nOverview\n********\n\n\nAccessing dependencies\n======================\n\nAntidote works with a :code:`Catalog` which is a sort of \"collection\" of dependencies. Multiple collections can co-exist, but :code:`world` is used by default. The most common form of a dependency is an instance of a given class:\n\n.. code-block:: python\n\n    from antidote import injectable\n\n    @injectable\n    class Service:\n        pass\n\n    world[Service]  # retrieve the instance\n    world.get(Service, default='something')  # similar to a dict\n\nBy default, :code:`@injectable` defines a singleton. However, alternative lifetimes (how long the :code:`world` keeps value alive in its cache) can exist, such as :code:`transient`, where nothing is cached at all.\n\nDependencies can also be injected into a function/method with :code:`@inject`. For both kinds of callables, Mypy, Pyright and PyCharm will infer the correct types.\n\n.. code-block:: python\n\n    from antidote import inject\n\n    @inject  #                      ⯆ Infers the dependency from the type hint\n    def f(service: Service = inject.me()) -\u003e Service:\n        return service\n\n    f()  # service injected\n    f(Service())  # useful for testing: no injection, argument is used\n\n:code:`@inject` supports a variety of ways to bind arguments to their dependencies (if any.) This binding is *always* explicit:\n\n.. code-block:: python\n\n    from antidote import InjectMe\n\n    # recommended with inject.me() for best static-typing experience\n    @inject\n    def f2(service = inject[Service]):\n        ...\n\n    @inject(kwargs={'service': Service})\n    def f3(service):\n        ...\n\n    @inject\n    def f4(service: InjectMe[Service]):\n        ...\n\nClasses can also be fully wired, with all methods injected, by using :code:`@wire`. It is also possible to\ninject the first argument, commonly named :code:`self`, of a method with an instance of a class:\n\n.. code-block:: python\n\n    @injectable\n    class Dummy:\n        @inject.method\n        def method(self) -\u003e 'Dummy':\n            return self\n\n    # behaves like a class method\n    assert Dummy.method() is world[Dummy]\n\n    # useful for testing: when accessed trough an instance, no injection\n    dummy = Dummy()\n    assert dummy.method() is dummy\n\n\n\nDefining dependencies\n======================\n\nAntidote comes out-of-the-box with 4 kinds of dependencies:\n\n-   :code:`@injectable` classes for which an instance is provided.\n\n    .. code-block:: python\n\n        from antidote import injectable\n\n        #           ⯆ optional: would just call Service() otherwise.\n        @injectable(factory_method='load')\n        class Service:\n            @classmethod\n            def load(cls) -\u003e 'Service':\n                return cls()\n\n        world[Service]\n\n\n-   :code:`const` for defining simple constants.\n\n    .. code-block:: python\n\n        from antidote import const\n\n        # Used as namespace\n        class Conf:\n            TMP_DIR = const('/tmp')\n\n            # From environment variables, lazily retrieved\n            LOCATION = const.env(\"PWD\")\n            USER = const.env()  # uses the name of the variable\n            PORT = const.env(convert=int)  # convert the environment variable to a given type\n            UNKNOWN = const.env(default='unknown')\n\n        world[Conf.TMP_DIR]\n\n        @inject\n        def f(tmp_dir: str = inject[Conf.TMP_DIR]):\n            ...\n\n-   :code:`@lazy` function calls (taking into account arguments) used for (stateful-)factories, parameterized dependencies, complex constants, etc.\n\n    .. code-block:: python\n\n        from dataclasses import dataclass\n\n        from antidote import lazy\n\n        @dataclass\n        class Template:\n            name: str\n\n        # the wrapped template function is only executed when accessed through world/@inject\n        @lazy\n        def template(name: str) -\u003e Template:\n            return Template(name=name)\n\n        # By default a singleton, so it always returns the same instance of Template\n        world[template(name=\"main\")]\n\n        @inject\n        def f(main_template: Template = inject[template(name=\"main\")]):\n            ...\n\n    :code:`@lazy` will automatically apply :code:`@inject` and can also be a value, property or even a method similarly to :code:`@inject.method`.\n\n-   :code:`@interface` for which one or multiple implementations can be provided.\n\n    .. code-block:: python\n\n        from antidote import interface, implements\n\n        @interface\n        class Task:\n            pass\n\n        @implements(Task)\n        class CustomTask(Task):\n            pass\n\n        world[Task]  # instance of CustomTask\n\n    The interface does not need to be a class. It can also be a :code:`Protocol`, a function or a :code:`@lazy` function call!\n\n    .. code-block:: python\n\n        @interface\n        def callback(event: str) -\u003e bool:\n            ...\n\n        @implements(callback)\n        def on_event(event: str) -\u003e bool:\n            # do stuff\n            return True\n\n        # returns the on_event function\n        assert world[callback] is on_event\n\n    :code:`@implements` will enforce as much as possible that the interface is correctly implemented. Multiple implementations can also be retrieved. Conditions, filters on metadata and weighting implementation are all supported to allow full customization of which implementation should be retrieved in which use case.\n\nEach of those have several knobs to adapt them to your needs which are covered in the documentation.\n\n\nTesting \u0026 Debugging\n===================\n\nInjected functions can typically be tested by passing arguments explicitly but it's not always enough. Antidote provides a test context for full test isolation. The test context allows overriding any dependencies:\n\n.. code-block:: python\n\n    original = world[Service]\n    with world.test.clone() as overrides:\n        # dependency value is different, but it's still a singleton Service instance\n        assert world[Service] is not original\n\n        # override examples\n        overrides[Service] = 'x'\n        assert world[Service] == 'x'\n\n        del overrides[Service]\n        assert world.get(Service) is None\n\n        @overrides.factory(Service)\n        def build_service() -\u003e object:\n            return 'z'\n\n\n        # Test context can be nested and it wouldn't impact the current test context\n        with world.test.clone() as nested_overrides:\n            ...\n\n    # Outside the test context, nothing changed.\n    assert world[Service] is original\n\n\nAntidote also provides introspection capabilities with :code:`world.debug`  which returns a nicely-formatted tree to show what Antidote actually sees, without actually executing anything:\n\n.. code-block:: text\n\n    🟉 \u003clazy\u003e f()\n    └── ∅ Service\n        └── Service.__init__\n            └── 🟉 \u003cconst\u003e Conf.HOST\n\n     ∅ = transient\n     ↻ = bound\n     🟉 = singleton\n\n\nGoing Further\n=============\n\n- Scopes are supported. Defining a :code:`ScopeGlobalVar` and using it as a dependency will force any dependents to be updated whenever it changes (a request for example).\n- Multiple catalogs can be used which lets you expose only a subset of your API (dependencies) to your consumer within a catalog.\n- You can easily define your kind of dependencies with proper typing from both :code:`world` and :code:`inject`. :code:`@injectable`, :code:`@lazy`, :code:`inject.me()` etc.. all rely on Antidote's core (:code:`Provider`, :code:`Dependency`, etc.) which is part of the public API.\n\nCheck out the `Guide \u003chttps://antidote.readthedocs.io/en/latest/guide/index.html\u003e`_ which goes more in depth or the `Reference \u003chttps://antidote.readthedocs.io/en/latest/reference/index.html\u003e`_ for specific features.\n\n*****************\nHow to Contribute\n*****************\n\n\n1. Check for open issues or open a fresh issue to start a discussion around a feature or a bug.\n2. Fork the repo on GitHub. Run the tests to confirm they all pass on your  machine. If you cannot find why it fails, open an issue.\n3. Start making your changes to the master branch.\n4. Send a pull request.\n\n*Be sure to merge the latest from \"upstream\" before making a pull request!*\n\nIf you have any issue during development or just want some feedback, don't hesitate to open a pull request and ask for help ! You're also more than welcome to open a discussion or an issue on any topic!\n\nBut, no code changes will be merged if they do not pass mypy, pyright, don't have 100% test coverage or documentation with tested examples (if relevant.)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFinistere%2Fantidote","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FFinistere%2Fantidote","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFinistere%2Fantidote/lists"}