├── injector ├── py.typed └── __init__.py ├── docs ├── changelog.rst ├── _templates │ └── sidebar.html ├── api.rst ├── logging.rst ├── testing.rst ├── faq.rst ├── scopes.rst ├── index.rst ├── Makefile ├── practices.rst ├── terminology.rst └── conf.py ├── requirements.txt ├── pyproject.toml ├── mypy.ini ├── .coveragerc ├── pytest.ini ├── .gitignore ├── requirements-docs.in ├── MANIFEST.in ├── requirements-dev.in ├── .readthedocs.yaml ├── requirements-dev.txt ├── requirements-docs.txt ├── .github └── workflows │ └── ci.yml ├── COPYING ├── setup.py ├── README.md ├── CHANGES └── injector_test.py /injector/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGES 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | typing_extensions>=3.7.4;python_version<"3.9" 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 110 3 | target_version = ['py36', 'py37'] 4 | skip_string_normalization = true 5 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = true 3 | follow_imports = error 4 | warn_no_return = true 5 | warn_redundant_casts = true 6 | disallow_untyped_defs = true 7 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | exclude_also = 3 | if TYPE_CHECKING: 4 | def __repr__ 5 | @(abc\.)?abstractmethod 6 | @overload 7 | raise NotImplementedError 8 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -v --tb=native --doctest-glob=*.md --doctest-modules --cov-report term --cov-report html --cov-report xml --cov=injector --cov-branch 3 | norecursedirs = __pycache__ *venv* .git build 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.* 2 | 3 | !/.gitignore 4 | !/.github 5 | 6 | .cache/ 7 | __pycache__/ 8 | docs/_build/ 9 | build/ 10 | htmlcov/ 11 | *,cover 12 | .mypy_cache/ 13 | .pytest_cache/ 14 | coverage.xml 15 | /dist/ 16 | /injector.egg-info/ 17 | -------------------------------------------------------------------------------- /requirements-docs.in: -------------------------------------------------------------------------------- 1 | # The documentation-specific development dependencies. 2 | # 3 | # We generate requirements-dev.txt from this file by running 4 | # 5 | # pip install -r requirements-docs.in && pip freeze > requirements-docs.txt 6 | # 7 | # and then modifying the file manually to restrict black and mypy to CPython 8 | furo 9 | sphinx 10 | -------------------------------------------------------------------------------- /docs/_templates/sidebar.html: -------------------------------------------------------------------------------- 1 |

Injector

2 |

Dependency Injection Framework

3 | 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.py 2 | include *.toml 3 | include requirements-*.in 4 | include *.txt 5 | include CHANGES 6 | include COPYING 7 | include README.md 8 | include mypy.ini 9 | include pytest.ini 10 | recursive-include docs *.html 11 | recursive-include docs *.py 12 | recursive-include docs *.rst 13 | recursive-include docs Makefile 14 | exclude .readthedocs.yaml 15 | exclude .coveragerc 16 | -------------------------------------------------------------------------------- /requirements-dev.in: -------------------------------------------------------------------------------- 1 | # Our direct dependencies used in development/CI. 2 | # 3 | # We generate requirements-dev.txt from this file by running 4 | # 5 | # pip install -r requirements-dev.in && pip freeze > requirements-dev.txt 6 | # 7 | # and then modifying the file manually to restrict black and mypy to CPython 8 | 9 | pytest 10 | pytest-cov>=2.5.1 11 | mypy;implementation_name=="cpython" 12 | black;implementation_name=="cpython" 13 | check-manifest 14 | typing_extensions>=3.7.4;python_version<"3.9" 15 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | Injector API reference 2 | ====================== 3 | 4 | .. note:: 5 | 6 | Unless specified otherwise, instance methods are **not** thread safe. 7 | 8 | The following functions are thread safe: 9 | 10 | * :meth:`Injector.get` 11 | * injection provided by :func:`inject` decorator (please note, however, that it doesn't say anything about decorated function thread safety) 12 | 13 | 14 | .. automodule:: injector 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | version: 2 5 | 6 | build: 7 | os: ubuntu-22.04 8 | tools: 9 | python: "3.12" 10 | 11 | sphinx: 12 | configuration: docs/conf.py 13 | # TODO: Enable this when we get rid of the existing warnings 14 | # fail_on_warning: true 15 | 16 | python: 17 | install: 18 | - method: pip 19 | path: . 20 | - requirements: requirements-docs.txt 21 | -------------------------------------------------------------------------------- /docs/logging.rst: -------------------------------------------------------------------------------- 1 | Logging 2 | ======= 3 | 4 | Injector uses standard :mod:`logging` module, the logger name is ``injector``. 5 | 6 | By default ``injector`` logger is not configured to print logs anywhere. 7 | 8 | To enable ``get()`` tracing (and some other useful information) you need to set ``injector`` logger level to ``DEBUG``. You can do that by executing:: 9 | 10 | import logging 11 | 12 | logging.getLogger('injector').setLevel(logging.DEBUG) 13 | 14 | or by configuring :mod:`logging` module in any other way. 15 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | black==24.3.0 ; implementation_name == "cpython" 2 | build==1.0.3 3 | check-manifest==0.49 4 | click==8.1.7 5 | coverage[toml]==7.3.2 6 | exceptiongroup==1.2.0 7 | importlib-metadata==7.0.0 8 | iniconfig==2.0.0 9 | mypy==1.7.1 ; implementation_name == "cpython" 10 | mypy-extensions==1.0.0 11 | packaging==25.0 12 | pathspec==0.12.1 13 | platformdirs==4.1.0 14 | pluggy==1.3.0 15 | pyproject-hooks==1.0.0 16 | pytest==7.4.3 17 | pytest-cov==4.1.0 18 | tomli==2.0.1 19 | typing-extensions==4.9.0 ; python_version < "3.9" 20 | zipp==3.19.1 21 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.13 2 | babel==2.14.0 3 | beautifulsoup4==4.12.3 4 | certifi==2024.7.4 5 | charset-normalizer==3.3.2 6 | docutils==0.20.1 7 | furo==2024.5.6 8 | idna==3.7 9 | imagesize==1.4.1 10 | jinja2==3.1.6 11 | markupsafe==2.1.3 12 | packaging==25.0 13 | pygments==2.17.2 14 | requests==2.32.4 15 | snowballstemmer==2.2.0 16 | soupsieve==2.5 17 | sphinx==7.1.2 18 | sphinx-basic-ng==1.0.0b2 19 | sphinxcontrib-applehelp==1.0.4 20 | sphinxcontrib-devhelp==1.0.2 21 | sphinxcontrib-htmlhelp==2.0.1 22 | sphinxcontrib-jsmath==1.0.1 23 | sphinxcontrib-qthelp==1.0.3 24 | sphinxcontrib-serializinghtml==1.1.5 25 | urllib3==2.6.0 26 | -------------------------------------------------------------------------------- /docs/testing.rst: -------------------------------------------------------------------------------- 1 | Testing with Injector 2 | ===================== 3 | 4 | When you use unit test framework such as `unittest2` or `nose` you can also profit from `injector`. :: 5 | 6 | import unittest 7 | from injector import Injector, Module 8 | 9 | 10 | class UsernameModule(Module): 11 | def configure(self, binder): 12 | binder.bind(str, 'Maria') 13 | 14 | 15 | class TestSomethingClass(unittest.TestCase): 16 | 17 | def setUp(self): 18 | self.__injector = Injector(UsernameModule()) 19 | 20 | def test_username(self): 21 | username = self.__injector.get(str) 22 | self.assertEqual(username, 'Maria') 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest] 15 | python-version: 16 | [ 17 | 3.8, 18 | 3.9, 19 | "3.10", 20 | "3.11", 21 | "3.12", 22 | "3.13", 23 | "pypy3.8", 24 | "pypy3.9", 25 | "pypy3.10", 26 | "pypy3.11", 27 | ] 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Set up Python ${{ matrix.python-version }} 31 | uses: actions/setup-python@v4 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | - name: Install dependencies 35 | run: | 36 | pip install --upgrade -r requirements-dev.txt 37 | pip install . 38 | - name: Run tests 39 | run: | 40 | py.test -vv --cov=injector --cov-branch --cov-report html --cov-report term 41 | if which mypy; then mypy injector ; fi 42 | if which black; then black --check . ; fi 43 | check-manifest 44 | - name: Report coverage to Codecov 45 | uses: codecov/codecov-action@v5 46 | env: 47 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 48 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Alec Thomas, Google Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | - Neither the name of SwapOff.org nor the names of its contributors may 13 | be used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | .. _faq: 2 | 3 | Frequently Asked Questions 4 | ========================== 5 | 6 | If I use :func:`~injector.inject` or scope decorators on my classess will I be able to create instances of them without using Injector? 7 | --------------------------------------------------------------------------------------------------------------------------------------- 8 | 9 | Yes. Scope decorators don't change the way you can construct your class 10 | instances without Injector interaction. 11 | 12 | I'm calling this method (/function/class) but I'm getting "TypeError: XXX() takes exactly X arguments (Y given)" 13 | ---------------------------------------------------------------------------------------------------------------- 14 | 15 | Example code: 16 | 17 | .. code-block:: python 18 | 19 | class X: 20 | @inject 21 | def __init__(self, s: str): 22 | self.s = s 23 | 24 | def configure(binder): 25 | binder.bind(s, to='some string') 26 | 27 | injector = Injector(configure) 28 | x = X() 29 | 30 | Result? 31 | 32 | :: 33 | 34 | TypeError: __init__() takes exactly 2 arguments (1 given) 35 | 36 | Reason? There's *no* global state that :class:`Injector` modifies when 37 | it's instantiated and configured. Its whole knowledge about bindings etc. 38 | is stored in itself. Moreover :func:`inject` will *not* make 39 | dependencies appear out of thin air when you for example attempt to create 40 | an instance of a class manually (without ``Injector``'s help) - there's no 41 | global state ``@inject`` decorated methods can access. 42 | 43 | In order for ``X`` to be able to use bindings defined in ``@inject`` 44 | decoration :class:`Injector` needs to be used (directly or indirectly) 45 | to create an instance of ``X``. This means most of the time you want to just 46 | inject ``X`` where you need it, you can also use :meth:`Injector.get` to obtain 47 | an instance of the class (see its documentation for usage notes). 48 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Command 2 | import sys 3 | import warnings 4 | 5 | 6 | warnings.filterwarnings("always", module=__name__) 7 | 8 | 9 | def obtain_requirements(file_name): 10 | with open(file_name) as fd_in: 11 | for line in fd_in: 12 | line = line.split('#')[0] 13 | line = line.strip() 14 | if line: 15 | yield line 16 | 17 | 18 | class PyTest(Command): 19 | user_options = [] 20 | 21 | def initialize_options(self): 22 | pass 23 | 24 | def finalize_options(self): 25 | pass 26 | 27 | def run(self): 28 | import subprocess 29 | 30 | errno = subprocess.call([sys.executable, '-m', 'pytest']) 31 | raise SystemExit(errno) 32 | 33 | 34 | def read_injector_variable(name): 35 | prefix = '%s = ' % (name,) 36 | with open('injector/__init__.py') as f: 37 | for line in f: 38 | if line.startswith(prefix): 39 | return line.replace(prefix, '').strip().strip("'") 40 | raise AssertionError('variable %s not found' % (name,)) 41 | 42 | 43 | version = read_injector_variable('__version__') 44 | version_tag = read_injector_variable('__version_tag__') 45 | 46 | 47 | requirements = list(obtain_requirements('requirements.txt')) 48 | requirements_dev = list(obtain_requirements('requirements-dev.txt')) 49 | 50 | 51 | with open('README.md') as f: 52 | long_description = f.read() 53 | 54 | description = long_description.splitlines()[0].strip() 55 | 56 | 57 | setup( 58 | name='injector', 59 | url='https://github.com/alecthomas/injector', 60 | download_url='https://pypi.org/project/injector/', 61 | version=version, 62 | options=dict(egg_info=dict(tag_build=version_tag)), 63 | description=description, 64 | long_description=long_description, 65 | long_description_content_type='text/markdown', 66 | license='BSD', 67 | platforms=['any'], 68 | packages=['injector'], 69 | package_data={'injector': ['py.typed']}, 70 | author='Alec Thomas', 71 | author_email='alec@swapoff.org', 72 | cmdclass={'test': PyTest}, 73 | extras_require={'dev': requirements_dev}, 74 | keywords=[ 75 | 'Dependency Injection', 76 | 'DI', 77 | 'Dependency Injection framework', 78 | 'Inversion of Control', 79 | 'IoC', 80 | 'Inversion of Control container', 81 | ], 82 | install_requires=requirements, 83 | ) 84 | -------------------------------------------------------------------------------- /docs/scopes.rst: -------------------------------------------------------------------------------- 1 | .. _scopes: 2 | 3 | Scopes 4 | ====== 5 | 6 | Singletons 7 | `````````` 8 | 9 | Singletons are declared by binding them in the SingletonScope. This can be done in three ways: 10 | 11 | 1. Decorating the class with `@singleton`. 12 | 2. Decorating a `@provider` decorated Module method with `@singleton`. 13 | 3. Explicitly calling `binder.bind(X, scope=singleton)`. 14 | 15 | A (redundant) example showing all three methods:: 16 | 17 | @singleton 18 | class Thing: pass 19 | class ThingModule(Module): 20 | def configure(self, binder): 21 | binder.bind(Thing, scope=singleton) 22 | @singleton 23 | @provider 24 | def provide_thing(self) -> Thing: 25 | return Thing() 26 | 27 | If using hierarchies of injectors, classes decorated with `@singleton` will be created by and bound to the parent/ancestor injector closest to the root that can provide all of its dependencies. 28 | 29 | Implementing new Scopes 30 | ``````````````````````` 31 | 32 | In the above description of scopes, we glossed over a lot of detail. In particular, how one would go about implementing our own scopes. 33 | 34 | Basically, there are two steps. First, subclass `Scope` and implement `Scope.get`:: 35 | 36 | from injector import Scope 37 | class CustomScope(Scope): 38 | def get(self, key, provider): 39 | return provider 40 | 41 | Then create a global instance of :class:`ScopeDecorator` to allow classes to be easily annotated with your scope:: 42 | 43 | from injector import ScopeDecorator 44 | customscope = ScopeDecorator(CustomScope) 45 | 46 | This can be used like so:: 47 | 48 | @customscope 49 | class MyClass: 50 | pass 51 | 52 | Scopes are bound in modules with the :meth:`Binder.install` method:: 53 | 54 | class MyModule(Module): 55 | def configure(self, binder): 56 | binder.install(CustomScope) 57 | 58 | Scopes can be retrieved from the injector, as with any other instance. They are singletons across the life of the injector:: 59 | 60 | >>> injector = Injector([MyModule()]) 61 | >>> injector.get(CustomScope) is injector.get(CustomScope) 62 | True 63 | 64 | For scopes with a transient lifetime, such as those tied to HTTP requests, the usual solution is to use a thread or greenlet-local cache inside the scope. The scope is "entered" in some low-level code by calling a method on the scope instance that creates this cache. Once the request is complete, the scope is "left" and the cache cleared. 65 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Injector documentation master file, created by 2 | sphinx-quickstart on Mon Sep 16 02:58:17 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Injector's documentation! 7 | ==================================== 8 | 9 | .. image:: https://github.com/alecthomas/injector/workflows/CI/badge.svg 10 | :alt: Build status 11 | :target: https://github.com/alecthomas/injector/actions?query=workflow%3ACI+branch%3Amaster 12 | 13 | .. image:: https://codecov.io/gh/alecthomas/injector/branch/master/graph/badge.svg 14 | :alt: Covergage status 15 | :target: https://codecov.io/gh/alecthomas/injector 16 | 17 | 18 | GitHub (code repository, issues): https://github.com/alecthomas/injector 19 | 20 | PyPI (installable, stable distributions): https://pypi.org/project/injector. You can install Injector using pip:: 21 | 22 | pip install injector 23 | 24 | Injector works with CPython 3.6+ and PyPy 3 implementing Python 3.6+. 25 | 26 | Introduction 27 | ------------ 28 | 29 | While dependency injection is easy to do in Python due to its support for keyword arguments, the ease with which objects can be mocked and its dynamic natura, a framework for assisting in this process can remove a lot of boiler-plate from larger applications. That's where Injector can help. It automatically and transitively provides dependencies for you. As an added benefit, Injector encourages nicely compartmentalised code through the use of :ref:`modules `. 30 | 31 | If you're not sure what dependency injection is or you'd like to learn more about it see: 32 | 33 | * `The Clean Code Talks - Don't Look For Things! (a talk by Miško Hevery) 34 | `_ 35 | * `Inversion of Control Containers and the Dependency Injection pattern (an article by Martin Fowler) 36 | `_ 37 | 38 | The core values of Injector are: 39 | 40 | * Simplicity - while being inspired by Guice, Injector does not slavishly replicate its API. 41 | Providing a Pythonic API trumps faithfulness. Additionally some features are ommitted 42 | because supporting them would be cumbersome and introduce a little bit too much "magic" 43 | (member injection, method injection). 44 | 45 | Connected to this, Injector tries to be as nonintrusive as possible. For example while you may 46 | declare a class' constructor to expect some injectable parameters, the class' constructor 47 | remains a standard constructor – you may instaniate the class just the same manually, if you want. 48 | 49 | * No global state – you can have as many :class:`Injector` instances as you like, each with 50 | a different configuration and each with different objects in different scopes. Code like this 51 | won't work for this very reason:: 52 | 53 | # This will NOT work: 54 | 55 | class MyClass: 56 | @inject 57 | def __init__(self, t: SomeType): 58 | # ... 59 | 60 | MyClass() 61 | 62 | This is simply because there's no global :class:`Injector` to use. You need to be explicit and use 63 | :meth:`Injector.get `, 64 | :meth:`Injector.create_object ` or inject `MyClass` into the place 65 | that needs it. 66 | 67 | * Cooperation with static type checking infrastructure – the API provides as much static type safety 68 | as possible and only breaks it where there's no other option. For example the 69 | :meth:`Injector.get ` method is typed such that `injector.get(SomeType)` 70 | is statically declared to return an instance of `SomeType`, therefore making it possible for tools 71 | such as `mypy `_ to type-check correctly the code using it. 72 | 73 | Quick start 74 | ----------- 75 | 76 | See `the project's README `_ for an 77 | example of Injector use. 78 | 79 | Contents 80 | -------- 81 | 82 | .. toctree:: 83 | :maxdepth: 1 84 | 85 | changelog 86 | terminology 87 | testing 88 | scopes 89 | logging 90 | api 91 | faq 92 | practices 93 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Injector.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Injector.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Injector" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Injector" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/practices.rst: -------------------------------------------------------------------------------- 1 | .. _practices: 2 | 3 | Good and bad practices 4 | ====================== 5 | 6 | Side effects 7 | ```````````` 8 | 9 | You should avoid creating side effects in your modules for two reasons: 10 | 11 | * Side effects will make it more difficult to test a module if you want to do it 12 | * Modules expose a way to acquire some resource but they don't provide any way 13 | to release it. If, for example, your module connects to a remote server while 14 | creating a service you have no way of closing that connection unless the 15 | service exposes it. 16 | 17 | 18 | Injecting into constructors vs injecting into other methods 19 | ``````````````````````````````````````````````````````````` 20 | 21 | .. note:: 22 | 23 | Injector 0.11+ doesn't support injecting into non-constructor methods, 24 | this section is kept for historical reasons. 25 | 26 | .. note:: 27 | 28 | Injector 0.11 deprecates using @inject with keyword arguments to declare 29 | bindings, this section remains unchanged for historical reasons. 30 | 31 | In general you should prefer injecting into constructors to injecting into 32 | other methods because: 33 | 34 | * it can expose potential issues earlier (at object construction time rather 35 | than at the method call) 36 | * it exposes class' dependencies more openly. Constructor injection: 37 | 38 | .. code-block:: python 39 | 40 | class Service1(object): 41 | @inject(http_client=HTTP) 42 | def __init__(self, http_client): 43 | self.http_client = http_client 44 | # some other code 45 | 46 | # tens or hundreds lines of code 47 | 48 | def method(self): 49 | # do something 50 | pass 51 | 52 | Regular method injection: 53 | 54 | .. code-block:: python 55 | 56 | class Service2(object): 57 | def __init__(self): 58 | # some other code 59 | 60 | # tens or hundreds lines of code 61 | 62 | @inject(http_client=HTTP) 63 | def method(self, http_client): 64 | # do something 65 | pass 66 | 67 | 68 | In first case you know all the dependencies by looking at the class' 69 | constructor, in the second you don't know about ``HTTP`` dependency until 70 | you see the method definition. 71 | 72 | Slightly different approach is suggested when it comes to Injector modules - 73 | in this case injecting into their constructors (or ``configure`` methods) 74 | would make the injection process dependent on the order of passing modules 75 | to Injector and therefore quite fragile. See this code sample: 76 | 77 | .. code-block:: python 78 | 79 | A = Key('A') 80 | B = Key('B') 81 | 82 | class ModuleA(Module): 83 | @inject(a=A) 84 | def configure(self, binder, a): 85 | pass 86 | 87 | class ModuleB(Module): 88 | @inject(b=B) 89 | def __init__(self, b): 90 | pass 91 | 92 | class ModuleC(Module): 93 | def configure(self, binder): 94 | binder.bind(A, to='a') 95 | binder.bind(B, to='b') 96 | 97 | 98 | # error, at the time of ModuleA processing A is unbound 99 | Injector([ModuleA, ModuleC]) 100 | 101 | # error, at the time of ModuleB processing B is unbound 102 | Injector([ModuleB, ModuleC]) 103 | 104 | # no error this time 105 | Injector([ModuleC, ModuleA, ModuleB]) 106 | 107 | 108 | Doing too much in modules and/or providers 109 | `````````````````````````````````````````` 110 | 111 | An implementation detail of Injector: Injector and accompanying classes are 112 | protected by a lock to make them thread safe. This has a downside though: 113 | in general only one thread can use dependency injection at any given moment. 114 | 115 | In best case scenario you "only" slow other threads' dependency injection 116 | down. In worst case scenario (performing blocking calls without timeouts) you 117 | can **deadlock** whole application. 118 | 119 | **It is advised to avoid performing any IO, particularly without a timeout 120 | set, inside modules code.** 121 | 122 | As an illustration: 123 | 124 | .. code-block:: python 125 | 126 | from threading import Thread 127 | from time import sleep 128 | 129 | from injector import inject, Injector, Module, provider 130 | 131 | class A: pass 132 | class SubA(A): pass 133 | class B: pass 134 | 135 | 136 | class BadModule(Module): 137 | @provider 138 | def provide_a(self, suba: SubA) -> A: 139 | return suba 140 | 141 | @provider 142 | def provide_suba(self) -> SubA: 143 | print('Providing SubA...') 144 | while True: 145 | print('Sleeping...') 146 | sleep(1) 147 | 148 | # This never executes 149 | return SubA() 150 | 151 | @provider 152 | def provide_b(self) -> B: 153 | return B() 154 | 155 | 156 | injector = Injector([BadModule]) 157 | 158 | thread = Thread(target=lambda: injector.get(A)) 159 | 160 | # to make sure the thread doesn't keep the application alive 161 | thread.daemon = True 162 | thread.start() 163 | 164 | # This will never finish 165 | injector.get(B) 166 | print('Got B') 167 | 168 | 169 | Here's the output of the application:: 170 | 171 | Providing SubA... 172 | Sleeping... 173 | Sleeping... 174 | Sleeping... 175 | (...) 176 | 177 | 178 | Injecting Injector and abusing Injector.get 179 | ``````````````````````````````````````````` 180 | 181 | Sometimes code like this is written: 182 | 183 | .. code-block:: python 184 | 185 | class A: 186 | pass 187 | 188 | class B: 189 | pass 190 | 191 | class C: 192 | @inject 193 | def __init__(self, injector: Injector): 194 | self.a = injector.get(A) 195 | self.b = injector.get(B) 196 | 197 | 198 | It is advised to use the following pattern instead: 199 | 200 | .. code-block:: python 201 | 202 | class A: 203 | pass 204 | 205 | class B: 206 | pass 207 | 208 | class C: 209 | @inject 210 | def __init__(self, a: A, b: B): 211 | self.a = a 212 | self.b = b 213 | 214 | 215 | The second form has the benefits of: 216 | 217 | * expressing clearly what the dependencies of ``C`` are 218 | * making testing of the ``C`` class easier - you can provide the dependencies 219 | (whether they are mocks or not) directly, instead of having to mock 220 | :class:`Injector` and make the mock handle :meth:`Injector.get` calls 221 | * following the common practice and being easier to understand 222 | 223 | 224 | Injecting dependencies only to pass them somewhere else 225 | ``````````````````````````````````````````````````````` 226 | 227 | A pattern similar to the one below can emerge: 228 | 229 | .. code-block:: python 230 | 231 | class A: 232 | pass 233 | 234 | class B: 235 | def __init__(self, a): 236 | self.a = a 237 | 238 | class C: 239 | @inject 240 | def __init__(self, a: A): 241 | self.b = B(a) 242 | 243 | Class ``C`` in this example has the responsibility of gathering dependencies of 244 | class ``B`` and constructing an object of type ``B``, there may be a valid reason 245 | for it but in general it defeats the purpose of using ``Injector`` and should 246 | be avoided. 247 | 248 | The appropriate pattern is: 249 | 250 | .. code-block:: python 251 | 252 | class A: 253 | pass 254 | 255 | class B: 256 | @inject 257 | def __init__(self, a: A): 258 | self.a = a 259 | 260 | class C: 261 | @inject 262 | def __init__(self, b: B): 263 | self.b = b 264 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Injector - Python dependency injection framework, inspired by Guice 2 | =================================================================== 3 | 4 | [![CI](https://github.com/python-injector/injector/actions/workflows/ci.yml/badge.svg)](https://github.com/python-injector/injector/actions/workflows/ci.yml) 5 | [![Coverage Status](https://codecov.io/gh/python-injector/injector/branch/master/graph/badge.svg)](https://codecov.io/gh/alecthomas/injector) 6 | 7 | Introduction 8 | ------------ 9 | 10 | While dependency injection is easy to do in Python due to its support for keyword arguments, the ease with which objects can be mocked and its dynamic nature, a framework for assisting in this process can remove a lot of boiler-plate from larger applications. That's where Injector can help. It automatically and transitively provides dependencies for you. As an added benefit, Injector encourages nicely compartmentalised code through the use of modules. 11 | 12 | If you're not sure what dependency injection is or you'd like to learn more about it see: 13 | 14 | * [The Clean Code Talks - Don't Look For Things! (a talk by Miško Hevery)]( 15 | https://www.youtube.com/watch?v=RlfLCWKxHJ0) 16 | * [Inversion of Control Containers and the Dependency Injection pattern (an article by Martin Fowler)]( 17 | https://martinfowler.com/articles/injection.html) 18 | 19 | The core values of Injector are: 20 | 21 | * Simplicity - while being inspired by Guice, Injector does not slavishly replicate its API. 22 | Providing a Pythonic API trumps faithfulness. Additionally some features are omitted 23 | because supporting them would be cumbersome and introduce a little bit too much "magic" 24 | (member injection, method injection). 25 | 26 | Connected to this, Injector tries to be as nonintrusive as possible. For example while you may 27 | declare a class' constructor to expect some injectable parameters, the class' constructor 28 | remains a standard constructor – you may instantiate the class just the same manually, if you want. 29 | 30 | * No global state – you can have as many [Injector](https://injector.readthedocs.io/en/latest/api.html#injector.Injector) 31 | instances as you like, each with a different configuration and each with different objects in different 32 | scopes. Code like this won't work for this very reason: 33 | 34 | ```python 35 | class MyClass: 36 | @inject 37 | def __init__(t: SomeType): 38 | # ... 39 | 40 | MyClass() 41 | ``` 42 | 43 | This is simply because there's no global `Injector` to use. You need to be explicit and use 44 | [Injector.get](https://injector.readthedocs.io/en/latest/api.html#injector.Injector.get), 45 | [Injector.create_object](https://injector.readthedocs.io/en/latest/api.html#injector.Injector.create_object) 46 | or inject `MyClass` into the place that needs it. 47 | 48 | * Cooperation with static type checking infrastructure – the API provides as much static type safety 49 | as possible and only breaks it where there's no other option. For example the 50 | [Injector.get](https://injector.readthedocs.io/en/latest/api.html#injector.Injector.get) method 51 | is typed such that `injector.get(SomeType)` is statically declared to return an instance of 52 | `SomeType`, therefore making it possible for tools such as [mypy](https://github.com/python/mypy) to 53 | type-check correctly the code using it. 54 | 55 | * The client code only knows about dependency injection to the extent it needs –  56 | [`inject`](https://injector.readthedocs.io/en/latest/api.html#injector.inject), 57 | [`Inject`](https://injector.readthedocs.io/en/latest/api.html#injector.Inject) and 58 | [`NoInject`](https://injector.readthedocs.io/en/latest/api.html#injector.NoInject) are simple markers 59 | that don't really do anything on their own and your code can run just fine without Injector 60 | orchestrating things. 61 | 62 | ### How to get Injector? 63 | 64 | * GitHub (code repository, issues): https://github.com/alecthomas/injector 65 | 66 | * PyPI (installable, stable distributions): https://pypi.org/project/injector/. You can install it using pip: 67 | 68 | ```bash 69 | pip install injector 70 | ``` 71 | 72 | * Documentation: https://injector.readthedocs.org 73 | * Change log: https://injector.readthedocs.io/en/latest/changelog.html 74 | 75 | Injector works with CPython 3.8+ and PyPy 3 implementing Python 3.8+. 76 | 77 | A Quick Example 78 | --------------- 79 | 80 | 81 | ```python 82 | >>> from injector import Injector, inject 83 | >>> class Inner: 84 | ... def __init__(self): 85 | ... self.forty_two = 42 86 | ... 87 | >>> class Outer: 88 | ... @inject 89 | ... def __init__(self, inner: Inner): 90 | ... self.inner = inner 91 | ... 92 | >>> injector = Injector() 93 | >>> outer = injector.get(Outer) 94 | >>> outer.inner.forty_two 95 | 42 96 | 97 | ``` 98 | 99 | Or with `dataclasses` if you like: 100 | 101 | ```python 102 | from dataclasses import dataclass 103 | from injector import Injector, inject 104 | class Inner: 105 | def __init__(self): 106 | self.forty_two = 42 107 | 108 | @inject 109 | @dataclass 110 | class Outer: 111 | inner: Inner 112 | 113 | injector = Injector() 114 | outer = injector.get(Outer) 115 | print(outer.inner.forty_two) # Prints 42 116 | ``` 117 | 118 | 119 | A Full Example 120 | -------------- 121 | 122 | Here's a full example to give you a taste of how Injector works: 123 | 124 | 125 | ```python 126 | >>> from injector import Module, provider, Injector, inject, singleton 127 | 128 | ``` 129 | 130 | We'll use an in-memory SQLite database for our example: 131 | 132 | 133 | ```python 134 | >>> import sqlite3 135 | 136 | ``` 137 | 138 | And make up an imaginary `RequestHandler` class that uses the SQLite connection: 139 | 140 | 141 | ```python 142 | >>> class RequestHandler: 143 | ... @inject 144 | ... def __init__(self, db: sqlite3.Connection): 145 | ... self._db = db 146 | ... 147 | ... def get(self): 148 | ... cursor = self._db.cursor() 149 | ... cursor.execute('SELECT key, value FROM data ORDER by key') 150 | ... return cursor.fetchall() 151 | 152 | ``` 153 | 154 | Next, for the sake of the example, we'll create a configuration type: 155 | 156 | 157 | ```python 158 | >>> class Configuration: 159 | ... def __init__(self, connection_string): 160 | ... self.connection_string = connection_string 161 | 162 | ``` 163 | 164 | Next, we bind the configuration to the injector, using a module: 165 | 166 | 167 | ```python 168 | >>> def configure_for_testing(binder): 169 | ... configuration = Configuration(':memory:') 170 | ... binder.bind(Configuration, to=configuration, scope=singleton) 171 | 172 | ``` 173 | 174 | Next we create a module that initialises the DB. It depends on the configuration provided by the above module to create a new DB connection, then populates it with some dummy data, and provides a `Connection` object: 175 | 176 | 177 | ```python 178 | >>> class DatabaseModule(Module): 179 | ... @singleton 180 | ... @provider 181 | ... def provide_sqlite_connection(self, configuration: Configuration) -> sqlite3.Connection: 182 | ... conn = sqlite3.connect(configuration.connection_string) 183 | ... cursor = conn.cursor() 184 | ... cursor.execute('CREATE TABLE IF NOT EXISTS data (key PRIMARY KEY, value)') 185 | ... cursor.execute('INSERT OR REPLACE INTO data VALUES ("hello", "world")') 186 | ... return conn 187 | 188 | ``` 189 | 190 | (Note how we have decoupled configuration from our database initialisation code.) 191 | 192 | Finally, we initialise an `Injector` and use it to instantiate a `RequestHandler` instance. This first transitively constructs a `sqlite3.Connection` object, and the Configuration dictionary that it in turn requires, then instantiates our `RequestHandler`: 193 | 194 | 195 | ```python 196 | >>> injector = Injector([configure_for_testing, DatabaseModule()]) 197 | >>> handler = injector.get(RequestHandler) 198 | >>> tuple(map(str, handler.get()[0])) # py3/py2 compatibility hack 199 | ('hello', 'world') 200 | 201 | ``` 202 | 203 | We can also verify that our `Configuration` and `SQLite` connections are indeed singletons within the Injector: 204 | 205 | 206 | ```python 207 | >>> injector.get(Configuration) is injector.get(Configuration) 208 | True 209 | >>> injector.get(sqlite3.Connection) is injector.get(sqlite3.Connection) 210 | True 211 | 212 | ``` 213 | 214 | You're probably thinking something like: "this is a large amount of work just to give me a database connection", and you are correct; dependency injection is typically not that useful for smaller projects. It comes into its own on large projects where the up-front effort pays for itself in two ways: 215 | 216 | 1. Forces decoupling. In our example, this is illustrated by decoupling our configuration and database configuration. 217 | 2. After a type is configured, it can be injected anywhere with no additional effort. Simply `@inject` and it appears. We don't really illustrate that here, but you can imagine adding an arbitrary number of `RequestHandler` subclasses, all of which will automatically have a DB connection provided. 218 | 219 | Footnote 220 | -------- 221 | 222 | This framework is similar to snake-guice, but aims for simplification. 223 | 224 | © Copyright 2010-2013 to Alec Thomas, under the BSD license 225 | -------------------------------------------------------------------------------- /docs/terminology.rst: -------------------------------------------------------------------------------- 1 | Terminology 2 | =========== 3 | 4 | At its heart, Injector is simply a dictionary for mapping types to things that create instances of those types. This could be as simple as:: 5 | 6 | {str: 'an instance of a string'} 7 | 8 | For those new to dependency-injection and/or Guice, though, some of the terminology used may not be obvious. 9 | 10 | Provider 11 | ```````` 12 | 13 | A means of providing an instance of a type. Built-in providers include: 14 | 15 | * :class:`~injector.ClassProvider` - creates a new instance from a class 16 | * :class:`~injector.InstanceProvider` - returns an existing instance directly 17 | * :class:`~injector.CallableProvider` - provides an instance by calling a function 18 | 19 | In order to create custom provider you need to subclass :class:`~injector.Provider` and override its :meth:`~injector.Provider.get` method. 20 | 21 | Scope 22 | ````` 23 | 24 | By default, providers are executed each time an instance is required. Scopes allow this behaviour to be customised. For example, `SingletonScope` (typically used through the class decorator `singleton`), can be used to always provide the same instance of a class. 25 | 26 | Other examples of where scopes might be a threading scope, where instances are provided per-thread, or a request scope, where instances are provided per-HTTP-request. 27 | 28 | The default scope is :class:`NoScope`. 29 | 30 | .. seealso:: :ref:`scopes` 31 | 32 | Binding 33 | ``````` 34 | 35 | A binding is the mapping of a unique binding key to a corresponding provider. For example:: 36 | 37 | >>> from injector import InstanceProvider 38 | >>> bindings = { 39 | ... (Name, None): InstanceProvider('Sherlock'), 40 | ... (Description, None): InstanceProvider('A man of astounding insight'), 41 | ... } 42 | 43 | 44 | Binder 45 | `````` 46 | 47 | The `Binder` is simply a convenient wrapper around the dictionary that maps types to providers. It provides methods that make declaring bindings easier. 48 | 49 | 50 | .. _module: 51 | 52 | Module 53 | `````` 54 | 55 | A `Module` configures bindings. It provides methods that simplify the process of binding a key to a provider. For example the above bindings would be created with:: 56 | 57 | >>> from injector import Module 58 | >>> class MyModule(Module): 59 | ... def configure(self, binder): 60 | ... binder.bind(Name, to='Sherlock') 61 | ... binder.bind(Description, to='A man of astounding insight') 62 | 63 | For more complex instance construction, methods decorated with `@provider` will be called to resolve binding keys:: 64 | 65 | >>> from injector import provider 66 | >>> class MyModule(Module): 67 | ... def configure(self, binder): 68 | ... binder.bind(Name, to='Sherlock') 69 | ... 70 | ... @provider 71 | ... def describe(self) -> Description: 72 | ... return 'A man of astounding insight (at %s)' % time.time() 73 | 74 | Injection 75 | ````````` 76 | 77 | Injection is the process of providing an instance of a type, to a method that uses that instance. It is achieved with the `inject` decorator. Keyword arguments to inject define which arguments in its decorated method should be injected, and with what. 78 | 79 | Here is an example of injection on a module provider method, and on the constructor of a normal class:: 80 | 81 | from typing import NewType 82 | 83 | from injector import Binder, Module, inject, provider 84 | 85 | Name = NewType("Name", str) 86 | Description = NewType("Description", str) 87 | 88 | class User: 89 | @inject 90 | def __init__(self, name: Name, description: Description): 91 | self.name = name 92 | self.description = description 93 | 94 | class UserModule(Module): 95 | def configure(self, binder: Binder): 96 | binder.bind(User) 97 | 98 | class UserAttributeModule(Module): 99 | def configure(self, binder: Binder): 100 | binder.bind(Name, to='Sherlock') 101 | 102 | @provider 103 | def describe(self, name: Name) -> Description: 104 | return '%s is a man of astounding insight' % name 105 | 106 | 107 | Injector 108 | ```````` 109 | 110 | The `Injector` brings everything together. It takes a list of `Module` s, and configures them with a binder, effectively creating a dependency graph:: 111 | 112 | from injector import Injector 113 | injector = Injector([UserModule(), UserAttributeModule()]) 114 | 115 | You can also pass classes instead of instances to `Injector`, it will instantiate them for you:: 116 | 117 | injector = Injector([UserModule, UserAttributeModule]) 118 | 119 | The injector can then be used to acquire instances of a type, either directly:: 120 | 121 | >>> injector.get(Name) 122 | 'Sherlock' 123 | >>> injector.get(Description) 124 | 'Sherlock is a man of astounding insight' 125 | 126 | Or transitively:: 127 | 128 | >>> user = injector.get(User) 129 | >>> isinstance(user, User) 130 | True 131 | >>> user.name 132 | 'Sherlock' 133 | >>> user.description 134 | 'Sherlock is a man of astounding insight' 135 | 136 | Assisted injection 137 | `````````````````` 138 | 139 | Sometimes there are classes that have injectable and non-injectable parameters in their constructors. Let's have for example:: 140 | 141 | class Database: pass 142 | 143 | 144 | class User: 145 | def __init__(self, name): 146 | self.name = name 147 | 148 | 149 | class UserUpdater: 150 | def __init__(self, db: Database, user): 151 | pass 152 | 153 | You may want to have database connection `db` injected into `UserUpdater` constructor, but in the same time provide `user` object by yourself, and assuming that `user` object is a value object and there's many users in your application it doesn't make much sense to inject objects of class `User`. 154 | 155 | In this situation there's technique called Assisted injection:: 156 | 157 | from injector import ClassAssistedBuilder 158 | injector = Injector() 159 | builder = injector.get(ClassAssistedBuilder[UserUpdater]) 160 | user = User('John') 161 | user_updater = builder.build(user=user) 162 | 163 | This way we don't get `UserUpdater` directly but rather a builder object. Such builder has `build(**kwargs)` method which takes non-injectable parameters, combines them with injectable dependencies of `UserUpdater` and calls `UserUpdater` initializer using all of them. 164 | 165 | `AssistedBuilder[T]` and `ClassAssistedBuilder[T]` are injectable just as anything 166 | else, if you need instance of it you just ask for it like that:: 167 | 168 | class NeedsUserUpdater: 169 | @inject 170 | def __init__(self, builder: ClassAssistedBuilder[UserUpdater]): 171 | self.updater_builder = builder 172 | 173 | def method(self): 174 | updater = self.updater_builder.build(user=None) 175 | 176 | `ClassAssistedBuilder` means it'll construct a concrete class and no bindings will be used. 177 | 178 | If you want to follow bindings and construct class pointed to by a key you use `AssistedBuilder` and can do it like this:: 179 | 180 | >>> DB = Key('DB') 181 | >>> class DBImplementation: 182 | ... def __init__(self, uri): 183 | ... pass 184 | ... 185 | >>> def configure(binder): 186 | ... binder.bind(DB, to=DBImplementation) 187 | ... 188 | >>> injector = Injector(configure) 189 | >>> builder = injector.get(AssistedBuilder[DB]) 190 | >>> isinstance(builder.build(uri='x'), DBImplementation) 191 | True 192 | 193 | More information on this topic: 194 | 195 | - `"How to use Google Guice to create objects that require parameters?" on Stack Overflow `_ 196 | - `Google Guice assisted injection `_ 197 | 198 | 199 | Child injectors 200 | ``````````````` 201 | 202 | Concept similar to Guice's child injectors is supported by `Injector`. This way you can have one injector that inherits bindings from other injector (parent) but these bindings can be overriden in it and it doesn't affect parent injector bindings:: 203 | 204 | >>> def configure_parent(binder): 205 | ... binder.bind(str, to='asd') 206 | ... binder.bind(int, to=42) 207 | ... 208 | >>> def configure_child(binder): 209 | ... binder.bind(str, to='qwe') 210 | ... 211 | >>> parent = Injector(configure_parent) 212 | >>> child = parent.create_child_injector(configure_child) 213 | >>> parent.get(str), parent.get(int) 214 | ('asd', 42) 215 | >>> child.get(str), child.get(int) 216 | ('qwe', 42) 217 | 218 | **Note**: Default scopes are bound only to root injector. Binding them manually to child injectors will result in unexpected behaviour. **Note 2**: Once a binding key is present in parent injector scope (like `singleton` scope), provider saved there takes predecence when binding is overridden in child injector in the same scope. This behaviour is subject to change:: 219 | 220 | 221 | >>> def configure_parent(binder): 222 | ... binder.bind(str, to='asd', scope=singleton) 223 | ... 224 | >>> def configure_child(binder): 225 | ... binder.bind(str, to='qwe', scope=singleton) 226 | ... 227 | >>> parent = Injector(configure_parent) 228 | >>> child = parent.create_child_injector(configure_child) 229 | >>> child.get(str) # this behaves as expected 230 | 'qwe' 231 | >>> parent.get(str) # wat 232 | 'qwe' 233 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Injector documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Sep 16 02:58:17 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import injector 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | # needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | # source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'Injector' 44 | copyright = u'2013, Alec Thomas' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = injector.__version__ 52 | # The full version, including alpha/beta/rc tags. 53 | release = version 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | # language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | # today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | # today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | # default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | # add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | # add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | # show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | # modindex_common_prefix = [] 88 | 89 | # If true, keep warnings as "system message" paragraphs in the built documents. 90 | # keep_warnings = False 91 | 92 | 93 | # -- Options for HTML output --------------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. See the documentation for 96 | # a list of builtin themes. 97 | html_theme = 'furo' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | # html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | # html_theme_path = [] 106 | 107 | # The name for this set of Sphinx documents. If None, it defaults to 108 | # " v documentation". 109 | # html_title = None 110 | 111 | # A shorter title for the navigation bar. Default is the same as html_title. 112 | # html_short_title = None 113 | 114 | # The name of an image file (relative to this directory) to place at the top 115 | # of the sidebar. 116 | # html_logo = None 117 | 118 | # The name of an image file (within the static path) to use as favicon of the 119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | # html_favicon = None 122 | 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = ['_static'] 127 | 128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 129 | # using the given strftime format. 130 | # html_last_updated_fmt = '%b %d, %Y' 131 | 132 | # If true, SmartyPants will be used to convert quotes and dashes to 133 | # typographically correct entities. 134 | # html_use_smartypants = True 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | # html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | # html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | # html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | # html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | # html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | # html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | # html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | # html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | # html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'Injectordoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | # The font size ('10pt', '11pt' or '12pt'). 176 | #'pointsize': '10pt', 177 | # Additional stuff for the LaTeX preamble. 178 | #'preamble': '', 179 | } 180 | 181 | # Grouping the document tree into LaTeX files. List of tuples 182 | # (source start file, target name, title, author, documentclass [howto/manual]). 183 | latex_documents = [('index', 'Injector.tex', u'Injector Documentation', u'Alec Thomas', 'manual')] 184 | 185 | # The name of an image file (relative to this directory) to place at the top of 186 | # the title page. 187 | # latex_logo = None 188 | 189 | # For "manual" documents, if this is true, then toplevel headings are parts, 190 | # not chapters. 191 | # latex_use_parts = False 192 | 193 | # If true, show page references after internal links. 194 | # latex_show_pagerefs = False 195 | 196 | # If true, show URL addresses after external links. 197 | # latex_show_urls = False 198 | 199 | # Documents to append as an appendix to all manuals. 200 | # latex_appendices = [] 201 | 202 | # If false, no module index is generated. 203 | # latex_domain_indices = True 204 | 205 | 206 | # -- Options for manual page output -------------------------------------------- 207 | 208 | # One entry per manual page. List of tuples 209 | # (source start file, name, description, authors, manual section). 210 | man_pages = [('index', 'injector', u'Injector Documentation', [u'Alec Thomas'], 1)] 211 | 212 | # If true, show URL addresses after external links. 213 | # man_show_urls = False 214 | 215 | 216 | # -- Options for Texinfo output ------------------------------------------------ 217 | 218 | # Grouping the document tree into Texinfo files. List of tuples 219 | # (source start file, target name, title, author, 220 | # dir menu entry, description, category) 221 | texinfo_documents = [ 222 | ( 223 | 'index', 224 | 'Injector', 225 | u'Injector Documentation', 226 | u'Alec Thomas', 227 | 'Injector', 228 | 'One line description of project.', 229 | 'Miscellaneous', 230 | ) 231 | ] 232 | 233 | # Documents to append as an appendix to all manuals. 234 | # texinfo_appendices = [] 235 | 236 | # If false, no module index is generated. 237 | # texinfo_domain_indices = True 238 | 239 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 240 | # texinfo_show_urls = 'footnote' 241 | 242 | # If true, do not generate a @detailmenu in the "Top" node's menu. 243 | # texinfo_no_detailmenu = False 244 | 245 | 246 | # Example configuration for intersphinx: refer to the Python standard library. 247 | intersphinx_mapping = {'http://docs.python.org/': None} 248 | 249 | 250 | def setup(app): 251 | app.connect('autodoc-skip-member', skip_member) 252 | 253 | 254 | def skip_member(app, what, name, obj, skip, options): 255 | return ( 256 | skip 257 | or getattr(obj, '__doc__', None) is None 258 | or getattr(obj, '__private__', False) is True 259 | or getattr(getattr(obj, '__func__', None), '__private__', False) is True 260 | ) 261 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Injector Change Log 2 | =================== 3 | 4 | 0.23.0 5 | ------ 6 | 7 | Added: 8 | 9 | - Allow injecting Annotated types into classes, thanks to Filip Nešťák. 10 | - Fixed Annotated type support in `@provider` methods, thanks to Satwik Agrawal. 11 | - `multibind()` now accepts types and providers, not just instances. Thanks to Eiríkur Torfason. 12 | 13 | Backwards incompatible: 14 | 15 | - Changed so that the `scope` provided to `multibind()` applies to the individual bound types, within the collection. Previously the scope was applied to the collection, the `list` or `dict` instance. Thanks to Eiríkur Torfason. 16 | 17 | 0.22.0 18 | ------ 19 | 20 | Date: 2024-07-08 21 | 22 | Added: 23 | 24 | - Added support for injecting `PEP 593 `_ 25 | `Annotated `_ 26 | 27 | Removed: 28 | 29 | - Dropped Python 3.7 support 30 | 31 | 0.21.0 32 | ------ 33 | 34 | - Improved the documentation, thanks to jonathanmach and Jakub Wilk 35 | - Fixed a thread-safety regression 36 | - Improved the type annotations, thanks to David Pärsson 37 | - Fixed singleton scope behavior with parent/child injectors, thanks to David Pärsson 38 | - Stopped using a deprecated test function, thanks to ljnsn 39 | 40 | 0.20.1 41 | ------ 42 | 43 | - Added support for PEP 604 union types (Python 3.10+), thanks to David Pärsson 44 | - Fixed building with pypandoc 1.8+, thanks to Søren Fuglede Jørgensen 45 | 46 | 0.20.0 47 | ------ 48 | 49 | - Fixed handling of Union combined with Annotated, thanks to Tobias Nilsson 50 | - Fixed AssitedBuilder/child Injector interaction, thanks to Erik Cederberg 51 | - Made get_bindings() and injections work even if a injectee's return type 52 | annotation is a forward reference that can't be resolved 53 | 54 | Backwards incompatible: 55 | 56 | - Dropped Python 3.6 support 57 | 58 | 0.19.0 59 | ------ 60 | 61 | - Added the license to the source distribution, thanks to Joshua Adelman 62 | - Added Python 3.9 and 3.10 support, this includes fixing Python 3.10 compatibility, thanks to Torge Matthies 63 | - Improved the documentation, thanks to Takahiro Kojima 64 | - Improved the source distribution so that it can be used to build and install wheels, thanks to Janusz Skonieczny 65 | - Added requirements files for easier development, thanks to Greg Eremeev 66 | 67 | Backwards incompatible: 68 | 69 | - Removed Python 3.5 support 70 | 71 | 0.18.4 72 | ------ 73 | 74 | - Fixed a bug where only one of multiple NoInject annotations was interpreted 75 | 76 | 0.18.3 77 | ------ 78 | 79 | - Fixed Python 3.5.3 compatibility 80 | 81 | 0.18.2 82 | ------ 83 | 84 | - Added remaining type hints to the codebase so that the client code can have better static typing safety 85 | - Fixed UnsatisfiedRequirement string representation (this time for real) 86 | - Added forward return type reference support to provider methods 87 | 88 | 0.18.1 89 | ------ 90 | 91 | - Fixed UnsatisfiedRequirement instantiation (trying to get its string representation would fail) 92 | - Fixed injecting a subclass of a generic type on Python versions older than 3.7.0 93 | - Fixed regression that caused BoundKey injection failure 94 | 95 | 0.18.0 96 | ------ 97 | 98 | - Added new public :func:`get_bindings ` function to see what parameters will be injected 99 | into a function 100 | - Added new generic types using a draft implementation of `PEP 593 `_: 101 | :data:`Inject ` and :data:`NoInject `. Those serve as additional ways to 102 | declare (non)injectable parameters while :func:`inject ` won't go away any time soon 103 | :func:`noninjectable ` may be removed once `NoInject` is cofirmed to work. 104 | 105 | Backwards incompatible: 106 | 107 | - Removed previously deprecated `Key`, `BindingKey`, `SequenceKey` and `MappingKey` pseudo-types 108 | 109 | 0.17.0 110 | ------ 111 | 112 | - Added support for using `typing.Dict` and `typing.List` in multibindings. See :meth:`multibind `. 113 | - Added multibinding-specific :func:`provider ` variant: :func:`multiprovider ` 114 | - Deprecated using :func:`provider ` for multibindings 115 | - Fixed failure to provide a default value to a `NewType`-aliased type with auto_bind enabled 116 | - Deprecated :func:`Key `, :func:`SequenceKey ` and 117 | :func:`MappingKey ` – use real types or type aliases instead 118 | - Deprecated using single-item lists and dictionaries for multibindings - use real types or type aliases instead 119 | 120 | Technically backwards incompatible: 121 | 122 | - typing.List and typing.Dict specializations are now explicitly disallowed as :meth:`bind ` 123 | interfaces and types returned by :func:`provider `-decorated methods 124 | 125 | 0.16.2 126 | ------ 127 | 128 | - (Re)added support for decorating classes themselves with :func:`@inject `. This is the same 129 | as decorating their constructors. Among other things this gives us 130 | `dataclasses `_ integration. 131 | 132 | 0.16.1 133 | ------ 134 | 135 | - Reuploaded to fix incorrectly formatted project description 136 | 137 | 0.16.0 138 | ------ 139 | 140 | - Added support for overriding injectable parameters with positional arguments (previously only 141 | possible with keyword arguments) 142 | - Fixed crashes caused by typed self in method signatures 143 | - Improved typing coverage 144 | 145 | Backwards incompatible: 146 | 147 | - Dropped Python 3.4 support 148 | - Removed previously deprecated constructs: with_injector, Injector.install_into, Binder.bind_scope 149 | - Dependencies are no longer injected into Module.configure and raw module functions (previously 150 | deprecated) 151 | - Removed unofficial support for injecting into parent class constructors 152 | 153 | 0.15.0 154 | ------ 155 | 156 | - Added type information for Injector.create_object() (patch #101 thanks to David Pärsson) 157 | - Made the code easier to understand (patch #105 thanks to Christian Clauss) 158 | - Opted the package into distributing type information and checking it (PEP 561) 159 | 160 | 0.14.1 161 | ------ 162 | 163 | - Fixed regression that required all noninjectable parameters to be typed 164 | 165 | 0.14.0 166 | ------ 167 | 168 | - Added NewType support 169 | - Added type hints 170 | 171 | Backwards incompatible: 172 | 173 | - Passing invalid parameter names to @noninjectable() will now result in an error 174 | - Dropped Python 3.3 support 175 | 176 | 0.13.4 177 | ------ 178 | 179 | - Deprecated with_injector. There's no one migration path recommended, it depends on 180 | a particular case. 181 | - Deprecated install_into. 182 | 183 | 0.13.3 184 | ------ 185 | 186 | - Fixed a bug with classes deriving from PyQt classes not being able to be 187 | instantiated manually (bug #75, patch #76 thanks to David Pärsson) 188 | 189 | 0.13.2 190 | ------ 191 | 192 | - Fixed a bug with values shared between Injectors in a hierarchy (bugs #52 and #72) 193 | - Binding scopes explicitly (``Binder.bind_scope``) is no longer necessary and ``bind_scope`` is a no-op now. 194 | 195 | 0.13.1 196 | ------ 197 | 198 | - Improved some error messages 199 | 200 | 0.13.0 201 | ------ 202 | 203 | Backwards incompatible: 204 | 205 | - Dropped Python 3.2 support 206 | - Dropped Injector use_annotations constructor parameter. Whenever @inject is 207 | used parameter annotations will be used automatically. 208 | - Dropped Python 2 support (this includes PyPy) 209 | - Removed @provides decorator, use @provider instead 210 | - Removed support for passing keyword arguments to @inject 211 | 212 | 0.12.0 213 | ------ 214 | 215 | - Fixed binding inference in presence of * and ** arguments (previously Injector 216 | would generate extra arguments, now it just ignores them) 217 | - Improved error reporting 218 | - Fixed compatibility with newer typing versions (that includes the one 219 | bundled with Python 3.6) 220 | 221 | Technically backwards incompatible: 222 | 223 | - Forward references as PEP 484 understands them are being resolved now when 224 | Python 3-style annotations are used. See 225 | https://www.python.org/dev/peps/pep-0484/#forward-references for details. 226 | 227 | Optional parameters are treated as compulsory for the purpose of injection. 228 | 229 | 0.11.1 230 | ------ 231 | 232 | - 0.11.0 packages uploaded to PyPI are broken (can't be installed), this is 233 | a fix-only release. 234 | 235 | 0.11.0 236 | ------ 237 | 238 | * The following way to declare dependencies is introduced and recommended 239 | now: 240 | 241 | .. code-block:: python 242 | 243 | class SomeClass: 244 | @inject 245 | def __init__(self, other: OtherClass): 246 | # ... 247 | 248 | The following ways are still supported but are deprecated and will be 249 | removed in the future: 250 | 251 | .. code-block:: python 252 | 253 | # Python 2-compatible style 254 | class SomeClass 255 | @inject(other=OtherClass) 256 | def __init__(self, other): 257 | # ... 258 | 259 | # Python 3 style without @inject-decoration but with use_annotations 260 | class SomeClass: 261 | def __init__(self, other: OtherClass): 262 | # ... 263 | 264 | injector = Injector(use_annotations=True) 265 | # ... 266 | 267 | * The following way to declare Module provider methods is introduced and 268 | recommended now: 269 | 270 | .. code-block:: python 271 | 272 | class MyModule(Module): 273 | @provider 274 | def provide_something(self, dependency: Dependency) -> Something: 275 | # ... 276 | 277 | @provider implies @inject. 278 | 279 | Previously it would look like this: 280 | 281 | .. code-block:: python 282 | 283 | class MyModule(Module): 284 | @provides(Something) 285 | @inject 286 | def provide_something(self, dependency: Dependency): 287 | # ... 288 | 289 | The :func:`~injector.provides` decorator will be removed in the future. 290 | 291 | * Added a :func:`~injector.noninjectable` decorator to mark parameters as not injectable 292 | (this serves as documentation and a way to avoid some runtime errors) 293 | 294 | 295 | Backwards incompatible: 296 | 297 | * Removed support for decorating classes with :func:`@inject `. Previously: 298 | 299 | .. code-block:: python 300 | 301 | @inject(something=Something) 302 | class Class: 303 | pass 304 | 305 | Now: 306 | 307 | .. code-block:: python 308 | 309 | class Class: 310 | @inject 311 | def __init__(self, something: Something): 312 | self.something = something 313 | 314 | * Removed support for injecting partially applied functions, previously: 315 | 316 | .. code-block:: python 317 | 318 | @inject(something=Something) 319 | def some_function(something): 320 | pass 321 | 322 | 323 | class Class: 324 | @inject(function=some_function) 325 | def __init__(self, function): 326 | # ... 327 | 328 | Now you need to move the function with injectable dependencies to a class. 329 | 330 | * Removed support for getting :class:`AssistedBuilder(callable=...) ` 331 | * Dropped Python 2.6 support 332 | * Changed the way :class:`~injector.AssistedBuilder` and :class:`~injector.ProviderOf` are used. 333 | Previously: 334 | 335 | .. code-block:: python 336 | 337 | builder1 = injector.get(AssistedBuilder(Something)) 338 | # or: builder1 = injector.get(AssistedBuilder(interface=Something)) 339 | builder2 = injector.get(AssistedBuilder(cls=SomethingElse)) 340 | provider = injector.get(ProviderOf(SomeOtherThing)) 341 | 342 | Now: 343 | 344 | .. code-block:: python 345 | 346 | builder1 = injector.get(AssistedBuilder[Something]) 347 | builder2 = injector.get(ClassAssistedBuilder[cls=SomethingElse]) 348 | provider = injector.get(ProviderOf[SomeOtherThing]) 349 | 350 | * Removed support for injecting into non-constructor methods 351 | 352 | 0.10.1 353 | ------ 354 | 355 | - Fixed a false positive bug in dependency cycle detection (AssistedBuilder can be 356 | used to break dependency cycles now) 357 | 358 | 0.10.0 359 | ------ 360 | 361 | - :meth:`injector.Provider.get()` now requires an :class:`injector.Injector` instance as 362 | its parameter 363 | - deprecated injecting arguments into modules (be it functions/callables, 364 | :class:`~injector.Module` constructors or :meth:`injector.Module.configure` methods) 365 | - removed `extends` decorator 366 | - few classes got useful __repr__ implementations 367 | - fixed injecting ProviderOf and AssistedBuilders when :class:`injector.Injector` 368 | auto_bind is set to False (previously would result in `UnsatisfiedRequirement` 369 | error) 370 | - fixed crash occurring when Python 3-function annotation use is enabled and 371 | __init__ method has a return value annotation ("injector.UnknownProvider: 372 | couldn't determine provider for None to None"), should also apply to free 373 | functions as well 374 | 375 | 0.9.1 376 | ----- 377 | - Bug fix release. 378 | 379 | 0.9.0 380 | ----- 381 | 382 | - Child :class:`~injector.Injector` can rebind dependancies bound in parent Injector (that changes :class:`~injector.Provider` semantics), thanks to Ilya Orlov 383 | - :class:`~injector.CallableProvider` callables can be injected into, thanks to Ilya Strukov 384 | - One can request :class:`~injector.ProviderOf` (Interface) and get a :class:`~injector.BoundProvider` which can be used to get an implementation of Interface when needed 385 | 386 | 0.8.0 387 | ----- 388 | 389 | - Binding annotations are removed. Use :func:`~injector.Key` to create unique types instead. 390 | 391 | 392 | 0.7.9 393 | ----- 394 | 395 | - Fixed regression with injecting unbound key resulting in None instead of raising an exception 396 | 397 | 398 | 0.7.8 399 | ----- 400 | 401 | - Exception is raised when :class:`~injector.Injector` can't install itself into a class instance due to __slots__ presence 402 | - Some of exception messages are now more detailed to make debugging easier when injection fails 403 | - You can inject functions now - :class:`~injector.Injector` provides a wrapper that takes care of injecting dependencies into the original function 404 | 405 | 0.7.7 406 | ----- 407 | 408 | - Made :class:`~injector.AssistedBuilder` behave more explicitly: it can build either innstance of a concrete class (``AssistedBuilder(cls=Class)``) or it will follow Injector bindings (if exist) and construct instance of a class pointed by an interface (``AssistedBuilder(interface=Interface)``). ``AssistedBuilder(X)`` behaviour remains the same, it's equivalent to ``AssistedBuilder(interface=X)`` 409 | 410 | 0.7.6 411 | ----- 412 | 413 | - Auto-convert README.md to RST for PyPi. 414 | 415 | 0.7.5 416 | ----- 417 | 418 | - Added a ChangeLog! 419 | - Added support for using Python3 annotations as binding types. 420 | -------------------------------------------------------------------------------- /injector/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # 3 | # Copyright (C) 2010 Alec Thomas 4 | # All rights reserved. 5 | # 6 | # This software is licensed as described in the file COPYING, which 7 | # you should have received as part of this distribution. 8 | # 9 | # Author: Alec Thomas 10 | 11 | """Injector - Python dependency injection framework, inspired by Guice 12 | 13 | :copyright: (c) 2012 by Alec Thomas 14 | :license: BSD 15 | """ 16 | 17 | import functools 18 | import inspect 19 | import itertools 20 | import logging 21 | import sys 22 | import threading 23 | import types 24 | from abc import ABCMeta, abstractmethod 25 | from dataclasses import dataclass 26 | from typing import ( 27 | TYPE_CHECKING, 28 | Any, 29 | Callable, 30 | Dict, 31 | Generator, 32 | Generic, 33 | Iterable, 34 | List, 35 | Optional, 36 | Set, 37 | Tuple, 38 | Type, 39 | TypeVar, 40 | Union, 41 | cast, 42 | get_args, 43 | overload, 44 | ) 45 | 46 | try: 47 | from typing import NoReturn 48 | except ImportError: 49 | from typing_extensions import NoReturn 50 | 51 | # This is a messy, type-wise, because we not only have two potentially conflicting imports here 52 | # The easiest way to make mypy happy here is to tell it the versions from typing_extensions are 53 | # canonical. Since this typing_extensions import is only for mypy it'll work even without 54 | # typing_extensions actually installed so all's good. 55 | if TYPE_CHECKING: 56 | from typing_extensions import Annotated, _AnnotatedAlias, get_type_hints 57 | else: 58 | # Ignoring errors here as typing_extensions stub doesn't know about those things yet 59 | try: 60 | from typing import Annotated, _AnnotatedAlias, get_type_hints 61 | except ImportError: 62 | from typing_extensions import Annotated, _AnnotatedAlias, get_type_hints 63 | 64 | 65 | __author__ = 'Alec Thomas ' 66 | __version__ = '0.23.0' 67 | __version_tag__ = '' 68 | 69 | log = logging.getLogger('injector') 70 | log.addHandler(logging.NullHandler()) 71 | 72 | if log.level == logging.NOTSET: 73 | log.setLevel(logging.WARN) 74 | 75 | T = TypeVar('T') 76 | K = TypeVar('K') 77 | V = TypeVar('V') 78 | 79 | 80 | def private(something: T) -> T: 81 | something.__private__ = True # type: ignore 82 | return something 83 | 84 | 85 | CallableT = TypeVar('CallableT', bound=Callable) 86 | 87 | 88 | def synchronized(lock: threading.RLock) -> Callable[[CallableT], CallableT]: 89 | def outside_wrapper(function: CallableT) -> CallableT: 90 | @functools.wraps(function) 91 | def wrapper(*args: Any, **kwargs: Any) -> Any: 92 | with lock: 93 | return function(*args, **kwargs) 94 | 95 | return cast(CallableT, wrapper) 96 | 97 | return outside_wrapper 98 | 99 | 100 | lock = threading.RLock() 101 | 102 | 103 | _inject_marker = object() 104 | _noinject_marker = object() 105 | 106 | InjectT = TypeVar('InjectT') 107 | Inject = Annotated[InjectT, _inject_marker] 108 | """An experimental way to declare injectable dependencies utilizing a `PEP 593`_ implementation 109 | in Python 3.9 and backported to Python 3.7+ in `typing_extensions`. 110 | 111 | Those two declarations are equivalent:: 112 | 113 | @inject 114 | def fun(t: SomeType) -> None: 115 | pass 116 | 117 | def fun(t: Inject[SomeType]) -> None: 118 | pass 119 | 120 | The advantage over using :func:`inject` is that if you have some noninjectable parameters 121 | it may be easier to spot what are they. Those two are equivalent:: 122 | 123 | @inject 124 | @noninjectable('s') 125 | def fun(t: SomeType, s: SomeOtherType) -> None: 126 | pass 127 | 128 | def fun(t: Inject[SomeType], s: SomeOtherType) -> None: 129 | pass 130 | 131 | .. seealso:: 132 | 133 | Function :func:`get_bindings` 134 | A way to inspect how various injection declarations interact with each other. 135 | 136 | .. versionadded:: 0.18.0 137 | .. note:: Requires Python 3.7+. 138 | .. note:: 139 | 140 | If you're using mypy you need the version 0.750 or newer to fully type-check code using this 141 | construct. 142 | 143 | .. _PEP 593: https://www.python.org/dev/peps/pep-0593/ 144 | .. _typing_extensions: https://pypi.org/project/typing-extensions/ 145 | """ 146 | 147 | NoInject = Annotated[InjectT, _noinject_marker] 148 | """An experimental way to declare noninjectable dependencies utilizing a `PEP 593`_ implementation 149 | in Python 3.9 and backported to Python 3.7+ in `typing_extensions`. 150 | 151 | Since :func:`inject` declares all function's parameters to be injectable there needs to be a way 152 | to opt out of it. This has been provided by :func:`noninjectable` but `noninjectable` suffers from 153 | two issues: 154 | 155 | * You need to repeat the parameter name 156 | * The declaration may be relatively distance in space from the actual parameter declaration, thus 157 | hindering readability 158 | 159 | `NoInject` solves both of those concerns, for example (those two declarations are equivalent):: 160 | 161 | @inject 162 | @noninjectable('b') 163 | def fun(a: TypeA, b: TypeB) -> None: 164 | pass 165 | 166 | @inject 167 | def fun(a: TypeA, b: NoInject[TypeB]) -> None: 168 | pass 169 | 170 | .. seealso:: 171 | 172 | Function :func:`get_bindings` 173 | A way to inspect how various injection declarations interact with each other. 174 | 175 | .. versionadded:: 0.18.0 176 | .. note:: Requires Python 3.7+. 177 | .. note:: 178 | 179 | If you're using mypy you need the version 0.750 or newer to fully type-check code using this 180 | construct. 181 | 182 | .. _PEP 593: https://www.python.org/dev/peps/pep-0593/ 183 | .. _typing_extensions: https://pypi.org/project/typing-extensions/ 184 | """ 185 | 186 | 187 | def reraise(original: Exception, exception: Exception, maximum_frames: int = 1) -> NoReturn: 188 | prev_cls, prev, tb = sys.exc_info() 189 | frames = inspect.getinnerframes(cast(types.TracebackType, tb)) 190 | if len(frames) > maximum_frames: 191 | exception = original 192 | raise exception.with_traceback(tb) 193 | 194 | 195 | class Error(Exception): 196 | """Base exception.""" 197 | 198 | 199 | class UnsatisfiedRequirement(Error): 200 | """Requirement could not be satisfied.""" 201 | 202 | def __init__(self, owner: Optional[object], interface: type) -> None: 203 | super().__init__(owner, interface) 204 | self.owner = owner 205 | self.interface = interface 206 | 207 | def __str__(self) -> str: 208 | on = '%s has an ' % _describe(self.owner) if self.owner else '' 209 | return '%sunsatisfied requirement on %s' % (on, _describe(self.interface)) 210 | 211 | 212 | class CallError(Error): 213 | """Call to callable object fails.""" 214 | 215 | def __str__(self) -> str: 216 | if len(self.args) == 1: 217 | return self.args[0] 218 | 219 | instance, method, args, kwargs, original_error, stack = self.args 220 | cls = instance.__class__.__name__ if instance is not None else '' 221 | 222 | full_method = '.'.join((cls, method.__name__)).strip('.') 223 | 224 | parameters = ', '.join( 225 | itertools.chain( 226 | (repr(arg) for arg in args), ('%s=%r' % (key, value) for (key, value) in kwargs.items()) 227 | ) 228 | ) 229 | return 'Call to %s(%s) failed: %s (injection stack: %r)' % ( 230 | full_method, 231 | parameters, 232 | original_error, 233 | [level[0] for level in stack], 234 | ) 235 | 236 | 237 | class CircularDependency(Error): 238 | """Circular dependency detected.""" 239 | 240 | 241 | class UnknownProvider(Error): 242 | """Tried to bind to a type whose provider couldn't be determined.""" 243 | 244 | 245 | class UnknownArgument(Error): 246 | """Tried to mark an unknown argument as noninjectable.""" 247 | 248 | 249 | class InvalidInterface(Error): 250 | """Cannot bind to the specified interface.""" 251 | 252 | 253 | class Provider(Generic[T]): 254 | """Provides class instances.""" 255 | 256 | __metaclass__ = ABCMeta 257 | 258 | @abstractmethod 259 | def get(self, injector: 'Injector') -> T: 260 | raise NotImplementedError # pragma: no cover 261 | 262 | 263 | class ClassProvider(Provider, Generic[T]): 264 | """Provides instances from a given class, created using an Injector.""" 265 | 266 | def __init__(self, cls: Type[T]) -> None: 267 | self._cls = cls 268 | 269 | def get(self, injector: 'Injector') -> T: 270 | return injector.create_object(self._cls) 271 | 272 | 273 | class CallableProvider(Provider, Generic[T]): 274 | """Provides something using a callable. 275 | 276 | The callable is called every time new value is requested from the provider. 277 | 278 | There's no need to explicitly use :func:`inject` or :data:`Inject` with the callable as it's 279 | assumed that, if the callable has annotated parameters, they're meant to be provided 280 | automatically. It wouldn't make sense any other way, as there's no mechanism to provide 281 | parameters to the callable at a later time, so either they'll be injected or there'll be 282 | a `CallError`. 283 | 284 | :: 285 | 286 | >>> class MyClass: 287 | ... def __init__(self, value: int) -> None: 288 | ... self.value = value 289 | ... 290 | >>> def factory(): 291 | ... print('providing') 292 | ... return MyClass(42) 293 | ... 294 | >>> def configure(binder): 295 | ... binder.bind(MyClass, to=CallableProvider(factory)) 296 | ... 297 | >>> injector = Injector(configure) 298 | >>> injector.get(MyClass) is injector.get(MyClass) 299 | providing 300 | providing 301 | False 302 | """ 303 | 304 | def __init__(self, callable: Callable[..., T]): 305 | self._callable = callable 306 | 307 | def get(self, injector: 'Injector') -> T: 308 | return injector.call_with_injection(self._callable) 309 | 310 | def __repr__(self) -> str: 311 | return '%s(%r)' % (type(self).__name__, self._callable) 312 | 313 | 314 | class InstanceProvider(Provider, Generic[T]): 315 | """Provide a specific instance. 316 | 317 | :: 318 | 319 | >>> class MyType: 320 | ... def __init__(self): 321 | ... self.contents = [] 322 | >>> def configure(binder): 323 | ... binder.bind(MyType, to=InstanceProvider(MyType())) 324 | ... 325 | >>> injector = Injector(configure) 326 | >>> injector.get(MyType) is injector.get(MyType) 327 | True 328 | >>> injector.get(MyType).contents.append('x') 329 | >>> injector.get(MyType).contents 330 | ['x'] 331 | """ 332 | 333 | def __init__(self, instance: T) -> None: 334 | self._instance = instance 335 | 336 | def get(self, injector: 'Injector') -> T: 337 | return self._instance 338 | 339 | def __repr__(self) -> str: 340 | return '%s(%r)' % (type(self).__name__, self._instance) 341 | 342 | 343 | @private 344 | class MultiBinder(Provider, Generic[T]): 345 | """Provide a list of instances via other Providers.""" 346 | 347 | __metaclass__ = ABCMeta 348 | 349 | _multi_bindings: List['Binding'] 350 | 351 | def __init__(self, parent: 'Binder') -> None: 352 | self._multi_bindings = [] 353 | self._binder = Binder(parent.injector, auto_bind=False, parent=parent) 354 | 355 | @abstractmethod 356 | def multibind( 357 | self, interface: type, to: Any, scope: Union['ScopeDecorator', Type['Scope'], None] 358 | ) -> None: 359 | raise NotImplementedError 360 | 361 | def append(self, provider: Provider[T], scope: Type['Scope']) -> None: 362 | # HACK: generate a pseudo-type for this element in the list. 363 | # This is needed for scopes to work properly. Some, like the Singleton scope, 364 | # key instances by type, so we need one that is unique to this binding. 365 | pseudo_type = type(f"multibind-type-{id(provider)}", (provider.__class__,), {}) 366 | self._multi_bindings.append(Binding(pseudo_type, provider, scope)) 367 | 368 | def get_scoped_providers(self, injector: 'Injector') -> Generator[Provider[T], None, None]: 369 | for binding in self._multi_bindings: 370 | scope_binding, _ = self._binder.get_binding(binding.scope) 371 | scope_instance: Scope = scope_binding.provider.get(injector) 372 | provider_instance = scope_instance.get(binding.interface, binding.provider) 373 | yield provider_instance 374 | 375 | def __repr__(self) -> str: 376 | return '%s(%r)' % (type(self).__name__, self._multi_bindings) 377 | 378 | 379 | class MultiBindProvider(MultiBinder[List[T]]): 380 | """Used by :meth:`Binder.multibind` to flatten results of providers that 381 | return sequences.""" 382 | 383 | def multibind( 384 | self, interface: type, to: Any, scope: Union['ScopeDecorator', Type['Scope'], None] 385 | ) -> None: 386 | try: 387 | element_type = get_args(_punch_through_alias(interface))[0] 388 | except IndexError: 389 | raise InvalidInterface(f"Use typing.List[T] or list[T] to specify the element type of the list") 390 | if isinstance(to, list): 391 | for element in to: 392 | element_binding = self._binder.create_binding(element_type, element, scope) 393 | self.append(element_binding.provider, element_binding.scope) 394 | else: 395 | element_binding = self._binder.create_binding(interface, to, scope) 396 | self.append(element_binding.provider, element_binding.scope) 397 | 398 | def get(self, injector: 'Injector') -> List[T]: 399 | result: List[T] = [] 400 | for provider in self.get_scoped_providers(injector): 401 | instances: List[T] = _ensure_iterable(provider.get(injector)) 402 | result.extend(instances) 403 | return result 404 | 405 | 406 | class MapBindProvider(MultiBinder[Dict[str, T]]): 407 | """A provider for map bindings.""" 408 | 409 | def multibind( 410 | self, interface: type, to: Any, scope: Union['ScopeDecorator', Type['Scope'], None] 411 | ) -> None: 412 | try: 413 | value_type = get_args(_punch_through_alias(interface))[1] 414 | except IndexError: 415 | raise InvalidInterface( 416 | f"Use typing.Dict[K, V] or dict[K, V] to specify the value type of the dict" 417 | ) 418 | if isinstance(to, dict): 419 | for key, value in to.items(): 420 | element_binding = self._binder.create_binding(value_type, value, scope) 421 | self.append(KeyValueProvider(key, element_binding.provider), element_binding.scope) 422 | else: 423 | element_binding = self._binder.create_binding(interface, to, scope) 424 | self.append(element_binding.provider, element_binding.scope) 425 | 426 | def get(self, injector: 'Injector') -> Dict[str, T]: 427 | map: Dict[str, T] = {} 428 | for provider in self.get_scoped_providers(injector): 429 | map.update(provider.get(injector)) 430 | return map 431 | 432 | 433 | @private 434 | class KeyValueProvider(Provider[Dict[str, T]]): 435 | def __init__(self, key: str, inner_provider: Provider[T]) -> None: 436 | self._key = key 437 | self._provider = inner_provider 438 | 439 | def get(self, injector: 'Injector') -> Dict[str, T]: 440 | return {self._key: self._provider.get(injector)} 441 | 442 | 443 | @dataclass 444 | class _BindingBase: 445 | interface: type 446 | provider: Provider 447 | scope: Type['Scope'] 448 | 449 | 450 | @private 451 | class Binding(_BindingBase): 452 | """A binding from an (interface,) to a provider in a scope.""" 453 | 454 | def is_multibinding(self) -> bool: 455 | return _get_origin(_punch_through_alias(self.interface)) in {dict, list} 456 | 457 | 458 | @private 459 | class ImplicitBinding(Binding): 460 | """A binding that was created implicitly by auto-binding.""" 461 | 462 | pass 463 | 464 | 465 | _InstallableModuleType = Union[Callable[['Binder'], None], 'Module', Type['Module']] 466 | 467 | 468 | class Binder: 469 | """Bind interfaces to implementations. 470 | 471 | .. note:: This class is instantiated internally for you and there's no need 472 | to instantiate it on your own. 473 | """ 474 | 475 | _bindings: Dict[type, Binding] 476 | 477 | @private 478 | def __init__( 479 | self, injector: 'Injector', auto_bind: bool = True, parent: Optional['Binder'] = None 480 | ) -> None: 481 | """Create a new Binder. 482 | 483 | :param injector: Injector we are binding for. 484 | :param auto_bind: Whether to automatically bind missing types. 485 | :param parent: Parent binder. 486 | """ 487 | self.injector = injector 488 | self._auto_bind = auto_bind 489 | self._bindings = {} 490 | self.parent = parent 491 | 492 | def bind( 493 | self, 494 | interface: Type[T], 495 | to: Union[None, T, Callable[..., T], Provider[T]] = None, 496 | scope: Union[None, Type['Scope'], 'ScopeDecorator'] = None, 497 | ) -> None: 498 | """Bind an interface to an implementation. 499 | 500 | Binding `T` to an instance of `T` like 501 | 502 | :: 503 | 504 | binder.bind(A, to=A('some', 'thing')) 505 | 506 | is, for convenience, a shortcut for 507 | 508 | :: 509 | 510 | binder.bind(A, to=InstanceProvider(A('some', 'thing'))). 511 | 512 | Likewise, binding to a callable like 513 | 514 | :: 515 | 516 | binder.bind(A, to=some_callable) 517 | 518 | is a shortcut for 519 | 520 | :: 521 | 522 | binder.bind(A, to=CallableProvider(some_callable)) 523 | 524 | and, as such, if `some_callable` there has any annotated parameters they'll be provided 525 | automatically without having to use :func:`inject` or :data:`Inject` with the callable. 526 | 527 | `typing.List` and `typing.Dict` instances are reserved for multibindings and trying to bind them 528 | here will result in an error (use :meth:`multibind` instead):: 529 | 530 | binder.bind(List[str], to=['hello', 'there']) # Error 531 | 532 | :param interface: Type to bind. 533 | :param to: Instance or class to bind to, or an instance of 534 | :class:`Provider` subclass. 535 | :param scope: Optional :class:`Scope` in which to bind. 536 | """ 537 | if _get_origin(_punch_through_alias(interface)) in {dict, list}: 538 | raise Error( 539 | 'Type %s is reserved for multibindings. Use multibind instead of bind.' % (interface,) 540 | ) 541 | self._bindings[interface] = self.create_binding(interface, to, scope) 542 | 543 | @overload 544 | def multibind( 545 | self, 546 | interface: Type[List[T]], 547 | to: Union[List[Union[T, Type[T]]], Callable[..., List[T]], Provider[List[T]], Type[T]], 548 | scope: Union[Type['Scope'], 'ScopeDecorator', None] = None, 549 | ) -> None: # pragma: no cover 550 | pass 551 | 552 | @overload 553 | def multibind( 554 | self, 555 | interface: Type[Dict[K, V]], 556 | to: Union[Dict[K, Union[V, Type[V]]], Callable[..., Dict[K, V]], Provider[Dict[K, V]]], 557 | scope: Union[Type['Scope'], 'ScopeDecorator', None] = None, 558 | ) -> None: # pragma: no cover 559 | pass 560 | 561 | def multibind( 562 | self, interface: type, to: Any, scope: Union['ScopeDecorator', Type['Scope'], None] = None 563 | ) -> None: 564 | """Creates or extends a multi-binding. 565 | 566 | A multi-binding contributes values to a list or to a dictionary. For example:: 567 | 568 | binder.multibind(list[Interface], to=A) 569 | binder.multibind(list[Interface], to=[B, C()]) 570 | injector.get(list[Interface]) 571 | # [, , ] 572 | 573 | binder.multibind(dict[str, Interface], to={'key': A}) 574 | binder.multibind(dict[str, Interface], to={'other_key': B}) 575 | injector.get(dict[str, Interface]) 576 | # {'key': , 'other_key': } 577 | 578 | .. versionchanged:: 0.17.0 579 | Added support for using `typing.Dict` and `typing.List` instances as interfaces. 580 | Deprecated support for `MappingKey`, `SequenceKey` and single-item lists and 581 | dictionaries as interfaces. 582 | 583 | :param interface: A generic list[T] or dict[str, T] type to bind to. 584 | 585 | :param to: A list/dict to bind to, where the values are either instances or classes implementing T. 586 | Can also be an explicit :class:`Provider` or a callable that returns a list/dict. 587 | For lists, this can also be a class implementing T (e.g. multibind(list[T], to=A)) 588 | 589 | :param scope: Optional Scope in which to bind. 590 | """ 591 | multi_binder = self._get_multi_binder(interface) 592 | multi_binder.multibind(interface, to, scope) 593 | 594 | def _get_multi_binder(self, interface: type) -> MultiBinder: 595 | multi_binder: MultiBinder 596 | if interface not in self._bindings: 597 | if ( 598 | isinstance(interface, dict) 599 | or isinstance(interface, type) 600 | and issubclass(interface, dict) 601 | or _get_origin(_punch_through_alias(interface)) is dict 602 | ): 603 | multi_binder = MapBindProvider(self) 604 | else: 605 | multi_binder = MultiBindProvider(self) 606 | binding = self.create_binding(interface, multi_binder) 607 | self._bindings[interface] = binding 608 | else: 609 | binding = self._bindings[interface] 610 | assert isinstance(binding.provider, MultiBinder) 611 | multi_binder = binding.provider 612 | 613 | return multi_binder 614 | 615 | def install(self, module: _InstallableModuleType) -> None: 616 | """Install a module into this binder. 617 | 618 | In this context the module is one of the following: 619 | 620 | * function taking the :class:`Binder` as its only parameter 621 | 622 | :: 623 | 624 | def configure(binder): 625 | bind(str, to='s') 626 | 627 | binder.install(configure) 628 | 629 | * instance of :class:`Module` (instance of its subclass counts) 630 | 631 | :: 632 | 633 | class MyModule(Module): 634 | def configure(self, binder): 635 | binder.bind(str, to='s') 636 | 637 | binder.install(MyModule()) 638 | 639 | * subclass of :class:`Module` - the subclass needs to be instantiable so if it 640 | expects any parameters they need to be injected 641 | 642 | :: 643 | 644 | binder.install(MyModule) 645 | """ 646 | if type(module) is type and issubclass(cast(type, module), Module): 647 | instance = cast(type, module)() 648 | else: 649 | instance = module 650 | instance(self) 651 | 652 | def create_binding( 653 | self, interface: type, to: Any = None, scope: Union['ScopeDecorator', Type['Scope'], None] = None 654 | ) -> Binding: 655 | provider = self.provider_for(interface, to) 656 | scope = scope or getattr(to or interface, '__scope__', None) 657 | if isinstance(scope, ScopeDecorator): 658 | scope = scope.scope 659 | return Binding(interface, provider, scope or NoScope) 660 | 661 | def provider_for(self, interface: Any, to: Any = None) -> Provider: 662 | base_type = _punch_through_alias(interface) 663 | origin = _get_origin(base_type) 664 | 665 | if interface is Any: 666 | raise TypeError('Injecting Any is not supported') 667 | elif _is_specialization(interface, ProviderOf): 668 | (target,) = interface.__args__ 669 | if to is not None: 670 | raise Exception('ProviderOf cannot be bound to anything') 671 | return InstanceProvider(ProviderOf(self.injector, target)) 672 | elif isinstance(to, Provider): 673 | return to 674 | elif isinstance( 675 | to, 676 | ( 677 | types.FunctionType, 678 | types.LambdaType, 679 | types.MethodType, 680 | types.BuiltinFunctionType, 681 | types.BuiltinMethodType, 682 | ), 683 | ): 684 | return CallableProvider(to) 685 | elif issubclass(type(to), type): 686 | return ClassProvider(cast(type, to)) 687 | elif isinstance(interface, BoundKey): 688 | 689 | def proxy(injector: Injector) -> Any: 690 | binder = injector.binder 691 | kwarg_providers = { 692 | name: binder.provider_for(None, provider) for (name, provider) in interface.kwargs.items() 693 | } 694 | kwargs = {name: provider.get(injector) for (name, provider) in kwarg_providers.items()} 695 | return interface.interface(**kwargs) 696 | 697 | return CallableProvider(inject(proxy)) 698 | elif _is_specialization(interface, AssistedBuilder): 699 | (target,) = interface.__args__ 700 | builder = interface(self.injector, target) 701 | return InstanceProvider(builder) 702 | elif ( 703 | origin is None 704 | and isinstance(base_type, (tuple, type)) 705 | and interface is not Any 706 | and isinstance(to, base_type) 707 | or origin in {dict, list} 708 | and isinstance(to, origin) 709 | ): 710 | return InstanceProvider(to) 711 | elif issubclass(type(base_type), type) or isinstance(base_type, (tuple, list)): 712 | if to is not None: 713 | return InstanceProvider(to) 714 | return ClassProvider(base_type) 715 | 716 | else: 717 | raise UnknownProvider('couldn\'t determine provider for %r to %r' % (interface, to)) 718 | 719 | def _get_binding(self, key: type, *, only_this_binder: bool = False) -> Tuple[Binding, 'Binder']: 720 | binding = self._bindings.get(key) 721 | if binding: 722 | return binding, self 723 | if self.parent and not only_this_binder: 724 | return self.parent._get_binding(key) 725 | 726 | raise KeyError 727 | 728 | def get_binding(self, interface: type) -> Tuple[Binding, 'Binder']: 729 | is_scope = isinstance(interface, type) and issubclass(interface, Scope) 730 | is_assisted_builder = _is_specialization(interface, AssistedBuilder) 731 | try: 732 | return self._get_binding(interface, only_this_binder=is_scope or is_assisted_builder) 733 | except (KeyError, UnsatisfiedRequirement): 734 | if is_scope: 735 | scope = interface 736 | self.bind(scope, to=scope(self.injector)) 737 | return self._get_binding(interface) 738 | # The special interface is added here so that requesting a special 739 | # interface with auto_bind disabled works 740 | if self._auto_bind or self._is_special_interface(interface): 741 | binding = ImplicitBinding(**self.create_binding(interface).__dict__) 742 | self._bindings[interface] = binding 743 | return binding, self 744 | 745 | raise UnsatisfiedRequirement(None, interface) 746 | 747 | def has_binding_for(self, interface: type) -> bool: 748 | return interface in self._bindings 749 | 750 | def has_explicit_binding_for(self, interface: type) -> bool: 751 | return self.has_binding_for(interface) and not isinstance(self._bindings[interface], ImplicitBinding) 752 | 753 | def _is_special_interface(self, interface: type) -> bool: 754 | # "Special" interfaces are ones that you cannot bind yourself but 755 | # you can request them (for example you cannot bind ProviderOf(SomeClass) 756 | # to anything but you can inject ProviderOf(SomeClass) just fine 757 | return any(_is_specialization(interface, cls) for cls in [AssistedBuilder, ProviderOf]) 758 | 759 | 760 | def _is_specialization(cls: type, generic_class: Any) -> bool: 761 | # Starting with typing 3.5.3/Python 3.6 it is no longer necessarily true that 762 | # issubclass(SomeGeneric[X], SomeGeneric) so we need some other way to 763 | # determine whether a particular object is a generic class with type parameters 764 | # provided. Fortunately there seems to be __origin__ attribute that's useful here. 765 | 766 | # We need to special-case Annotated as its __origin__ behaves differently than 767 | # other typing generic classes. See https://github.com/python/typing/pull/635 768 | # for some details. 769 | if generic_class is Annotated and isinstance(cls, _AnnotatedAlias): 770 | return True 771 | 772 | if not hasattr(cls, '__origin__'): 773 | return False 774 | origin = cast(Any, cls).__origin__ 775 | if not inspect.isclass(generic_class): 776 | generic_class = type(generic_class) 777 | if not inspect.isclass(origin): 778 | origin = type(origin) 779 | # __origin__ is generic_class is a special case to handle Union as 780 | # Union cannot be used in issubclass() check (it raises an exception 781 | # by design). 782 | return origin is generic_class or issubclass(origin, generic_class) 783 | 784 | 785 | def _ensure_iterable(item_or_list: Union[T, List[T]]) -> List[T]: 786 | if isinstance(item_or_list, list): 787 | return item_or_list 788 | return [item_or_list] 789 | 790 | 791 | def _punch_through_alias(type_: Any) -> type: 792 | if ( 793 | sys.version_info < (3, 10) 794 | and getattr(type_, '__qualname__', '') == 'NewType..new_type' 795 | or sys.version_info >= (3, 10) 796 | and type(type_).__module__ == 'typing' 797 | and type(type_).__name__ == 'NewType' 798 | ): 799 | return type_.__supertype__ 800 | elif isinstance(type_, _AnnotatedAlias) and getattr(type_, '__metadata__', None) is not None: 801 | return type_.__origin__ 802 | else: 803 | return type_ 804 | 805 | 806 | def _get_origin(type_: type) -> Optional[type]: 807 | origin = getattr(type_, '__origin__', None) 808 | # Older typing behaves differently there and stores Dict and List as origin, we need to be flexible. 809 | if origin is List: 810 | return list 811 | elif origin is Dict: 812 | return dict 813 | return origin 814 | 815 | 816 | class Scope: 817 | """A Scope looks up the Provider for a binding. 818 | 819 | By default (ie. :class:`NoScope` ) this simply returns the default 820 | :class:`Provider` . 821 | """ 822 | 823 | __metaclass__ = ABCMeta 824 | 825 | def __init__(self, injector: 'Injector') -> None: 826 | self.injector = injector 827 | self.configure() 828 | 829 | def configure(self) -> None: 830 | """Configure the scope.""" 831 | 832 | @abstractmethod 833 | def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]: 834 | """Get a :class:`Provider` for a key. 835 | 836 | :param key: The key to return a provider for. 837 | :param provider: The default Provider associated with the key. 838 | :returns: A Provider instance that can provide an instance of key. 839 | """ 840 | raise NotImplementedError # pragma: no cover 841 | 842 | 843 | class ScopeDecorator: 844 | def __init__(self, scope: Type[Scope]) -> None: 845 | self.scope = scope 846 | 847 | def __call__(self, cls: T) -> T: 848 | cast(Any, cls).__scope__ = self.scope 849 | binding = getattr(cls, '__binding__', None) 850 | if binding: 851 | new_binding = Binding(interface=binding.interface, provider=binding.provider, scope=self.scope) 852 | setattr(cls, '__binding__', new_binding) 853 | return cls 854 | 855 | def __repr__(self) -> str: 856 | return 'ScopeDecorator(%s)' % self.scope.__name__ 857 | 858 | 859 | class NoScope(Scope): 860 | """An unscoped provider.""" 861 | 862 | def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]: 863 | return provider 864 | 865 | 866 | noscope = ScopeDecorator(NoScope) 867 | 868 | 869 | class SingletonScope(Scope): 870 | """A :class:`Scope` that returns a per-Injector instance for a key. 871 | 872 | :data:`singleton` can be used as a convenience class decorator. 873 | 874 | >>> class A: pass 875 | >>> injector = Injector() 876 | >>> provider = ClassProvider(A) 877 | >>> singleton = SingletonScope(injector) 878 | >>> a = singleton.get(A, provider) 879 | >>> b = singleton.get(A, provider) 880 | >>> a is b 881 | True 882 | """ 883 | 884 | _context: Dict[type, Provider] 885 | 886 | def configure(self) -> None: 887 | self._context = {} 888 | 889 | @synchronized(lock) 890 | def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]: 891 | try: 892 | return self._context[key] 893 | except KeyError: 894 | instance = self._get_instance(key, provider, self.injector) 895 | provider = InstanceProvider(instance) 896 | self._context[key] = provider 897 | return provider 898 | 899 | def _get_instance(self, key: Type[T], provider: Provider[T], injector: 'Injector') -> T: 900 | if injector.parent and not injector.binder.has_explicit_binding_for(key): 901 | try: 902 | return self._get_instance_from_parent(key, provider, injector.parent) 903 | except (CallError, UnsatisfiedRequirement): 904 | pass 905 | return provider.get(injector) 906 | 907 | def _get_instance_from_parent(self, key: Type[T], provider: Provider[T], parent: 'Injector') -> T: 908 | singleton_scope_binding, _ = parent.binder.get_binding(type(self)) 909 | singleton_scope = singleton_scope_binding.provider.get(parent) 910 | provider = singleton_scope.get(key, provider) 911 | return provider.get(parent) 912 | 913 | 914 | singleton = ScopeDecorator(SingletonScope) 915 | 916 | 917 | class ThreadLocalScope(Scope): 918 | """A :class:`Scope` that returns a per-thread instance for a key.""" 919 | 920 | def configure(self) -> None: 921 | self._locals = threading.local() 922 | 923 | def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]: 924 | try: 925 | return getattr(self._locals, repr(key)) 926 | except AttributeError: 927 | provider = InstanceProvider(provider.get(self.injector)) 928 | setattr(self._locals, repr(key), provider) 929 | return provider 930 | 931 | 932 | threadlocal = ScopeDecorator(ThreadLocalScope) 933 | 934 | 935 | class Module: 936 | """Configures injector and providers.""" 937 | 938 | def __call__(self, binder: Binder) -> None: 939 | """Configure the binder.""" 940 | self.__injector__ = binder.injector 941 | for unused_name, function in inspect.getmembers(self, inspect.ismethod): 942 | binding = None 943 | if hasattr(function, '__binding__'): 944 | binding = function.__binding__ 945 | if binding.interface == '__deferred__': 946 | # We could not evaluate a forward reference at @provider-decoration time, we need to 947 | # try again now. 948 | try: 949 | annotations = get_type_hints(function) 950 | except NameError as e: 951 | raise NameError( 952 | 'Cannot avaluate forward reference annotation(s) in method %r belonging to %r: %s' 953 | % (function.__name__, type(self), e) 954 | ) from e 955 | return_type = annotations['return'] 956 | binding = cast(Any, function.__func__).__binding__ = Binding( 957 | interface=return_type, provider=binding.provider, scope=binding.scope 958 | ) 959 | bind_method = binder.multibind if binding.is_multibinding() else binder.bind 960 | bind_method( # type: ignore 961 | binding.interface, to=types.MethodType(binding.provider, self), scope=binding.scope 962 | ) 963 | self.configure(binder) 964 | 965 | def configure(self, binder: Binder) -> None: 966 | """Override to configure bindings.""" 967 | 968 | 969 | class Injector: 970 | """ 971 | :param modules: Optional - a configuration module or iterable of configuration modules. 972 | Each module will be installed in current :class:`Binder` using :meth:`Binder.install`. 973 | 974 | Consult :meth:`Binder.install` documentation for the details. 975 | 976 | :param auto_bind: Whether to automatically bind missing types. 977 | :param parent: Parent injector. 978 | 979 | .. versionadded:: 0.7.5 980 | ``use_annotations`` parameter 981 | 982 | .. versionchanged:: 0.13.0 983 | ``use_annotations`` parameter is removed 984 | """ 985 | 986 | _stack: Tuple[Tuple[object, Callable, Tuple[Tuple[str, type], ...]], ...] 987 | binder: Binder 988 | 989 | def __init__( 990 | self, 991 | modules: Union[_InstallableModuleType, Iterable[_InstallableModuleType], None] = None, 992 | auto_bind: bool = True, 993 | parent: Optional['Injector'] = None, 994 | ) -> None: 995 | # Stack of keys currently being injected. Used to detect circular 996 | # dependencies. 997 | self._stack = () 998 | 999 | self.parent = parent 1000 | 1001 | # Binder 1002 | self.binder = Binder(self, auto_bind=auto_bind, parent=parent.binder if parent is not None else None) 1003 | 1004 | if not modules: 1005 | modules = [] 1006 | elif not hasattr(modules, '__iter__'): 1007 | modules = [cast(_InstallableModuleType, modules)] 1008 | # This line is needed to pelase mypy. We know we have Iteable of modules here. 1009 | modules = cast(Iterable[_InstallableModuleType], modules) 1010 | 1011 | # Bind some useful types 1012 | self.binder.bind(Injector, to=self) 1013 | self.binder.bind(Binder, to=self.binder) 1014 | 1015 | # Initialise modules 1016 | for module in modules: 1017 | self.binder.install(module) 1018 | 1019 | @property 1020 | def _log_prefix(self) -> str: 1021 | return '>' * (len(self._stack) + 1) + ' ' 1022 | 1023 | @synchronized(lock) 1024 | def get(self, interface: Type[T], scope: Union[ScopeDecorator, Type[Scope], None] = None) -> T: 1025 | """Get an instance of the given interface. 1026 | 1027 | .. note:: 1028 | 1029 | Although this method is part of :class:`Injector`'s public interface 1030 | it's meant to be used in limited set of circumstances. 1031 | 1032 | For example, to create some kind of root object (application object) 1033 | of your application (note that only one `get` call is needed, 1034 | inside the `Application` class and any of its dependencies 1035 | :func:`inject` can and should be used): 1036 | 1037 | .. code-block:: python 1038 | 1039 | class Application: 1040 | 1041 | @inject 1042 | def __init__(self, dep1: Dep1, dep2: Dep2): 1043 | self.dep1 = dep1 1044 | self.dep2 = dep2 1045 | 1046 | def run(self): 1047 | self.dep1.something() 1048 | 1049 | injector = Injector(configuration) 1050 | application = injector.get(Application) 1051 | application.run() 1052 | 1053 | :param interface: Interface whose implementation we want. 1054 | :param scope: Class of the Scope in which to resolve. 1055 | :returns: An implementation of interface. 1056 | """ 1057 | binding, binder = self.binder.get_binding(interface) 1058 | scope = scope or binding.scope 1059 | if isinstance(scope, ScopeDecorator): 1060 | scope = scope.scope 1061 | # Fetch the corresponding Scope instance from the Binder. 1062 | scope_binding, _ = binder.get_binding(scope) 1063 | scope_instance = scope_binding.provider.get(self) 1064 | 1065 | log.debug( 1066 | '%sInjector.get(%r, scope=%r) using %r', self._log_prefix, interface, scope, binding.provider 1067 | ) 1068 | provider_instance = scope_instance.get(interface, binding.provider) 1069 | result = provider_instance.get(self) 1070 | log.debug('%s -> %r', self._log_prefix, result) 1071 | return result 1072 | 1073 | def create_child_injector(self, *args: Any, **kwargs: Any) -> 'Injector': 1074 | kwargs['parent'] = self 1075 | return Injector(*args, **kwargs) 1076 | 1077 | def create_object(self, cls: Type[T], additional_kwargs: Any = None) -> T: 1078 | """Create a new instance, satisfying any dependencies on cls.""" 1079 | additional_kwargs = additional_kwargs or {} 1080 | log.debug('%sCreating %r object with %r', self._log_prefix, cls, additional_kwargs) 1081 | 1082 | try: 1083 | instance = cls.__new__(cls) 1084 | except TypeError as e: 1085 | reraise( 1086 | e, 1087 | CallError(cls, getattr(cls.__new__, '__func__', cls.__new__), (), {}, e, self._stack), 1088 | maximum_frames=2, 1089 | ) 1090 | init = cls.__init__ 1091 | try: 1092 | self.call_with_injection(init, self_=instance, kwargs=additional_kwargs) 1093 | except TypeError as e: 1094 | # Mypy says "Cannot access "__init__" directly" 1095 | init_function = instance.__init__.__func__ # type: ignore 1096 | reraise(e, CallError(instance, init_function, (), additional_kwargs, e, self._stack)) 1097 | return instance 1098 | 1099 | def call_with_injection( 1100 | self, callable: Callable[..., T], self_: Any = None, args: Any = (), kwargs: Any = {} 1101 | ) -> T: 1102 | """Call a callable and provide its dependencies if needed. 1103 | 1104 | Dependencies are provided when the callable is decorated with :func:`@inject ` 1105 | or some individual parameters are wrapped in :data:`Inject` – otherwise 1106 | ``call_with_injection()`` is equivalent to just calling the callable directly. 1107 | 1108 | If there is an overlap between arguments provided in ``args`` and ``kwargs`` 1109 | and injectable dependencies the provided values take precedence and no dependency 1110 | injection process will take place for the corresponding parameters. 1111 | 1112 | :param self_: Instance of a class callable belongs to if it's a method, 1113 | None otherwise. 1114 | :param args: Arguments to pass to callable. 1115 | :param kwargs: Keyword arguments to pass to callable. 1116 | :type callable: callable 1117 | :type args: tuple of objects 1118 | :type kwargs: dict of string -> object 1119 | :return: Value returned by callable. 1120 | """ 1121 | 1122 | bindings = get_bindings(callable) 1123 | signature = inspect.signature(callable) 1124 | full_args = args 1125 | if self_ is not None: 1126 | full_args = (self_,) + full_args 1127 | bound_arguments = signature.bind_partial(*full_args) 1128 | 1129 | needed = dict( 1130 | (k, v) for (k, v) in bindings.items() if k not in kwargs and k not in bound_arguments.arguments 1131 | ) 1132 | 1133 | dependencies = self.args_to_inject( 1134 | function=callable, 1135 | bindings=needed, 1136 | owner_key=self_.__class__ if self_ is not None else callable.__module__, 1137 | ) 1138 | 1139 | dependencies.update(kwargs) 1140 | 1141 | try: 1142 | return callable(*full_args, **dependencies) 1143 | except TypeError as e: 1144 | reraise(e, CallError(self_, callable, args, dependencies, e, self._stack)) 1145 | # Needed because of a mypy-related issue (https://github.com/python/mypy/issues/8129). 1146 | assert False, "unreachable" # pragma: no cover 1147 | 1148 | @private 1149 | @synchronized(lock) 1150 | def args_to_inject( 1151 | self, function: Callable, bindings: Dict[str, type], owner_key: object 1152 | ) -> Dict[str, Any]: 1153 | """Inject arguments into a function. 1154 | 1155 | :param function: The function. 1156 | :param bindings: Map of argument name to binding key to inject. 1157 | :param owner_key: A key uniquely identifying the *scope* of this function. 1158 | For a method this will be the owning class. 1159 | :returns: Dictionary of resolved arguments. 1160 | """ 1161 | dependencies = {} 1162 | 1163 | key = (owner_key, function, tuple(sorted(bindings.items()))) 1164 | 1165 | def repr_key(k: Tuple[object, Callable, Tuple[Tuple[str, type], ...]]) -> str: 1166 | owner_key, function, bindings = k 1167 | return '%s.%s(injecting %s)' % (tuple(map(_describe, k[:2])) + (dict(k[2]),)) 1168 | 1169 | log.debug('%sProviding %r for %r', self._log_prefix, bindings, function) 1170 | 1171 | if key in self._stack: 1172 | raise CircularDependency( 1173 | 'circular dependency detected: %s -> %s' 1174 | % (' -> '.join(map(repr_key, self._stack)), repr_key(key)) 1175 | ) 1176 | 1177 | self._stack += (key,) 1178 | try: 1179 | for arg, interface in bindings.items(): 1180 | try: 1181 | instance: Any = self.get(interface) 1182 | except UnsatisfiedRequirement as e: 1183 | if not e.owner: 1184 | e = UnsatisfiedRequirement(owner_key, e.interface) 1185 | raise e 1186 | dependencies[arg] = instance 1187 | finally: 1188 | self._stack = tuple(self._stack[:-1]) 1189 | 1190 | return dependencies 1191 | 1192 | 1193 | def get_bindings(callable: Callable) -> Dict[str, type]: 1194 | """Get bindings of injectable parameters from a callable. 1195 | 1196 | If the callable is not decorated with :func:`inject` and does not have any of its 1197 | parameters declared as injectable using :data:`Inject` an empty dictionary will 1198 | be returned. Otherwise the returned dictionary will contain a mapping 1199 | between parameter names and their types with the exception of parameters 1200 | excluded from dependency injection (either with :func:`noninjectable`, :data:`NoInject` 1201 | or only explicit injection with :data:`Inject` being used). For example:: 1202 | 1203 | >>> def function1(a: int) -> None: 1204 | ... pass 1205 | ... 1206 | >>> get_bindings(function1) 1207 | {} 1208 | 1209 | >>> @inject 1210 | ... def function2(a: int) -> None: 1211 | ... pass 1212 | ... 1213 | >>> get_bindings(function2) 1214 | {'a': } 1215 | 1216 | >>> @inject 1217 | ... @noninjectable('b') 1218 | ... def function3(a: int, b: str) -> None: 1219 | ... pass 1220 | ... 1221 | >>> get_bindings(function3) 1222 | {'a': } 1223 | 1224 | >>> # The simple case of no @inject but injection requested with Inject[...] 1225 | >>> def function4(a: Inject[int], b: str) -> None: 1226 | ... pass 1227 | ... 1228 | >>> get_bindings(function4) 1229 | {'a': } 1230 | 1231 | >>> # Using @inject with Inject is redundant but it should not break anything 1232 | >>> @inject 1233 | ... def function5(a: Inject[int], b: str) -> None: 1234 | ... pass 1235 | ... 1236 | >>> get_bindings(function5) 1237 | {'a': , 'b': } 1238 | 1239 | >>> # We need to be able to exclude a parameter from injection with NoInject 1240 | >>> @inject 1241 | ... def function6(a: int, b: NoInject[str]) -> None: 1242 | ... pass 1243 | ... 1244 | >>> get_bindings(function6) 1245 | {'a': } 1246 | 1247 | >>> # The presence of NoInject should not trigger anything on its own 1248 | >>> def function7(a: int, b: NoInject[str]) -> None: 1249 | ... pass 1250 | ... 1251 | >>> get_bindings(function7) 1252 | {} 1253 | 1254 | This function is used internally so by calling it you can learn what exactly 1255 | Injector is going to try to provide to a callable. 1256 | """ 1257 | look_for_explicit_bindings = False 1258 | if not hasattr(callable, '__bindings__'): 1259 | type_hints = get_type_hints(callable, include_extras=True) 1260 | has_injectable_parameters = any( 1261 | _is_specialization(v, Annotated) and _inject_marker in v.__metadata__ for v in type_hints.values() 1262 | ) 1263 | 1264 | if not has_injectable_parameters: 1265 | return {} 1266 | else: 1267 | look_for_explicit_bindings = True 1268 | 1269 | if look_for_explicit_bindings or cast(Any, callable).__bindings__ == 'deferred': 1270 | read_and_store_bindings( 1271 | callable, _infer_injected_bindings(callable, only_explicit_bindings=look_for_explicit_bindings) 1272 | ) 1273 | noninjectables: Set[str] = getattr(callable, '__noninjectables__', set()) 1274 | return {k: v for k, v in cast(Any, callable).__bindings__.items() if k not in noninjectables} 1275 | 1276 | 1277 | class _BindingNotYetAvailable(Exception): 1278 | pass 1279 | 1280 | 1281 | # See a comment in _infer_injected_bindings() for why this is useful. 1282 | class _NoReturnAnnotationProxy: 1283 | def __init__(self, callable: Callable) -> None: 1284 | self.callable = callable 1285 | 1286 | def __getattribute__(self, name: str) -> Any: 1287 | # get_type_hints() uses quite complex logic to determine the namespaces using which 1288 | # any forward references should be resolved. Instead of mirroring this logic here 1289 | # let's just take the easy way out and forward all attribute access to the original 1290 | # callable except for the annotations – we want to filter them. 1291 | callable = object.__getattribute__(self, 'callable') 1292 | if name == '__annotations__': 1293 | annotations = callable.__annotations__ 1294 | return {name: value for (name, value) in annotations.items() if name != 'return'} 1295 | return getattr(callable, name) 1296 | 1297 | 1298 | def _infer_injected_bindings(callable: Callable, only_explicit_bindings: bool) -> Dict[str, type]: 1299 | def _is_new_union_type(instance: Any) -> bool: 1300 | new_union_type = getattr(types, 'UnionType', None) 1301 | return new_union_type is not None and isinstance(instance, new_union_type) 1302 | 1303 | def _is_injection_annotation(annotation: Any) -> bool: 1304 | return _is_specialization(annotation, Annotated) and ( 1305 | _inject_marker in annotation.__metadata__ or _noinject_marker in annotation.__metadata__ 1306 | ) 1307 | 1308 | def _recreate_annotated_origin(annotated_type: Any) -> Any: 1309 | # Creates `Annotated[type, annotation]` from `Inject[Annotated[type, annotation]]`, 1310 | # to support the injection of annotated types with the `Inject[]` annotation. 1311 | origin = annotated_type.__origin__ 1312 | for metadata in annotated_type.__metadata__: # pragma: no branch 1313 | if metadata in (_inject_marker, _noinject_marker): 1314 | break 1315 | origin = Annotated[origin, metadata] 1316 | return origin 1317 | 1318 | spec = inspect.getfullargspec(callable) 1319 | 1320 | try: 1321 | # Return types don't matter for the purpose of dependency injection so instead of 1322 | # obtaining type hints of the callable directly let's wrap it in _NoReturnAnnotationProxy. 1323 | # The proxy removes the return type annotation (if present) from the annotations so that 1324 | # get_type_hints() works even if the return type is a forward reference that can't be 1325 | # resolved. 1326 | bindings = get_type_hints(cast(Callable, _NoReturnAnnotationProxy(callable)), include_extras=True) 1327 | except NameError as e: 1328 | raise _BindingNotYetAvailable(e) 1329 | 1330 | # We don't care about the return value annotation as it doesn't matter 1331 | # injection-wise. 1332 | bindings.pop('return', None) 1333 | 1334 | # If we're dealing with a bound method get_type_hints will still return `self` annotation even though 1335 | # it's already provided and we're not really interested in its type. So – drop it. 1336 | if isinstance(callable, types.MethodType): 1337 | self_name = spec.args[0] 1338 | bindings.pop(self_name, None) 1339 | 1340 | # variadic arguments aren't supported at the moment (this may change 1341 | # in the future if someone has a good idea how to utilize them) 1342 | if spec.varargs: 1343 | bindings.pop(spec.varargs, None) 1344 | if spec.varkw: 1345 | bindings.pop(spec.varkw, None) 1346 | 1347 | for k, v in list(bindings.items()): 1348 | # extract metadata only from Inject and NonInject 1349 | if _is_injection_annotation(v): 1350 | v, metadata = _recreate_annotated_origin(v), v.__metadata__ 1351 | bindings[k] = v 1352 | else: 1353 | metadata = tuple() 1354 | 1355 | if only_explicit_bindings and _inject_marker not in metadata or _noinject_marker in metadata: 1356 | del bindings[k] 1357 | elif _is_specialization(v, Union) or _is_new_union_type(v): 1358 | # We don't treat Optional parameters in any special way at the moment. 1359 | union_members = v.__args__ 1360 | new_members = tuple(set(union_members) - {type(None)}) 1361 | # mypy stared complaining about this line for some reason: 1362 | # error: Variable "new_members" is not valid as a type 1363 | new_union = Union[new_members] # type: ignore 1364 | # mypy complains about this construct: 1365 | # error: The type alias is invalid in runtime context 1366 | # See: https://github.com/python/mypy/issues/5354 1367 | union_metadata = { 1368 | metadata 1369 | for member in new_members 1370 | for metadata in getattr(member, '__metadata__', tuple()) 1371 | if _is_specialization(member, Annotated) 1372 | } 1373 | if ( 1374 | only_explicit_bindings 1375 | and _inject_marker not in union_metadata 1376 | or _noinject_marker in union_metadata 1377 | ): 1378 | del bindings[k] 1379 | else: 1380 | bindings[k] = new_union # type: ignore 1381 | 1382 | return bindings 1383 | 1384 | 1385 | def provider(function: CallableT) -> CallableT: 1386 | """Decorator for :class:`Module` methods, registering a provider of a type. 1387 | 1388 | >>> class MyModule(Module): 1389 | ... @provider 1390 | ... def provide_name(self) -> str: 1391 | ... return 'Bob' 1392 | 1393 | @provider-decoration implies @inject so you can omit it and things will 1394 | work just the same: 1395 | 1396 | >>> class MyModule2(Module): 1397 | ... def configure(self, binder): 1398 | ... binder.bind(int, to=654) 1399 | ... 1400 | ... @provider 1401 | ... def provide_str(self, i: int) -> str: 1402 | ... return str(i) 1403 | ... 1404 | >>> injector = Injector(MyModule2) 1405 | >>> injector.get(str) 1406 | '654' 1407 | """ 1408 | _mark_provider_function(function, allow_multi=False) 1409 | return function 1410 | 1411 | 1412 | def multiprovider(function: CallableT) -> CallableT: 1413 | """Like :func:`provider`, but for multibindings. Example usage:: 1414 | 1415 | class MyModule(Module): 1416 | @multiprovider 1417 | def provide_strs(self) -> List[str]: 1418 | return ['str1'] 1419 | 1420 | class OtherModule(Module): 1421 | @multiprovider 1422 | def provide_strs_also(self) -> List[str]: 1423 | return ['str2'] 1424 | 1425 | Injector([MyModule, OtherModule]).get(List[str]) # ['str1', 'str2'] 1426 | 1427 | See also: :meth:`Binder.multibind`.""" 1428 | _mark_provider_function(function, allow_multi=True) 1429 | return function 1430 | 1431 | 1432 | def _mark_provider_function(function: Callable, *, allow_multi: bool) -> None: 1433 | scope_ = getattr(function, '__scope__', None) 1434 | try: 1435 | annotations = get_type_hints(function, include_extras=True) 1436 | except NameError: 1437 | return_type = '__deferred__' 1438 | else: 1439 | return_type = annotations['return'] 1440 | _validate_provider_return_type(function, cast(type, return_type), allow_multi) 1441 | function.__binding__ = Binding(return_type, inject(function), scope_) # type: ignore 1442 | 1443 | 1444 | def _validate_provider_return_type(function: Callable, return_type: type, allow_multi: bool) -> None: 1445 | origin = _get_origin(_punch_through_alias(return_type)) 1446 | if origin in {dict, list} and not allow_multi: 1447 | raise Error( 1448 | 'Function %s needs to be decorated with multiprovider instead of provider if it is to ' 1449 | 'provide values to a multibinding of type %s' % (function.__name__, return_type) 1450 | ) 1451 | 1452 | 1453 | ConstructorOrClassT = TypeVar('ConstructorOrClassT', bound=Union[Callable, Type]) 1454 | 1455 | 1456 | @overload 1457 | def inject(constructor_or_class: CallableT) -> CallableT: # pragma: no cover 1458 | pass 1459 | 1460 | 1461 | @overload 1462 | def inject(constructor_or_class: Type[T]) -> Type[T]: # pragma: no cover 1463 | pass 1464 | 1465 | 1466 | def inject(constructor_or_class: ConstructorOrClassT) -> ConstructorOrClassT: 1467 | """Decorator declaring parameters to be injected. 1468 | 1469 | eg. 1470 | 1471 | >>> class A: 1472 | ... @inject 1473 | ... def __init__(self, number: int, name: str): 1474 | ... print([number, name]) 1475 | ... 1476 | >>> def configure(binder): 1477 | ... binder.bind(A) 1478 | ... binder.bind(int, to=123) 1479 | ... binder.bind(str, to='Bob') 1480 | 1481 | Use the Injector to get a new instance of A: 1482 | 1483 | >>> a = Injector(configure).get(A) 1484 | [123, 'Bob'] 1485 | 1486 | As a convenience one can decorate a class itself:: 1487 | 1488 | @inject 1489 | class B: 1490 | def __init__(self, dependency: Dependency): 1491 | self.dependency = dependency 1492 | 1493 | This is equivalent to decorating its constructor. In particular this provides integration with 1494 | `dataclasses `_ (the order of decorator 1495 | application is important here):: 1496 | 1497 | @inject 1498 | @dataclass 1499 | class C: 1500 | dependency: Dependency 1501 | 1502 | .. note:: 1503 | 1504 | This decorator is to be used on class constructors (or, as a convenience, on classes). 1505 | Using it on non-constructor methods worked in the past but it was an implementation 1506 | detail rather than a design decision. 1507 | 1508 | Third party libraries may, however, provide support for injecting dependencies 1509 | into non-constructor methods or free functions in one form or another. 1510 | 1511 | .. seealso:: 1512 | 1513 | Generic type :data:`Inject` 1514 | A more explicit way to declare parameters as injectable. 1515 | 1516 | Function :func:`get_bindings` 1517 | A way to inspect how various injection declarations interact with each other. 1518 | 1519 | .. versionchanged:: 0.16.2 1520 | 1521 | (Re)added support for decorating classes with @inject. 1522 | """ 1523 | if isinstance(constructor_or_class, type) and hasattr(constructor_or_class, '__init__'): 1524 | inject(cast(Any, constructor_or_class).__init__) 1525 | else: 1526 | function = constructor_or_class 1527 | try: 1528 | bindings = _infer_injected_bindings(function, only_explicit_bindings=False) 1529 | read_and_store_bindings(function, bindings) 1530 | except _BindingNotYetAvailable: 1531 | cast(Any, function).__bindings__ = 'deferred' 1532 | return constructor_or_class 1533 | 1534 | 1535 | def noninjectable(*args: str) -> Callable[[CallableT], CallableT]: 1536 | """Mark some parameters as not injectable. 1537 | 1538 | This serves as documentation for people reading the code and will prevent 1539 | Injector from ever attempting to provide the parameters. 1540 | 1541 | For example: 1542 | 1543 | >>> class Service: 1544 | ... pass 1545 | ... 1546 | >>> class SomeClass: 1547 | ... @inject 1548 | ... @noninjectable('user_id') 1549 | ... def __init__(self, service: Service, user_id: int): 1550 | ... # ... 1551 | ... pass 1552 | 1553 | :func:`noninjectable` decorations can be stacked on top of 1554 | each other and the order in which a function is decorated with 1555 | :func:`inject` and :func:`noninjectable` 1556 | doesn't matter. 1557 | 1558 | .. seealso:: 1559 | 1560 | Generic type :data:`NoInject` 1561 | A nicer way to declare parameters as noninjectable. 1562 | 1563 | Function :func:`get_bindings` 1564 | A way to inspect how various injection declarations interact with each other. 1565 | 1566 | """ 1567 | 1568 | def decorator(function: CallableT) -> CallableT: 1569 | argspec = inspect.getfullargspec(inspect.unwrap(function)) 1570 | for arg in args: 1571 | if arg not in argspec.args and arg not in argspec.kwonlyargs: 1572 | raise UnknownArgument('Unable to mark unknown argument %s ' 'as non-injectable.' % arg) 1573 | 1574 | existing: Set[str] = getattr(function, '__noninjectables__', set()) 1575 | merged = existing | set(args) 1576 | cast(Any, function).__noninjectables__ = merged 1577 | return function 1578 | 1579 | return decorator 1580 | 1581 | 1582 | @private 1583 | def read_and_store_bindings(f: Callable, bindings: Dict[str, type]) -> None: 1584 | function_bindings = getattr(f, '__bindings__', None) or {} 1585 | if function_bindings == 'deferred': 1586 | function_bindings = {} 1587 | merged_bindings = dict(function_bindings, **bindings) 1588 | 1589 | if hasattr(f, '__func__'): 1590 | f = cast(Any, f).__func__ 1591 | cast(Any, f).__bindings__ = merged_bindings 1592 | 1593 | 1594 | class BoundKey(tuple): 1595 | """A BoundKey provides a key to a type with pre-injected arguments. 1596 | 1597 | >>> class A: 1598 | ... def __init__(self, a, b): 1599 | ... self.a = a 1600 | ... self.b = b 1601 | >>> InjectedA = BoundKey(A, a=InstanceProvider(1), b=InstanceProvider(2)) 1602 | >>> injector = Injector() 1603 | >>> a = injector.get(InjectedA) 1604 | >>> a.a, a.b 1605 | (1, 2) 1606 | """ 1607 | 1608 | def __new__(cls, interface: Type[T], **kwargs: Any) -> 'BoundKey': 1609 | kwargs_tuple = tuple(sorted(kwargs.items())) 1610 | return super(BoundKey, cls).__new__(cls, (interface, kwargs_tuple)) # type: ignore 1611 | 1612 | @property 1613 | def interface(self) -> Type[T]: 1614 | return self[0] 1615 | 1616 | @property 1617 | def kwargs(self) -> Dict[str, Any]: 1618 | return dict(self[1]) 1619 | 1620 | 1621 | class AssistedBuilder(Generic[T]): 1622 | def __init__(self, injector: Injector, target: Type[T]) -> None: 1623 | self._injector = injector 1624 | self._target = target 1625 | 1626 | def build(self, **kwargs: Any) -> T: 1627 | binder = self._injector.binder 1628 | binding, _ = binder.get_binding(self._target) 1629 | provider = binding.provider 1630 | if not isinstance(provider, ClassProvider): 1631 | raise Error( 1632 | 'Assisted interface building works only with ClassProviders, ' 1633 | 'got %r for %r' % (provider, binding.interface) 1634 | ) 1635 | 1636 | return self._build_class(cast(Type[T], provider._cls), **kwargs) 1637 | 1638 | def _build_class(self, cls: Type[T], **kwargs: Any) -> T: 1639 | return self._injector.create_object(cls, additional_kwargs=kwargs) 1640 | 1641 | 1642 | class ClassAssistedBuilder(AssistedBuilder[T]): 1643 | def build(self, **kwargs: Any) -> T: 1644 | return self._build_class(self._target, **kwargs) 1645 | 1646 | 1647 | def _describe(c: Any) -> str: 1648 | if hasattr(c, '__name__'): 1649 | return cast(str, c.__name__) 1650 | if type(c) in (tuple, list): 1651 | return '[%s]' % c[0].__name__ 1652 | return str(c) 1653 | 1654 | 1655 | class ProviderOf(Generic[T]): 1656 | """Can be used to get a provider of an interface, for example: 1657 | 1658 | >>> def provide_int(): 1659 | ... print('providing') 1660 | ... return 123 1661 | >>> 1662 | >>> def configure(binder): 1663 | ... binder.bind(int, to=provide_int) 1664 | >>> 1665 | >>> injector = Injector(configure) 1666 | >>> provider = injector.get(ProviderOf[int]) 1667 | >>> value = provider.get() 1668 | providing 1669 | >>> value 1670 | 123 1671 | """ 1672 | 1673 | def __init__(self, injector: Injector, interface: Type[T]): 1674 | self._injector = injector 1675 | self._interface = interface 1676 | 1677 | def __repr__(self) -> str: 1678 | return '%s(%r, %r)' % (type(self).__name__, self._injector, self._interface) 1679 | 1680 | def get(self) -> T: 1681 | """Get an implementation for the specified interface.""" 1682 | return self._injector.get(self._interface) 1683 | 1684 | 1685 | def is_decorated_with_inject(function: Callable[..., Any]) -> bool: 1686 | """See if given callable is declared to want some dependencies injected. 1687 | 1688 | Example use: 1689 | 1690 | >>> def fun(i: int) -> str: 1691 | ... return str(i) 1692 | 1693 | >>> is_decorated_with_inject(fun) 1694 | False 1695 | >>> 1696 | >>> @inject 1697 | ... def fun2(i: int) -> str: 1698 | ... return str(i) 1699 | 1700 | >>> is_decorated_with_inject(fun2) 1701 | True 1702 | """ 1703 | return hasattr(function, '__bindings__') 1704 | -------------------------------------------------------------------------------- /injector_test.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # 3 | # Copyright (C) 2010 Alec Thomas 4 | # All rights reserved. 5 | # 6 | # This software is licensed as described in the file COPYING, which 7 | # you should have received as part of this distribution. 8 | # 9 | # Author: Alec Thomas 10 | 11 | """Functional tests for the "Injector" dependency injection framework.""" 12 | 13 | import abc 14 | import sys 15 | import threading 16 | import traceback 17 | import warnings 18 | from contextlib import contextmanager 19 | from dataclasses import dataclass 20 | from typing import Any, NewType, Optional, Union 21 | 22 | if sys.version_info >= (3, 9): 23 | from typing import Annotated 24 | else: 25 | from typing_extensions import Annotated 26 | 27 | from typing import Dict, List, NewType 28 | 29 | import pytest 30 | 31 | from injector import ( 32 | AssistedBuilder, 33 | Binder, 34 | CallError, 35 | CircularDependency, 36 | ClassAssistedBuilder, 37 | ClassProvider, 38 | Error, 39 | Inject, 40 | Injector, 41 | InstanceProvider, 42 | InvalidInterface, 43 | Module, 44 | NoInject, 45 | ProviderOf, 46 | Scope, 47 | ScopeDecorator, 48 | SingletonScope, 49 | UnknownArgument, 50 | UnsatisfiedRequirement, 51 | get_bindings, 52 | inject, 53 | multiprovider, 54 | noninjectable, 55 | provider, 56 | singleton, 57 | threadlocal, 58 | ) 59 | 60 | 61 | class EmptyClass: 62 | pass 63 | 64 | 65 | class DependsOnEmptyClass: 66 | @inject 67 | def __init__(self, b: EmptyClass): 68 | """Construct a new DependsOnEmptyClass.""" 69 | self.b = b 70 | 71 | 72 | City = NewType('City', str) 73 | Animal = Annotated[str, 'Animal'] 74 | 75 | 76 | def prepare_nested_injectors(): 77 | def configure(binder): 78 | binder.bind(str, to='asd') 79 | 80 | parent = Injector(configure) 81 | child = parent.create_child_injector() 82 | return parent, child 83 | 84 | 85 | def check_exception_contains_stuff(exception, stuff): 86 | stringified = str(exception) 87 | 88 | for thing in stuff: 89 | assert thing in stringified, '%r should be present in the exception representation: %s' % ( 90 | thing, 91 | stringified, 92 | ) 93 | 94 | 95 | def test_child_injector_inherits_parent_bindings(): 96 | parent, child = prepare_nested_injectors() 97 | assert child.get(str) == parent.get(str) 98 | 99 | 100 | def test_child_injector_overrides_parent_bindings(): 101 | parent, child = prepare_nested_injectors() 102 | child.binder.bind(str, to='qwe') 103 | 104 | assert (parent.get(str), child.get(str)) == ('asd', 'qwe') 105 | 106 | 107 | def test_child_injector_rebinds_arguments_for_parent_scope(): 108 | class Cls: 109 | val = "" 110 | 111 | class A(Cls): 112 | @inject 113 | def __init__(self, val: str): 114 | self.val = val 115 | 116 | def configure_parent(binder): 117 | binder.bind(Cls, to=A) 118 | binder.bind(str, to="Parent") 119 | 120 | def configure_child(binder): 121 | binder.bind(str, to="Child") 122 | 123 | parent = Injector(configure_parent) 124 | assert parent.get(Cls).val == "Parent" 125 | child = parent.create_child_injector(configure_child) 126 | assert child.get(Cls).val == "Child" 127 | 128 | 129 | def test_scopes_are_only_bound_to_root_injector(): 130 | parent, child = prepare_nested_injectors() 131 | 132 | class A: 133 | pass 134 | 135 | parent.binder.bind(A, to=A, scope=singleton) 136 | assert parent.get(A) is child.get(A) 137 | 138 | 139 | def test_get_default_injected_instances(): 140 | def configure(binder): 141 | binder.bind(DependsOnEmptyClass) 142 | binder.bind(EmptyClass) 143 | 144 | injector = Injector(configure) 145 | assert injector.get(Injector) is injector 146 | assert injector.get(Binder) is injector.binder 147 | 148 | 149 | def test_instantiate_injected_method(): 150 | a = DependsOnEmptyClass('Bob') 151 | assert a.b == 'Bob' 152 | 153 | 154 | def test_method_decorator_is_wrapped(): 155 | assert DependsOnEmptyClass.__init__.__doc__ == 'Construct a new DependsOnEmptyClass.' 156 | assert DependsOnEmptyClass.__init__.__name__ == '__init__' 157 | 158 | 159 | def test_decorator_works_for_function_with_no_args(): 160 | @inject 161 | def wrapped(*args, **kwargs): 162 | pass 163 | 164 | 165 | def test_providers_arent_called_for_dependencies_that_are_already_provided(): 166 | def configure(binder): 167 | binder.bind(int, to=lambda: 1 / 0) 168 | 169 | class A: 170 | @inject 171 | def __init__(self, i: int): 172 | pass 173 | 174 | injector = Injector(configure) 175 | builder = injector.get(AssistedBuilder[A]) 176 | 177 | with pytest.raises(ZeroDivisionError): 178 | builder.build() 179 | 180 | builder.build(i=3) 181 | 182 | 183 | def test_inject_direct(): 184 | def configure(binder): 185 | binder.bind(DependsOnEmptyClass) 186 | binder.bind(EmptyClass) 187 | 188 | injector = Injector(configure) 189 | a = injector.get(DependsOnEmptyClass) 190 | assert isinstance(a, DependsOnEmptyClass) 191 | assert isinstance(a.b, EmptyClass) 192 | 193 | 194 | def test_configure_multiple_modules(): 195 | def configure_a(binder): 196 | binder.bind(DependsOnEmptyClass) 197 | 198 | def configure_b(binder): 199 | binder.bind(EmptyClass) 200 | 201 | injector = Injector([configure_a, configure_b]) 202 | a = injector.get(DependsOnEmptyClass) 203 | assert isinstance(a, DependsOnEmptyClass) 204 | assert isinstance(a.b, EmptyClass) 205 | 206 | 207 | def test_inject_with_missing_dependency(): 208 | def configure(binder): 209 | binder.bind(DependsOnEmptyClass) 210 | 211 | injector = Injector(configure, auto_bind=False) 212 | with pytest.raises(UnsatisfiedRequirement): 213 | injector.get(EmptyClass) 214 | 215 | 216 | def test_inject_named_interface(): 217 | class A: 218 | @inject 219 | def __init__(self, b: EmptyClass): 220 | self.b = b 221 | 222 | def configure(binder): 223 | binder.bind(A) 224 | binder.bind(EmptyClass) 225 | 226 | injector = Injector(configure) 227 | a = injector.get(A) 228 | assert isinstance(a, A) 229 | assert isinstance(a.b, EmptyClass) 230 | 231 | 232 | class TransitiveC: 233 | pass 234 | 235 | 236 | class TransitiveB: 237 | @inject 238 | def __init__(self, c: TransitiveC): 239 | self.c = c 240 | 241 | 242 | class TransitiveA: 243 | @inject 244 | def __init__(self, b: TransitiveB): 245 | self.b = b 246 | 247 | 248 | def test_transitive_injection(): 249 | def configure(binder): 250 | binder.bind(TransitiveA) 251 | binder.bind(TransitiveB) 252 | binder.bind(TransitiveC) 253 | 254 | injector = Injector(configure) 255 | a = injector.get(TransitiveA) 256 | assert isinstance(a, TransitiveA) 257 | assert isinstance(a.b, TransitiveB) 258 | assert isinstance(a.b.c, TransitiveC) 259 | 260 | 261 | def test_transitive_injection_with_missing_dependency(): 262 | def configure(binder): 263 | binder.bind(TransitiveA) 264 | binder.bind(TransitiveB) 265 | 266 | injector = Injector(configure, auto_bind=False) 267 | with pytest.raises(UnsatisfiedRequirement): 268 | injector.get(TransitiveA) 269 | with pytest.raises(UnsatisfiedRequirement): 270 | injector.get(TransitiveB) 271 | 272 | 273 | def test_inject_singleton(): 274 | class A: 275 | @inject 276 | def __init__(self, b: EmptyClass): 277 | self.b = b 278 | 279 | def configure(binder): 280 | binder.bind(A) 281 | binder.bind(EmptyClass, scope=SingletonScope) 282 | 283 | injector1 = Injector(configure) 284 | a1 = injector1.get(A) 285 | a2 = injector1.get(A) 286 | assert a1.b is a2.b 287 | 288 | 289 | @singleton 290 | class SingletonB: 291 | pass 292 | 293 | 294 | def test_inject_decorated_singleton_class(): 295 | class A: 296 | @inject 297 | def __init__(self, b: SingletonB): 298 | self.b = b 299 | 300 | def configure(binder): 301 | binder.bind(A) 302 | binder.bind(SingletonB) 303 | 304 | injector1 = Injector(configure) 305 | a1 = injector1.get(A) 306 | a2 = injector1.get(A) 307 | assert a1.b is a2.b 308 | assert a1 is not a2 309 | 310 | 311 | def test_injecting_an_auto_bound_decorated_singleton_class(): 312 | class A: 313 | @inject 314 | def __init__(self, b: SingletonB): 315 | self.b = b 316 | 317 | injector1 = Injector() 318 | a1 = injector1.get(A) 319 | a2 = injector1.get(A) 320 | assert a1.b is a2.b 321 | assert a1 is not a2 322 | 323 | 324 | def test_a_decorated_singleton_is_shared_between_parent_and_child_injectors_when_parent_creates_it_first(): 325 | parent_injector = Injector() 326 | 327 | child_injector = parent_injector.create_child_injector() 328 | 329 | assert parent_injector.get(SingletonB) is child_injector.get(SingletonB) 330 | 331 | 332 | def test_a_decorated_singleton_is_shared_between_parent_and_child_injectors_when_child_creates_it_first(): 333 | parent_injector = Injector() 334 | 335 | child_injector = parent_injector.create_child_injector() 336 | 337 | assert child_injector.get(SingletonB) is parent_injector.get(SingletonB) 338 | 339 | 340 | # Test for https://github.com/python-injector/injector/issues/207 341 | def test_a_decorated_singleton_is_shared_among_child_injectors(): 342 | parent_injector = Injector() 343 | 344 | child_injector_1 = parent_injector.create_child_injector() 345 | child_injector_2 = parent_injector.create_child_injector() 346 | 347 | assert child_injector_1.get(SingletonB) is child_injector_2.get(SingletonB) 348 | 349 | 350 | def test_a_decorated_singleton_should_not_override_explicit_binds(): 351 | parent_injector = Injector() 352 | 353 | child_injector = parent_injector.create_child_injector() 354 | grand_child_injector = child_injector.create_child_injector() 355 | 356 | bound_singleton = SingletonB() 357 | child_injector.binder.bind(SingletonB, to=bound_singleton) 358 | 359 | assert parent_injector.get(SingletonB) is not bound_singleton 360 | assert child_injector.get(SingletonB) is bound_singleton 361 | assert grand_child_injector.get(SingletonB) is bound_singleton 362 | 363 | 364 | def test_binding_a_singleton_to_a_child_injector_does_not_affect_the_parent_injector(): 365 | parent_injector = Injector() 366 | 367 | child_injector = parent_injector.create_child_injector() 368 | child_injector.binder.bind(EmptyClass, scope=singleton) 369 | 370 | assert child_injector.get(EmptyClass) is child_injector.get(EmptyClass) 371 | assert child_injector.get(EmptyClass) is not parent_injector.get(EmptyClass) 372 | assert parent_injector.get(EmptyClass) is not parent_injector.get(EmptyClass) 373 | 374 | 375 | def test_a_decorated_singleton_should_not_override_a_child_provider(): 376 | parent_injector = Injector() 377 | 378 | provided_instance = SingletonB() 379 | 380 | class MyModule(Module): 381 | @provider 382 | def provide_name(self) -> SingletonB: 383 | return provided_instance 384 | 385 | child_injector = parent_injector.create_child_injector([MyModule]) 386 | 387 | assert child_injector.get(SingletonB) is provided_instance 388 | assert parent_injector.get(SingletonB) is not provided_instance 389 | assert parent_injector.get(SingletonB) is parent_injector.get(SingletonB) 390 | 391 | 392 | # Test for https://github.com/python-injector/injector/issues/207 393 | def test_a_decorated_singleton_is_created_as_close_to_the_root_where_dependencies_fulfilled(): 394 | class NonInjectableD: 395 | @inject 396 | def __init__(self, required) -> None: 397 | self.required = required 398 | 399 | @singleton 400 | class SingletonC: 401 | @inject 402 | def __init__(self, d: NonInjectableD): 403 | self.d = d 404 | 405 | parent_injector = Injector() 406 | 407 | child_injector_1 = parent_injector.create_child_injector() 408 | 409 | child_injector_2 = parent_injector.create_child_injector() 410 | child_injector_2_1 = child_injector_2.create_child_injector() 411 | 412 | provided_d = NonInjectableD(required=True) 413 | child_injector_2.binder.bind(NonInjectableD, to=provided_d) 414 | 415 | assert child_injector_2_1.get(SingletonC) is child_injector_2.get(SingletonC) 416 | assert child_injector_2.get(SingletonC).d is provided_d 417 | 418 | with pytest.raises(CallError): 419 | parent_injector.get(SingletonC) 420 | 421 | with pytest.raises(CallError): 422 | child_injector_1.get(SingletonC) 423 | 424 | 425 | def test_a_bound_decorated_singleton_is_created_as_close_to_the_root_where_it_exists_when_auto_bind_is_disabled(): 426 | parent_injector = Injector(auto_bind=False) 427 | 428 | child_injector_1 = parent_injector.create_child_injector(auto_bind=False) 429 | 430 | child_injector_2 = parent_injector.create_child_injector(auto_bind=False) 431 | child_injector_2_1 = child_injector_2.create_child_injector(auto_bind=False) 432 | 433 | child_injector_2.binder.bind(SingletonB) 434 | 435 | assert child_injector_2_1.get(SingletonB) is child_injector_2_1.get(SingletonB) 436 | assert child_injector_2_1.get(SingletonB) is child_injector_2.get(SingletonB) 437 | 438 | with pytest.raises(UnsatisfiedRequirement): 439 | parent_injector.get(SingletonB) 440 | 441 | with pytest.raises(UnsatisfiedRequirement): 442 | child_injector_1.get(SingletonB) 443 | 444 | 445 | def test_threadlocal(): 446 | @threadlocal 447 | class A: 448 | def __init__(self): 449 | pass 450 | 451 | def configure(binder): 452 | binder.bind(A) 453 | 454 | injector = Injector(configure) 455 | a1 = injector.get(A) 456 | a2 = injector.get(A) 457 | 458 | assert a1 is a2 459 | 460 | a3 = [None] 461 | ready = threading.Event() 462 | 463 | def inject_a3(): 464 | a3[0] = injector.get(A) 465 | ready.set() 466 | 467 | threading.Thread(target=inject_a3).start() 468 | ready.wait(1.0) 469 | 470 | assert a2 is not a3[0] and a3[0] is not None 471 | 472 | 473 | class Interface2: 474 | pass 475 | 476 | 477 | def test_injecting_interface_implementation(): 478 | class Implementation: 479 | pass 480 | 481 | class A: 482 | @inject 483 | def __init__(self, i: Interface2): 484 | self.i = i 485 | 486 | def configure(binder): 487 | binder.bind(A) 488 | binder.bind(Interface2, to=Implementation) 489 | 490 | injector = Injector(configure) 491 | a = injector.get(A) 492 | assert isinstance(a.i, Implementation) 493 | 494 | 495 | class CyclicInterface: 496 | pass 497 | 498 | 499 | class CyclicA: 500 | @inject 501 | def __init__(self, i: CyclicInterface): 502 | self.i = i 503 | 504 | 505 | class CyclicB: 506 | @inject 507 | def __init__(self, a: CyclicA): 508 | self.a = a 509 | 510 | 511 | def test_cyclic_dependencies(): 512 | def configure(binder): 513 | binder.bind(CyclicInterface, to=CyclicB) 514 | binder.bind(CyclicA) 515 | 516 | injector = Injector(configure) 517 | with pytest.raises(CircularDependency): 518 | injector.get(CyclicA) 519 | 520 | 521 | class CyclicInterface2: 522 | pass 523 | 524 | 525 | class CyclicA2: 526 | @inject 527 | def __init__(self, i: CyclicInterface2): 528 | self.i = i 529 | 530 | 531 | class CyclicB2: 532 | @inject 533 | def __init__(self, a_builder: AssistedBuilder[CyclicA2]): 534 | self.a = a_builder.build(i=self) 535 | 536 | 537 | def test_dependency_cycle_can_be_worked_broken_by_assisted_building(): 538 | def configure(binder): 539 | binder.bind(CyclicInterface2, to=CyclicB2) 540 | binder.bind(CyclicA2) 541 | 542 | injector = Injector(configure) 543 | 544 | # Previously it'd detect a circular dependency here: 545 | # 1. Constructing CyclicA2 requires CyclicInterface2 (bound to CyclicB2) 546 | # 2. Constructing CyclicB2 requires assisted build of CyclicA2 547 | # 3. Constructing CyclicA2 triggers circular dependency check 548 | assert isinstance(injector.get(CyclicA2), CyclicA2) 549 | 550 | 551 | class Interface5: 552 | constructed = False 553 | 554 | def __init__(self): 555 | Interface5.constructed = True 556 | 557 | 558 | def test_that_injection_is_lazy(): 559 | class A: 560 | @inject 561 | def __init__(self, i: Interface5): 562 | self.i = i 563 | 564 | def configure(binder): 565 | binder.bind(Interface5) 566 | binder.bind(A) 567 | 568 | injector = Injector(configure) 569 | assert not (Interface5.constructed) 570 | injector.get(A) 571 | assert Interface5.constructed 572 | 573 | 574 | def test_module_provider(): 575 | class MyModule(Module): 576 | @provider 577 | def provide_name(self) -> str: 578 | return 'Bob' 579 | 580 | module = MyModule() 581 | injector = Injector(module) 582 | assert injector.get(str) == 'Bob' 583 | 584 | 585 | def test_module_provider_keeps_annotated_types_and_new_types_separate() -> None: 586 | class MyModule(Module): 587 | @provider 588 | def provide_name(self) -> str: 589 | return 'Bob' 590 | 591 | @provider 592 | def provide_city(self) -> City: 593 | return City('Stockholm') 594 | 595 | @provider 596 | def provide_animal(self) -> Animal: 597 | return 'Dog' 598 | 599 | module = MyModule() 600 | injector = Injector(module) 601 | assert injector.get(str) == 'Bob' 602 | assert injector.get(City) == City('Stockholm') 603 | assert injector.get(Animal) == 'Dog' 604 | 605 | 606 | def test_module_class_gets_instantiated(): 607 | name = 'Meg' 608 | 609 | class MyModule(Module): 610 | def configure(self, binder): 611 | binder.bind(str, to=name) 612 | 613 | injector = Injector(MyModule) 614 | assert injector.get(str) == name 615 | 616 | 617 | def test_inject_and_provide_coexist_happily(): 618 | class MyModule(Module): 619 | @provider 620 | def provide_weight(self) -> float: 621 | return 50.0 622 | 623 | @provider 624 | def provide_age(self) -> int: 625 | return 25 626 | 627 | # TODO(alec) Make provider/inject order independent. 628 | @provider 629 | @inject 630 | def provide_description(self, age: int, weight: float) -> str: 631 | return 'Bob is %d and weighs %0.1fkg' % (age, weight) 632 | 633 | assert Injector(MyModule()).get(str) == 'Bob is 25 and weighs 50.0kg' 634 | 635 | 636 | Names = NewType('Names', List[str]) 637 | Passwords = NewType('Passwords', Dict[str, str]) 638 | 639 | Animals = Annotated[List[str], 'Animals'] 640 | AnimalFoods = Annotated[Dict[str, str], 'AnimalFoods'] 641 | 642 | 643 | def test_multibind(): 644 | # First let's have some explicit multibindings 645 | def configure(binder): 646 | binder.multibind(List[str], to=['not a name']) 647 | binder.multibind(Dict[str, str], to={'asd': 'qwe'}) 648 | # To make sure Lists and Dicts of different subtypes are treated distinctly 649 | binder.multibind(List[int], to=[1, 2, 3]) 650 | binder.multibind(Dict[str, int], to={'weight': 12}) 651 | # To see that NewTypes are treated distinctly 652 | binder.multibind(Names, to=['Bob']) 653 | binder.multibind(Passwords, to={'Bob': 'password1'}) 654 | # To see that Annotated collections are treated distinctly 655 | binder.multibind(Animals, to=['Dog']) 656 | binder.multibind(AnimalFoods, to={'Dog': 'meat'}) 657 | # To see that collections of Annotated types are treated distinctly 658 | binder.multibind(List[City], to=[City('Stockholm')]) 659 | binder.multibind(Dict[str, City], to={'Sweden': City('Stockholm')}) 660 | 661 | # Then @multiprovider-decorated Module methods 662 | class CustomModule(Module): 663 | @multiprovider 664 | def provide_some_ints(self) -> List[int]: 665 | return [4, 5, 6] 666 | 667 | @multiprovider 668 | def provide_some_strs(self) -> List[str]: 669 | return ['not a name either'] 670 | 671 | @multiprovider 672 | def provide_str_to_str_mapping(self) -> Dict[str, str]: 673 | return {'xxx': 'yyy'} 674 | 675 | @multiprovider 676 | def provide_str_to_int_mapping(self) -> Dict[str, int]: 677 | return {'height': 33} 678 | 679 | @multiprovider 680 | def provide_names(self) -> Names: 681 | return Names(['Alice', 'Clarice']) 682 | 683 | @multiprovider 684 | def provide_passwords(self) -> Passwords: 685 | return Passwords({'Alice': 'aojrioeg3', 'Clarice': 'clarice30'}) 686 | 687 | @multiprovider 688 | def provide_animals(self) -> Animals: 689 | return ['Cat', 'Fish'] 690 | 691 | @multiprovider 692 | def provide_animal_foods(self) -> AnimalFoods: 693 | return {'Cat': 'milk', 'Fish': 'flakes'} 694 | 695 | @multiprovider 696 | def provide_cities(self) -> List[City]: 697 | return [City('New York'), City('Tokyo')] 698 | 699 | @multiprovider 700 | def provide_city_mapping(self) -> Dict[str, City]: 701 | return {'USA': City('New York'), 'Japan': City('Tokyo')} 702 | 703 | injector = Injector([configure, CustomModule]) 704 | assert injector.get(List[str]) == ['not a name', 'not a name either'] 705 | assert injector.get(List[int]) == [1, 2, 3, 4, 5, 6] 706 | assert injector.get(Dict[str, str]) == {'asd': 'qwe', 'xxx': 'yyy'} 707 | assert injector.get(Dict[str, int]) == {'weight': 12, 'height': 33} 708 | assert injector.get(Names) == ['Bob', 'Alice', 'Clarice'] 709 | assert injector.get(Passwords) == {'Bob': 'password1', 'Alice': 'aojrioeg3', 'Clarice': 'clarice30'} 710 | assert injector.get(Animals) == ['Dog', 'Cat', 'Fish'] 711 | assert injector.get(AnimalFoods) == {'Dog': 'meat', 'Cat': 'milk', 'Fish': 'flakes'} 712 | assert injector.get(List[City]) == ['Stockholm', 'New York', 'Tokyo'] 713 | assert injector.get(Dict[str, City]) == {'Sweden': 'Stockholm', 'USA': 'New York', 'Japan': 'Tokyo'} 714 | 715 | 716 | class Plugin(abc.ABC): 717 | pass 718 | 719 | 720 | class PluginA(Plugin): 721 | pass 722 | 723 | 724 | class PluginB(Plugin): 725 | pass 726 | 727 | 728 | class PluginC(Plugin): 729 | pass 730 | 731 | 732 | class PluginD(Plugin): 733 | pass 734 | 735 | 736 | def test_multibind_list_of_plugins(): 737 | def configure(binder: Binder): 738 | binder.multibind(List[Plugin], to=PluginA) 739 | binder.multibind(List[Plugin], to=[PluginB, PluginC()]) 740 | binder.multibind(List[Plugin], to=lambda: [PluginD()]) 741 | 742 | injector = Injector([configure]) 743 | plugins = injector.get(List[Plugin]) 744 | assert len(plugins) == 4 745 | assert isinstance(plugins[0], PluginA) 746 | assert isinstance(plugins[1], PluginB) 747 | assert isinstance(plugins[2], PluginC) 748 | assert isinstance(plugins[3], PluginD) 749 | 750 | 751 | def test_multibind_dict_of_plugins(): 752 | def configure(binder: Binder): 753 | binder.multibind(Dict[str, Plugin], to={'a': PluginA}) 754 | binder.multibind(Dict[str, Plugin], to={'b': PluginB, 'c': PluginC()}) 755 | binder.multibind(Dict[str, Plugin], to=lambda: {'d': PluginD()}) 756 | 757 | injector = Injector([configure]) 758 | plugins = injector.get(Dict[str, Plugin]) 759 | assert len(plugins) == 4 760 | assert isinstance(plugins['a'], PluginA) 761 | assert isinstance(plugins['b'], PluginB) 762 | assert isinstance(plugins['c'], PluginC) 763 | assert isinstance(plugins['d'], PluginD) 764 | 765 | 766 | def test_multibinding_to_non_generic_type_raises_error(): 767 | def configure_list(binder: Binder): 768 | binder.multibind(List, to=[1]) 769 | 770 | def configure_dict(binder: Binder): 771 | binder.multibind(Dict, to={'a': 2}) 772 | 773 | with pytest.raises(InvalidInterface): 774 | Injector([configure_list]) 775 | 776 | with pytest.raises(InvalidInterface): 777 | Injector([configure_dict]) 778 | 779 | 780 | def test_multibind_types_are_not_affected_by_the_bound_type_scope() -> None: 781 | def configure(binder: Binder) -> None: 782 | binder.bind(PluginA, to=PluginA, scope=singleton) 783 | binder.multibind(List[Plugin], to=PluginA) 784 | 785 | injector = Injector([configure]) 786 | first_list = injector.get(List[Plugin]) 787 | second_list = injector.get(List[Plugin]) 788 | 789 | assert injector.get(PluginA) is injector.get(PluginA) 790 | assert first_list[0] is not injector.get(PluginA) 791 | assert first_list[0] is not second_list[0] 792 | 793 | 794 | def test_multibind_types_are_not_affected_by_the_bound_type_provider() -> None: 795 | def configure(binder: Binder) -> None: 796 | binder.bind(PluginA, to=InstanceProvider(PluginA())) 797 | binder.multibind(List[Plugin], to=PluginA) 798 | 799 | injector = Injector([configure]) 800 | first_list = injector.get(List[Plugin]) 801 | second_list = injector.get(List[Plugin]) 802 | 803 | assert injector.get(PluginA) is injector.get(PluginA) 804 | assert first_list[0] is not injector.get(PluginA) 805 | assert first_list[0] is not second_list[0] 806 | 807 | 808 | def test_multibind_dict_types_use_their_own_bound_providers_and_scopes() -> None: 809 | def configure(binder: Binder) -> None: 810 | binder.bind(PluginA, to=InstanceProvider(PluginA())) 811 | binder.bind(PluginB, to=PluginB, scope=singleton) 812 | binder.multibind(Dict[str, Plugin], to={'a': PluginA, 'b': PluginB}) 813 | 814 | injector = Injector([configure]) 815 | 816 | dictionary = injector.get(Dict[str, Plugin]) 817 | 818 | assert dictionary['a'] is not injector.get(PluginA) 819 | assert dictionary['b'] is not injector.get(PluginB) 820 | 821 | 822 | def test_multibind_list_scopes_apply_to_the_bound_items() -> None: 823 | def configure(binder: Binder) -> None: 824 | binder.multibind(List[Plugin], to=PluginA, scope=singleton) 825 | binder.multibind(List[Plugin], to=PluginB) 826 | binder.multibind(List[Plugin], to=[PluginC], scope=singleton) 827 | 828 | injector = Injector([configure]) 829 | first_list = injector.get(List[Plugin]) 830 | second_list = injector.get(List[Plugin]) 831 | 832 | assert first_list is not second_list 833 | assert first_list[0] is second_list[0] 834 | assert first_list[1] is not second_list[1] 835 | assert first_list[2] is second_list[2] 836 | 837 | 838 | def test_multibind_list_scopes_apply_to_the_bound_items_not_types() -> None: 839 | def configure(binder: Binder) -> None: 840 | binder.multibind(List[Plugin], to=PluginA) 841 | binder.multibind(List[Plugin], to=[PluginA, PluginB], scope=singleton) 842 | binder.multibind(List[Plugin], to=PluginA) 843 | binder.multibind(List[Plugin], to=PluginA, scope=singleton) 844 | 845 | injector = Injector([configure]) 846 | first_list = injector.get(List[Plugin]) 847 | second_list = injector.get(List[Plugin]) 848 | 849 | assert first_list is not second_list 850 | assert first_list[0] is not second_list[0] 851 | assert first_list[1] is second_list[1] 852 | assert first_list[2] is second_list[2] 853 | assert first_list[3] is not second_list[3] 854 | assert first_list[4] is second_list[4] 855 | 856 | 857 | def test_multibind_dict_scopes_apply_to_the_bound_items_in_the_multibound_dict() -> None: 858 | SingletonPlugins = Annotated[Plugin, "singleton"] 859 | OtherPlugins = Annotated[Plugin, "other"] 860 | 861 | def configure(binder: Binder) -> None: 862 | binder.multibind(Dict[str, SingletonPlugins], to={'a': PluginA}, scope=singleton) 863 | binder.multibind(Dict[str, OtherPlugins], to={'a': PluginA}) 864 | 865 | injector = Injector([configure]) 866 | singletons_1 = injector.get(Dict[str, SingletonPlugins]) 867 | singletons_2 = injector.get(Dict[str, SingletonPlugins]) 868 | others_1 = injector.get(Dict[str, OtherPlugins]) 869 | others_2 = injector.get(Dict[str, OtherPlugins]) 870 | 871 | assert singletons_1['a'] is singletons_2['a'] 872 | assert singletons_1['a'] is not others_1['a'] 873 | assert others_1['a'] is not others_2['a'] 874 | 875 | 876 | def test_multibind_list_scopes_apply_to_the_bound_items_in_the_multibound_list() -> None: 877 | SingletonPlugins = Annotated[Plugin, "singleton"] 878 | OtherPlugins = Annotated[Plugin, "other"] 879 | 880 | def configure(binder: Binder) -> None: 881 | binder.multibind(List[SingletonPlugins], to=PluginA, scope=singleton) 882 | binder.multibind(List[OtherPlugins], to=PluginA) 883 | 884 | injector = Injector([configure]) 885 | singletons_1 = injector.get(List[SingletonPlugins]) 886 | singletons_2 = injector.get(List[SingletonPlugins]) 887 | others_1 = injector.get(List[OtherPlugins]) 888 | others_2 = injector.get(List[OtherPlugins]) 889 | 890 | assert singletons_1[0] is singletons_2[0] 891 | assert singletons_1[0] is not others_1[0] 892 | assert others_1[0] is not others_2[0] 893 | 894 | 895 | def test_multibind_dict_scopes_apply_to_the_bound_items() -> None: 896 | def configure(binder: Binder) -> None: 897 | binder.multibind(Dict[str, Plugin], to={'a': PluginA}, scope=singleton) 898 | binder.multibind(Dict[str, Plugin], to={'b': PluginB}) 899 | binder.multibind(Dict[str, Plugin], to={'c': PluginC}, scope=singleton) 900 | 901 | injector = Injector([configure]) 902 | first_dict = injector.get(Dict[str, Plugin]) 903 | second_dict = injector.get(Dict[str, Plugin]) 904 | 905 | assert first_dict is not second_dict 906 | assert first_dict['a'] is second_dict['a'] 907 | assert first_dict['b'] is not second_dict['b'] 908 | assert first_dict['c'] is second_dict['c'] 909 | 910 | 911 | def test_multibind_scopes_does_not_apply_to_the_type_globally() -> None: 912 | def configure(binder: Binder) -> None: 913 | binder.multibind(List[Plugin], to=PluginA, scope=singleton) 914 | binder.multibind(Dict[str, Plugin], to={'a': PluginA}, scope=singleton) 915 | 916 | injector = Injector([configure]) 917 | plugins = injector.get(List[Plugin]) 918 | 919 | assert plugins[0] is not injector.get(PluginA) 920 | assert plugins[0] is not injector.get(Plugin) 921 | assert injector.get(PluginA) is not injector.get(PluginA) 922 | 923 | 924 | def test_regular_bind_and_provider_dont_work_with_multibind(): 925 | # We only want multibind and multiprovider to work to avoid confusion 926 | 927 | Names = NewType('Names', List[str]) 928 | Passwords = NewType('Passwords', Dict[str, str]) 929 | 930 | class MyModule(Module): 931 | with pytest.raises(Error): 932 | 933 | @provider 934 | def provide_strs(self) -> List[str]: 935 | return [] 936 | 937 | with pytest.raises(Error): 938 | 939 | @provider 940 | def provide_names(self) -> Names: 941 | return [] 942 | 943 | with pytest.raises(Error): 944 | 945 | @provider 946 | def provide_strs_in_dict(self) -> Dict[str, str]: 947 | return {} 948 | 949 | with pytest.raises(Error): 950 | 951 | @provider 952 | def provide_passwords(self) -> Passwords: 953 | return {} 954 | 955 | injector = Injector() 956 | binder = injector.binder 957 | 958 | with pytest.raises(Error): 959 | binder.bind(List[str], to=[]) 960 | 961 | with pytest.raises(Error): 962 | binder.bind(Names, to=[]) 963 | 964 | with pytest.raises(Error): 965 | binder.bind(Dict[str, str], to={}) 966 | 967 | with pytest.raises(Error): 968 | binder.bind(Passwords, to={}) 969 | 970 | 971 | def test_auto_bind(): 972 | class A: 973 | pass 974 | 975 | injector = Injector() 976 | assert isinstance(injector.get(A), A) 977 | 978 | 979 | def test_auto_bind_with_newtype(): 980 | # Reported in https://github.com/alecthomas/injector/issues/117 981 | class A: 982 | pass 983 | 984 | AliasOfA = NewType('AliasOfA', A) 985 | injector = Injector() 986 | assert isinstance(injector.get(AliasOfA), A) 987 | 988 | 989 | class Request: 990 | pass 991 | 992 | 993 | class RequestScope(Scope): 994 | def configure(self): 995 | self.context = None 996 | 997 | @contextmanager 998 | def __call__(self, request): 999 | assert self.context is None 1000 | self.context = {} 1001 | binder = self.injector.get(Binder) 1002 | binder.bind(Request, to=request, scope=RequestScope) 1003 | yield 1004 | self.context = None 1005 | 1006 | def get(self, key, provider): 1007 | if self.context is None: 1008 | raise UnsatisfiedRequirement(None, key) 1009 | try: 1010 | return self.context[key] 1011 | except KeyError: 1012 | provider = InstanceProvider(provider.get(self.injector)) 1013 | self.context[key] = provider 1014 | return provider 1015 | 1016 | 1017 | request = ScopeDecorator(RequestScope) 1018 | 1019 | 1020 | @request 1021 | class Handler: 1022 | def __init__(self, request): 1023 | self.request = request 1024 | 1025 | 1026 | class RequestModule(Module): 1027 | @provider 1028 | @inject 1029 | def handler(self, request: Request) -> Handler: 1030 | return Handler(request) 1031 | 1032 | 1033 | def test_custom_scope(): 1034 | injector = Injector([RequestModule()], auto_bind=False) 1035 | 1036 | with pytest.raises(UnsatisfiedRequirement): 1037 | injector.get(Handler) 1038 | 1039 | scope = injector.get(RequestScope) 1040 | request = Request() 1041 | 1042 | with scope(request): 1043 | handler = injector.get(Handler) 1044 | assert handler.request is request 1045 | 1046 | with pytest.raises(UnsatisfiedRequirement): 1047 | injector.get(Handler) 1048 | 1049 | 1050 | def test_binder_install(): 1051 | class ModuleA(Module): 1052 | def configure(self, binder): 1053 | binder.bind(str, to='hello world') 1054 | 1055 | class ModuleB(Module): 1056 | def configure(self, binder): 1057 | binder.install(ModuleA()) 1058 | 1059 | injector = Injector([ModuleB()]) 1060 | assert injector.get(str) == 'hello world' 1061 | 1062 | 1063 | def test_binder_provider_for_method_with_explicit_provider(): 1064 | injector = Injector() 1065 | binder = injector.binder 1066 | provider = binder.provider_for(int, to=InstanceProvider(1)) 1067 | assert type(provider) is InstanceProvider 1068 | assert provider.get(injector) == 1 1069 | 1070 | 1071 | def test_binder_provider_for_method_with_instance(): 1072 | injector = Injector() 1073 | binder = injector.binder 1074 | provider = binder.provider_for(int, to=1) 1075 | assert type(provider) is InstanceProvider 1076 | assert provider.get(injector) == 1 1077 | 1078 | 1079 | def test_binder_provider_for_method_with_class(): 1080 | injector = Injector() 1081 | binder = injector.binder 1082 | provider = binder.provider_for(int) 1083 | assert type(provider) is ClassProvider 1084 | assert provider.get(injector) == 0 1085 | 1086 | 1087 | def test_binder_provider_for_method_with_class_to_specific_subclass(): 1088 | class A: 1089 | pass 1090 | 1091 | class B(A): 1092 | pass 1093 | 1094 | injector = Injector() 1095 | binder = injector.binder 1096 | provider = binder.provider_for(A, B) 1097 | assert type(provider) is ClassProvider 1098 | assert isinstance(provider.get(injector), B) 1099 | 1100 | 1101 | def test_binder_provider_for_type_with_metaclass(): 1102 | # use a metaclass cross python2/3 way 1103 | # otherwise should be: 1104 | # class A(object, metaclass=abc.ABCMeta): 1105 | # passa 1106 | A = abc.ABCMeta('A', (object,), {}) 1107 | 1108 | injector = Injector() 1109 | binder = injector.binder 1110 | assert isinstance(binder.provider_for(A, None).get(injector), A) 1111 | 1112 | 1113 | class ClassA: 1114 | def __init__(self, parameter): 1115 | pass 1116 | 1117 | 1118 | class ClassB: 1119 | @inject 1120 | def __init__(self, a: ClassA): 1121 | pass 1122 | 1123 | 1124 | def test_injecting_undecorated_class_with_missing_dependencies_raises_the_right_error(): 1125 | injector = Injector() 1126 | try: 1127 | injector.get(ClassB) 1128 | except CallError as ce: 1129 | check_exception_contains_stuff(ce, ('ClassA.__init__', 'ClassB')) 1130 | 1131 | 1132 | def test_call_to_method_with_legitimate_call_error_raises_type_error(): 1133 | class A: 1134 | def __init__(self): 1135 | max() 1136 | 1137 | injector = Injector() 1138 | with pytest.raises(TypeError): 1139 | injector.get(A) 1140 | 1141 | 1142 | def test_call_error_str_representation_handles_single_arg(): 1143 | ce = CallError('zxc') 1144 | assert str(ce) == 'zxc' 1145 | 1146 | 1147 | class NeedsAssistance: 1148 | @inject 1149 | def __init__(self, a: str, b): 1150 | self.a = a 1151 | self.b = b 1152 | 1153 | 1154 | def test_assisted_builder_works_when_got_directly_from_injector(): 1155 | injector = Injector() 1156 | builder = injector.get(AssistedBuilder[NeedsAssistance]) 1157 | obj = builder.build(b=123) 1158 | assert (obj.a, obj.b) == (str(), 123) 1159 | 1160 | 1161 | def test_assisted_builder_works_when_injected(): 1162 | class X: 1163 | @inject 1164 | def __init__(self, builder: AssistedBuilder[NeedsAssistance]): 1165 | self.obj = builder.build(b=234) 1166 | 1167 | injector = Injector() 1168 | x = injector.get(X) 1169 | assert (x.obj.a, x.obj.b) == (str(), 234) 1170 | 1171 | 1172 | class Interface: 1173 | b = 0 1174 | 1175 | 1176 | def test_assisted_builder_uses_bindings(): 1177 | def configure(binder): 1178 | binder.bind(Interface, to=NeedsAssistance) 1179 | 1180 | injector = Injector(configure) 1181 | builder = injector.get(AssistedBuilder[Interface]) 1182 | x = builder.build(b=333) 1183 | assert (type(x), x.b) == (NeedsAssistance, 333) 1184 | 1185 | 1186 | def test_assisted_builder_uses_concrete_class_when_specified(): 1187 | class X: 1188 | pass 1189 | 1190 | def configure(binder): 1191 | # meant only to show that provider isn't called 1192 | binder.bind(X, to=lambda: 1 / 0) 1193 | 1194 | injector = Injector(configure) 1195 | builder = injector.get(ClassAssistedBuilder[X]) 1196 | builder.build() 1197 | 1198 | 1199 | def test_assisted_builder_injection_is_safe_to_use_with_multiple_injectors(): 1200 | class X: 1201 | @inject 1202 | def __init__(self, builder: AssistedBuilder[NeedsAssistance]): 1203 | self.builder = builder 1204 | 1205 | i1, i2 = Injector(), Injector() 1206 | b1 = i1.get(X).builder 1207 | b2 = i2.get(X).builder 1208 | assert (b1._injector, b2._injector) == (i1, i2) 1209 | 1210 | 1211 | def test_assisted_builder_injection_is_safe_to_use_with_child_injectors(): 1212 | class X: 1213 | @inject 1214 | def __init__(self, builder: AssistedBuilder[NeedsAssistance]): 1215 | self.builder = builder 1216 | 1217 | i1 = Injector() 1218 | i2 = i1.create_child_injector() 1219 | b1 = i1.get(X).builder 1220 | b2 = i2.get(X).builder 1221 | assert (b1._injector, b2._injector) == (i1, i2) 1222 | 1223 | 1224 | class TestThreadSafety: 1225 | def setup_method(self): 1226 | self.event = threading.Event() 1227 | 1228 | def configure(binder): 1229 | binder.bind(str, to=lambda: self.event.wait() and 'this is str') 1230 | 1231 | class XXX: 1232 | @inject 1233 | def __init__(self, s: str): 1234 | pass 1235 | 1236 | self.injector = Injector(configure) 1237 | self.cls = XXX 1238 | 1239 | def gather_results(self, count): 1240 | objects = [] 1241 | lock = threading.Lock() 1242 | 1243 | def target(): 1244 | o = self.injector.get(self.cls) 1245 | with lock: 1246 | objects.append(o) 1247 | 1248 | threads = [threading.Thread(target=target) for i in range(count)] 1249 | 1250 | for t in threads: 1251 | t.start() 1252 | 1253 | self.event.set() 1254 | 1255 | for t in threads: 1256 | t.join() 1257 | 1258 | return objects 1259 | 1260 | def test_injection_is_thread_safe(self): 1261 | objects = self.gather_results(2) 1262 | assert len(objects) == 2 1263 | 1264 | def test_singleton_scope_is_thread_safe(self): 1265 | self.injector.binder.bind(self.cls, scope=singleton) 1266 | a, b = self.gather_results(2) 1267 | assert a is b 1268 | 1269 | 1270 | def test_provider_and_scope_decorator_collaboration(): 1271 | @provider 1272 | @singleton 1273 | def provider_singleton() -> int: 1274 | return 10 1275 | 1276 | @singleton 1277 | @provider 1278 | def singleton_provider() -> int: 1279 | return 10 1280 | 1281 | assert provider_singleton.__binding__.scope == SingletonScope 1282 | assert singleton_provider.__binding__.scope == SingletonScope 1283 | 1284 | 1285 | def test_injecting_into_method_of_object_that_is_falseish_works(): 1286 | # regression test 1287 | 1288 | class X(dict): 1289 | @inject 1290 | def __init__(self, s: str): 1291 | pass 1292 | 1293 | injector = Injector() 1294 | injector.get(X) 1295 | 1296 | 1297 | Name = NewType("Name", str) 1298 | Message = NewType("Message", str) 1299 | 1300 | 1301 | def test_callable_provider_injection(): 1302 | @inject 1303 | def create_message(name: Name): 1304 | return "Hello, " + name 1305 | 1306 | def configure(binder): 1307 | binder.bind(Name, to="John") 1308 | binder.bind(Message, to=create_message) 1309 | 1310 | injector = Injector([configure]) 1311 | msg = injector.get(Message) 1312 | assert msg == "Hello, John" 1313 | 1314 | 1315 | def test_providerof(): 1316 | counter = [0] 1317 | 1318 | def provide_str(): 1319 | counter[0] += 1 1320 | return 'content' 1321 | 1322 | def configure(binder): 1323 | binder.bind(str, to=provide_str) 1324 | 1325 | injector = Injector(configure) 1326 | 1327 | assert counter[0] == 0 1328 | 1329 | provider = injector.get(ProviderOf[str]) 1330 | assert counter[0] == 0 1331 | 1332 | assert provider.get() == 'content' 1333 | assert counter[0] == 1 1334 | 1335 | assert provider.get() == injector.get(str) 1336 | assert counter[0] == 3 1337 | 1338 | 1339 | def test_providerof_cannot_be_bound(): 1340 | def configure(binder): 1341 | binder.bind(ProviderOf[int], to=InstanceProvider(None)) 1342 | 1343 | with pytest.raises(Exception): 1344 | Injector(configure) 1345 | 1346 | 1347 | def test_providerof_is_safe_to_use_with_multiple_injectors(): 1348 | def configure1(binder): 1349 | binder.bind(int, to=1) 1350 | 1351 | def configure2(binder): 1352 | binder.bind(int, to=2) 1353 | 1354 | injector1 = Injector(configure1) 1355 | injector2 = Injector(configure2) 1356 | 1357 | provider_of = ProviderOf[int] 1358 | 1359 | provider1 = injector1.get(provider_of) 1360 | provider2 = injector2.get(provider_of) 1361 | 1362 | assert provider1.get() == 1 1363 | assert provider2.get() == 2 1364 | 1365 | 1366 | def test_special_interfaces_work_with_auto_bind_disabled(): 1367 | class InjectMe: 1368 | pass 1369 | 1370 | def configure(binder): 1371 | binder.bind(InjectMe, to=InstanceProvider(InjectMe())) 1372 | 1373 | injector = Injector(configure, auto_bind=False) 1374 | 1375 | # This line used to fail with: 1376 | # Traceback (most recent call last): 1377 | # File "/projects/injector/injector_test.py", line 1171, 1378 | # in test_auto_bind_disabled_regressions 1379 | # injector.get(ProviderOf(InjectMe)) 1380 | # File "/projects/injector/injector.py", line 687, in get 1381 | # binding = self.binder.get_binding(None, key) 1382 | # File "/projects/injector/injector.py", line 459, in get_binding 1383 | # raise UnsatisfiedRequirement(cls, key) 1384 | # UnsatisfiedRequirement: unsatisfied requirement on 1385 | # 1386 | injector.get(ProviderOf[InjectMe]) 1387 | 1388 | # This used to fail with an error similar to the ProviderOf one 1389 | injector.get(ClassAssistedBuilder[InjectMe]) 1390 | 1391 | 1392 | def test_binding_an_instance_regression(): 1393 | text = b'hello'.decode() 1394 | 1395 | def configure(binder): 1396 | # Yes, this binding doesn't make sense strictly speaking but 1397 | # it's just a sample case. 1398 | binder.bind(bytes, to=text) 1399 | 1400 | injector = Injector(configure) 1401 | # This used to return empty bytes instead of the expected string 1402 | assert injector.get(bytes) == text 1403 | 1404 | 1405 | class PartialB: 1406 | @inject 1407 | def __init__(self, a: EmptyClass, b: str): 1408 | self.a = a 1409 | self.b = b 1410 | 1411 | 1412 | def test_class_assisted_builder_of_partially_injected_class_old(): 1413 | class C: 1414 | @inject 1415 | def __init__(self, a: EmptyClass, builder: ClassAssistedBuilder[PartialB]): 1416 | self.a = a 1417 | self.b = builder.build(b='C') 1418 | 1419 | c = Injector().get(C) 1420 | assert isinstance(c, C) 1421 | assert isinstance(c.b, PartialB) 1422 | assert isinstance(c.b.a, EmptyClass) 1423 | 1424 | 1425 | class ImplicitA: 1426 | pass 1427 | 1428 | 1429 | class ImplicitB: 1430 | @inject 1431 | def __init__(self, a: ImplicitA): 1432 | self.a = a 1433 | 1434 | 1435 | class ImplicitC: 1436 | @inject 1437 | def __init__(self, b: ImplicitB): 1438 | self.b = b 1439 | 1440 | 1441 | def test_implicit_injection_for_python3(): 1442 | injector = Injector() 1443 | c = injector.get(ImplicitC) 1444 | assert isinstance(c, ImplicitC) 1445 | assert isinstance(c.b, ImplicitB) 1446 | assert isinstance(c.b.a, ImplicitA) 1447 | 1448 | 1449 | def test_annotation_based_injection_works_in_provider_methods(): 1450 | class MyModule(Module): 1451 | def configure(self, binder): 1452 | binder.bind(int, to=42) 1453 | 1454 | @provider 1455 | def provide_str(self, i: int) -> str: 1456 | return str(i) 1457 | 1458 | @singleton 1459 | @provider 1460 | def provide_object(self) -> object: 1461 | return object() 1462 | 1463 | injector = Injector(MyModule) 1464 | assert injector.get(str) == '42' 1465 | assert injector.get(object) is injector.get(object) 1466 | 1467 | 1468 | class Fetcher: 1469 | def fetch(self, user_id): 1470 | assert user_id == 333 1471 | return {'name': 'John'} 1472 | 1473 | 1474 | class Processor: 1475 | @noninjectable('provider_id') 1476 | @inject 1477 | @noninjectable('user_id') 1478 | def __init__(self, fetcher: Fetcher, user_id: int, provider_id: str): 1479 | assert provider_id == 'not injected' 1480 | data = fetcher.fetch(user_id) 1481 | self.name = data['name'] 1482 | 1483 | 1484 | def test_assisted_building_is_supported(): 1485 | def configure(binder): 1486 | binder.bind(int, to=897) 1487 | binder.bind(str, to='injected') 1488 | 1489 | injector = Injector(configure) 1490 | processor_builder = injector.get(AssistedBuilder[Processor]) 1491 | 1492 | with pytest.raises(CallError): 1493 | processor_builder.build() 1494 | 1495 | processor = processor_builder.build(user_id=333, provider_id='not injected') 1496 | assert processor.name == 'John' 1497 | 1498 | 1499 | def test_raises_when_noninjectable_arguments_defined_with_invalid_arguments(): 1500 | with pytest.raises(UnknownArgument): 1501 | 1502 | class A: 1503 | @inject 1504 | @noninjectable('c') 1505 | def __init__(self, b: str): 1506 | self.b = b 1507 | 1508 | 1509 | def test_can_create_instance_with_untyped_noninjectable_argument(): 1510 | class Parent: 1511 | @inject 1512 | @noninjectable('child1', 'child2') 1513 | def __init__(self, child1, *, child2): 1514 | self.child1 = child1 1515 | self.child2 = child2 1516 | 1517 | injector = Injector() 1518 | parent_builder = injector.get(AssistedBuilder[Parent]) 1519 | parent = parent_builder.build(child1='injected1', child2='injected2') 1520 | 1521 | assert parent.child1 == 'injected1' 1522 | assert parent.child2 == 'injected2' 1523 | 1524 | 1525 | def test_implicit_injection_fails_when_annotations_are_missing(): 1526 | class A: 1527 | def __init__(self, n): 1528 | self.n = n 1529 | 1530 | injector = Injector() 1531 | with pytest.raises(CallError): 1532 | injector.get(A) 1533 | 1534 | 1535 | def test_injection_works_in_presence_of_return_value_annotation(): 1536 | # Code with PEP 484-compatible type hints will have __init__ methods 1537 | # annotated as returning None[1] and this didn't work well with Injector. 1538 | # 1539 | # [1] https://www.python.org/dev/peps/pep-0484/#the-meaning-of-annotations 1540 | 1541 | class A: 1542 | @inject 1543 | def __init__(self, s: str) -> None: 1544 | self.s = s 1545 | 1546 | def configure(binder): 1547 | binder.bind(str, to='this is string') 1548 | 1549 | injector = Injector([configure]) 1550 | 1551 | # Used to fail with: 1552 | # injector.UnknownProvider: couldn't determine provider for None to None 1553 | a = injector.get(A) 1554 | 1555 | # Just a sanity check, if the code above worked we're almost certain 1556 | # we're good but just in case the return value annotation handling changed 1557 | # something: 1558 | assert a.s == 'this is string' 1559 | 1560 | 1561 | def test_things_dont_break_in_presence_of_args_or_kwargs(): 1562 | class A: 1563 | @inject 1564 | def __init__(self, s: str, *args: int, **kwargs: str): 1565 | assert not args 1566 | assert not kwargs 1567 | 1568 | injector = Injector() 1569 | 1570 | # The following line used to fail with something like this: 1571 | # Traceback (most recent call last): 1572 | # File "/ve/injector/injector_test_py3.py", line 192, 1573 | # in test_things_dont_break_in_presence_of_args_or_kwargs 1574 | # injector.get(A) 1575 | # File "/ve/injector/injector.py", line 707, in get 1576 | # result = scope_instance.get(key, binding.provider).get(self) 1577 | # File "/ve/injector/injector.py", line 142, in get 1578 | # return injector.create_object(self._cls) 1579 | # File "/ve/injector/injector.py", line 744, in create_object 1580 | # init(instance, **additional_kwargs) 1581 | # File "/ve/injector/injector.py", line 1082, in inject 1582 | # kwargs=kwargs 1583 | # File "/ve/injector/injector.py", line 851, in call_with_injection 1584 | # **dependencies) 1585 | # File "/ve/injector/injector_test_py3.py", line 189, in __init__ 1586 | # assert not kwargs 1587 | # AssertionError: assert not {'args': 0, 'kwargs': ''} 1588 | injector.get(A) 1589 | 1590 | 1591 | def test_forward_references_in_annotations_are_handled(): 1592 | # See https://www.python.org/dev/peps/pep-0484/#forward-references for details 1593 | 1594 | class CustomModule(Module): 1595 | @provider 1596 | def provide_x(self) -> 'X': 1597 | return X('hello') 1598 | 1599 | @inject 1600 | def fun(s: 'X') -> 'X': 1601 | return s 1602 | 1603 | # The class needs to be module-global in order for the string -> object 1604 | # resolution mechanism to work. I could make it work with locals but it 1605 | # doesn't seem worth it. 1606 | global X 1607 | 1608 | class X: 1609 | def __init__(self, message: str) -> None: 1610 | self.message = message 1611 | 1612 | try: 1613 | injector = Injector(CustomModule) 1614 | assert injector.call_with_injection(fun).message == 'hello' 1615 | finally: 1616 | del X 1617 | 1618 | 1619 | def test_more_useful_exception_is_raised_when_parameters_type_is_any(): 1620 | @inject 1621 | def fun(a: Any) -> None: 1622 | pass 1623 | 1624 | injector = Injector() 1625 | 1626 | # This was the exception before: 1627 | # 1628 | # TypeError: Cannot instantiate 1629 | # 1630 | # Now: 1631 | # 1632 | # injector.CallError: Call to AnyMeta.__new__() failed: Cannot instantiate 1633 | # (injection stack: ['injector_test_py3']) 1634 | # 1635 | # In this case the injection stack doesn't provide too much information but 1636 | # it quickly gets helpful when the stack gets deeper. 1637 | with pytest.raises((CallError, TypeError)): 1638 | injector.call_with_injection(fun) 1639 | 1640 | 1641 | def test_optionals_are_ignored_for_now(): 1642 | @inject 1643 | def fun(s: str = None): 1644 | return s 1645 | 1646 | assert Injector().call_with_injection(fun) == '' 1647 | 1648 | 1649 | def test_explicitly_passed_parameters_override_injectable_values(): 1650 | # The class needs to be defined globally for the 'X' forward reference to be able to be resolved. 1651 | global X 1652 | 1653 | # We test a method on top of regular function to exercise the code path that's 1654 | # responsible for handling methods. 1655 | class X: 1656 | @inject 1657 | def method(self, s: str) -> str: 1658 | return s 1659 | 1660 | @inject 1661 | def method_typed_self(self: 'X', s: str) -> str: 1662 | return s 1663 | 1664 | @inject 1665 | def function(s: str) -> str: 1666 | return s 1667 | 1668 | injection_counter = 0 1669 | 1670 | def provide_str() -> str: 1671 | nonlocal injection_counter 1672 | injection_counter += 1 1673 | return 'injected string' 1674 | 1675 | def configure(binder: Binder) -> None: 1676 | binder.bind(str, to=provide_str) 1677 | 1678 | injector = Injector([configure]) 1679 | x = X() 1680 | 1681 | try: 1682 | assert injection_counter == 0 1683 | 1684 | assert injector.call_with_injection(x.method) == 'injected string' 1685 | assert injection_counter == 1 1686 | assert injector.call_with_injection(x.method_typed_self) == 'injected string' 1687 | assert injection_counter == 2 1688 | assert injector.call_with_injection(function) == 'injected string' 1689 | assert injection_counter == 3 1690 | 1691 | assert injector.call_with_injection(x.method, args=('passed string',)) == 'passed string' 1692 | assert injection_counter == 3 1693 | assert injector.call_with_injection(x.method_typed_self, args=('passed string',)) == 'passed string' 1694 | assert injection_counter == 3 1695 | assert injector.call_with_injection(function, args=('passed string',)) == 'passed string' 1696 | assert injection_counter == 3 1697 | 1698 | assert injector.call_with_injection(x.method, kwargs={'s': 'passed string'}) == 'passed string' 1699 | assert injection_counter == 3 1700 | assert ( 1701 | injector.call_with_injection(x.method_typed_self, kwargs={'s': 'passed string'}) 1702 | == 'passed string' 1703 | ) 1704 | assert injection_counter == 3 1705 | assert injector.call_with_injection(function, kwargs={'s': 'passed string'}) == 'passed string' 1706 | assert injection_counter == 3 1707 | finally: 1708 | del X 1709 | 1710 | 1711 | class AssistedB: 1712 | @inject 1713 | def __init__(self, a: EmptyClass, b: str): 1714 | self.a = a 1715 | self.b = b 1716 | 1717 | 1718 | def test_class_assisted_builder_of_partially_injected_class(): 1719 | class C: 1720 | @inject 1721 | def __init__(self, a: EmptyClass, builder: ClassAssistedBuilder[AssistedB]): 1722 | self.a = a 1723 | self.b = builder.build(b='C') 1724 | 1725 | c = Injector().get(C) 1726 | assert isinstance(c, C) 1727 | assert isinstance(c.b, AssistedB) 1728 | assert isinstance(c.b.a, EmptyClass) 1729 | 1730 | 1731 | # The test taken from Alec Thomas' pull request: https://github.com/alecthomas/injector/pull/73 1732 | def test_child_scope(): 1733 | TestKey = NewType('TestKey', str) 1734 | TestKey2 = NewType('TestKey2', str) 1735 | 1736 | def parent_module(binder): 1737 | binder.bind(TestKey, to='in parent', scope=singleton) 1738 | 1739 | def first_child_module(binder): 1740 | binder.bind(TestKey2, to='in first child', scope=singleton) 1741 | 1742 | def second_child_module(binder): 1743 | binder.bind(TestKey2, to='in second child', scope=singleton) 1744 | 1745 | injector = Injector(modules=[parent_module]) 1746 | first_child_injector = injector.create_child_injector(modules=[first_child_module]) 1747 | second_child_injector = injector.create_child_injector(modules=[second_child_module]) 1748 | 1749 | assert first_child_injector.get(TestKey) is first_child_injector.get(TestKey) 1750 | assert first_child_injector.get(TestKey) is second_child_injector.get(TestKey) 1751 | assert first_child_injector.get(TestKey2) is not second_child_injector.get(TestKey2) 1752 | 1753 | 1754 | def test_custom_scopes_work_as_expected_with_child_injectors(): 1755 | class CustomSingletonScope(SingletonScope): 1756 | pass 1757 | 1758 | custom_singleton = ScopeDecorator(CustomSingletonScope) 1759 | 1760 | def parent_module(binder): 1761 | binder.bind(str, to='parent value', scope=custom_singleton) 1762 | 1763 | def child_module(binder): 1764 | binder.bind(str, to='child value', scope=custom_singleton) 1765 | 1766 | parent = Injector(modules=[parent_module]) 1767 | child = parent.create_child_injector(modules=[child_module]) 1768 | print('parent, child: %s, %s' % (parent, child)) 1769 | assert parent.get(str) == 'parent value' 1770 | assert child.get(str) == 'child value' 1771 | 1772 | 1773 | # Test for https://github.com/alecthomas/injector/issues/75 1774 | def test_inject_decorator_does_not_break_manual_construction_of_pyqt_objects(): 1775 | class PyQtFake: 1776 | @inject 1777 | def __init__(self): 1778 | pass 1779 | 1780 | def __getattribute__(self, item): 1781 | if item == '__injector__': 1782 | raise RuntimeError( 1783 | 'A PyQt class would raise this exception if getting ' 1784 | 'self.__injector__ before __init__ is called and ' 1785 | 'self.__injector__ has not been set by Injector.' 1786 | ) 1787 | return object.__getattribute__(self, item) 1788 | 1789 | instance = PyQtFake() # This used to raise the exception 1790 | 1791 | assert isinstance(instance, PyQtFake) 1792 | 1793 | 1794 | def test_using_an_assisted_builder_with_a_provider_raises_an_injector_error(): 1795 | class MyModule(Module): 1796 | @provider 1797 | def provide_a(self, builder: AssistedBuilder[EmptyClass]) -> EmptyClass: 1798 | return builder.build() 1799 | 1800 | injector = Injector(MyModule) 1801 | 1802 | with pytest.raises(Error): 1803 | injector.get(EmptyClass) 1804 | 1805 | 1806 | def test_newtype_integration_works(): 1807 | UserID = NewType('UserID', int) 1808 | 1809 | def configure(binder): 1810 | binder.bind(UserID, to=123) 1811 | 1812 | injector = Injector([configure]) 1813 | assert injector.get(UserID) == 123 1814 | 1815 | 1816 | def test_dataclass_integration_works(): 1817 | import dataclasses 1818 | 1819 | @inject 1820 | @dataclasses.dataclass 1821 | class Data: 1822 | name: str 1823 | 1824 | def configure(binder): 1825 | binder.bind(str, to='data') 1826 | 1827 | injector = Injector([configure]) 1828 | assert injector.get(Data).name == 'data' 1829 | 1830 | 1831 | def test_binder_does_not_have_a_binding_for_an_unbound_type(): 1832 | injector = Injector() 1833 | assert not injector.binder.has_binding_for(int) 1834 | assert not injector.binder.has_explicit_binding_for(int) 1835 | 1836 | 1837 | def test_binder_has_binding_for_explicitly_bound_type(): 1838 | def configure(binder): 1839 | binder.bind(int, to=123) 1840 | 1841 | injector = Injector([configure]) 1842 | assert injector.binder.has_binding_for(int) 1843 | assert injector.binder.has_explicit_binding_for(int) 1844 | 1845 | 1846 | def test_binder_has_implicit_binding_for_implicitly_bound_type(): 1847 | injector = Injector() 1848 | 1849 | injector.get(int) 1850 | 1851 | assert injector.binder.has_binding_for(int) 1852 | assert not injector.binder.has_explicit_binding_for(int) 1853 | 1854 | 1855 | def test_gets_no_bindings_without_injection() -> None: 1856 | def function(a: int) -> None: 1857 | pass 1858 | 1859 | assert get_bindings(function) == {} 1860 | 1861 | 1862 | def test_gets_bindings_with_inject_decorator() -> None: 1863 | @inject 1864 | def function(a: int) -> None: 1865 | pass 1866 | 1867 | assert get_bindings(function) == {'a': int} 1868 | 1869 | 1870 | def test_gets_multiple_bindings_with_inject_decorator() -> None: 1871 | @inject 1872 | def function(a: int, b: str) -> None: 1873 | pass 1874 | 1875 | assert get_bindings(function) == {'a': int, 'b': str} 1876 | 1877 | 1878 | def test_only_gets_injectable_bindings_without_noninjectable_decorator() -> None: 1879 | @inject 1880 | @noninjectable('b') 1881 | def function1(a: int, b: str) -> None: 1882 | pass 1883 | 1884 | # Let's verify that the inject/noninjectable ordering doesn't matter 1885 | @noninjectable('b') 1886 | @inject 1887 | def function2(a: int, b: str) -> None: 1888 | pass 1889 | 1890 | assert get_bindings(function1) == {'a': int} == get_bindings(function2) 1891 | 1892 | 1893 | def test_gets_bindings_with_inject_annotation() -> None: 1894 | def function(a: Inject[int], b: str) -> None: 1895 | pass 1896 | 1897 | assert get_bindings(function) == {'a': int} 1898 | 1899 | 1900 | def test_gets_multiple_bindings_with_inject_annotation() -> None: 1901 | def function(a: Inject[int], b: Inject[str]) -> None: 1902 | pass 1903 | 1904 | assert get_bindings(function) == {'a': int, 'b': str} 1905 | 1906 | 1907 | def test_gets_bindings_inject_with_redundant_inject_annotation() -> None: 1908 | @inject 1909 | def function(a: Inject[int], b: str) -> None: 1910 | pass 1911 | 1912 | assert get_bindings(function) == {'a': int, 'b': str} 1913 | 1914 | 1915 | def test_only_gets_bindings_without_noinject_annotation() -> None: 1916 | @inject 1917 | def function(a: int, b: NoInject[str]) -> None: 1918 | pass 1919 | 1920 | assert get_bindings(function) == {'a': int} 1921 | 1922 | 1923 | def test_gets_no_bindings_for_noinject_annotation_only() -> None: 1924 | def function(a: int, b: NoInject[str]) -> None: 1925 | pass 1926 | 1927 | assert get_bindings(function) == {} 1928 | 1929 | 1930 | def test_gets_no_bindings_for_multiple_noinject_annotations() -> None: 1931 | # There was a bug where in case of multiple NoInject-decorated parameters only the first one was 1932 | # actually made noninjectable and we tried to inject something we couldn't possibly provide 1933 | # into the second one. 1934 | @inject 1935 | def function(a: NoInject[int], b: NoInject[int]) -> None: 1936 | pass 1937 | 1938 | assert get_bindings(function) == {} 1939 | 1940 | 1941 | def test_get_bindings_noinject_with_default_should_behave_identically() -> None: 1942 | @inject 1943 | @noninjectable('b') 1944 | def function1(self, a: int, b: Optional[str] = None) -> None: 1945 | pass 1946 | 1947 | @inject 1948 | def function2(self, a: int, b: NoInject[Optional[str]] = None) -> None: 1949 | # b's type is Union[NoInject[Union[str, None]], None] 1950 | pass 1951 | 1952 | assert get_bindings(function1) == {'a': int} == get_bindings(function2) 1953 | 1954 | 1955 | def test_get_bindings_with_an_invalid_forward_reference_return_type() -> None: 1956 | # If there's a return type annottion that contains an a forward reference that can't be 1957 | # resolved (for whatever reason) we don't want that to break things for us – return types 1958 | # don't matter for the purpose of dependency injection. 1959 | @inject 1960 | def function(a: int) -> 'InvalidForwardReference': 1961 | pass 1962 | 1963 | assert get_bindings(function) == {'a': int} 1964 | 1965 | 1966 | def test_gets_bindings_for_annotated_type_with_inject_decorator() -> None: 1967 | UserID = Annotated[int, 'user_id'] 1968 | 1969 | @inject 1970 | def function(a: UserID, b: str) -> None: 1971 | pass 1972 | 1973 | assert get_bindings(function) == {'a': UserID, 'b': str} 1974 | 1975 | 1976 | def test_gets_bindings_of_annotated_type_with_inject_annotation() -> None: 1977 | UserID = Annotated[int, 'user_id'] 1978 | 1979 | def function(a: Inject[UserID], b: Inject[str]) -> None: 1980 | pass 1981 | 1982 | assert get_bindings(function) == {'a': UserID, 'b': str} 1983 | 1984 | 1985 | def test_gets_bindings_of_new_type_with_inject_annotation() -> None: 1986 | Name = NewType('Name', str) 1987 | 1988 | @inject 1989 | def function(a: Name, b: str) -> None: 1990 | pass 1991 | 1992 | assert get_bindings(function) == {'a': Name, 'b': str} 1993 | 1994 | 1995 | def test_gets_bindings_of_inject_annotation_with_new_type() -> None: 1996 | def function(a: Inject[Name], b: str) -> None: 1997 | pass 1998 | 1999 | assert get_bindings(function) == {'a': Name} 2000 | 2001 | 2002 | def test_get_bindings_of_nested_noinject_inject_annotation() -> None: 2003 | # This is not how this is intended to be used 2004 | def function(a: Inject[NoInject[int]], b: NoInject[Inject[str]]) -> None: 2005 | pass 2006 | 2007 | assert get_bindings(function) == {} 2008 | 2009 | 2010 | def test_get_bindings_of_nested_noinject_inject_annotation_and_inject_decorator() -> None: 2011 | # This is not how this is intended to be used 2012 | @inject 2013 | def function(a: Inject[NoInject[int]], b: NoInject[Inject[str]]) -> None: 2014 | pass 2015 | 2016 | assert get_bindings(function) == {} 2017 | 2018 | 2019 | def test_get_bindings_of_nested_inject_annotations() -> None: 2020 | # This is not how this is intended to be used 2021 | def function(a: Inject[Inject[int]]) -> None: 2022 | pass 2023 | 2024 | assert get_bindings(function) == {'a': int} 2025 | 2026 | 2027 | # Tests https://github.com/alecthomas/injector/issues/202 2028 | @pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+") 2029 | def test_get_bindings_for_pep_604(): 2030 | @inject 2031 | def function1(a: int | None) -> None: 2032 | pass 2033 | 2034 | assert get_bindings(function1) == {'a': int} 2035 | 2036 | @inject 2037 | def function1(a: int | str) -> None: 2038 | pass 2039 | 2040 | assert get_bindings(function1) == {'a': Union[int, str]} 2041 | 2042 | 2043 | # test for https://github.com/python-injector/injector/issues/217 2044 | def test_annotated_instance_integration_works(): 2045 | UserID = Annotated[int, "user_id"] 2046 | 2047 | def configure(binder): 2048 | binder.bind(UserID, to=123) 2049 | 2050 | injector = Injector([configure]) 2051 | assert injector.get(UserID) == 123 2052 | 2053 | 2054 | def test_annotated_class_integration_works(): 2055 | class Shape(abc.ABC): 2056 | pass 2057 | 2058 | class Circle(Shape): 2059 | pass 2060 | 2061 | first = Annotated[Shape, "first"] 2062 | 2063 | def configure(binder): 2064 | binder.bind(first, to=Circle) 2065 | 2066 | injector = Injector([configure]) 2067 | assert isinstance(injector.get(first), Circle) 2068 | 2069 | 2070 | def test_annotated_meta_separate_bindings(): 2071 | first = Annotated[int, "first"] 2072 | second = Annotated[int, "second"] 2073 | 2074 | def configure(binder): 2075 | binder.bind(first, to=123) 2076 | binder.bind(second, to=456) 2077 | 2078 | injector = Injector([configure]) 2079 | assert injector.get(first) == 123 2080 | assert injector.get(second) == 456 2081 | assert injector.get(first) != injector.get(second) 2082 | 2083 | 2084 | def test_annotated_origin_separate_bindings(): 2085 | UserID = Annotated[int, "user_id"] 2086 | 2087 | def configure(binder): 2088 | binder.bind(UserID, to=123) 2089 | binder.bind(int, to=456) 2090 | 2091 | injector = Injector([configure]) 2092 | assert injector.get(UserID) == 123 2093 | assert injector.get(int) == 456 2094 | assert injector.get(UserID) != injector.get(int) 2095 | 2096 | 2097 | def test_annotated_non_comparable_types(): 2098 | foo = Annotated[int, float("nan")] 2099 | bar = Annotated[int, object()] 2100 | 2101 | def configure(binder): 2102 | binder.bind(foo, to=123) 2103 | binder.bind(bar, to=456) 2104 | 2105 | injector = Injector([configure]) 2106 | assert injector.get(foo) == 123 2107 | assert injector.get(bar) == 456 2108 | 2109 | 2110 | def test_annotated_integration_with_annotated(): 2111 | UserID = Annotated[int, 'user_id'] 2112 | UserAge = Annotated[int, 'user_age'] 2113 | 2114 | @inject 2115 | class TestClass: 2116 | def __init__(self, user_id: UserID, user_age: UserAge): 2117 | self.user_id = user_id 2118 | self.user_age = user_age 2119 | 2120 | def configure(binder): 2121 | binder.bind(UserID, to=123) 2122 | binder.bind(UserAge, to=32) 2123 | 2124 | injector = Injector([configure]) 2125 | 2126 | test_class = injector.get(TestClass) 2127 | assert test_class.user_id == 123 2128 | assert test_class.user_age == 32 2129 | 2130 | 2131 | def test_inject_annotation_with_annotated_type(): 2132 | UserID = Annotated[int, 'user_id'] 2133 | UserAge = Annotated[int, 'user_age'] 2134 | 2135 | class TestClass: 2136 | def __init__(self, user_id: Inject[UserID], user_age: Inject[UserAge]): 2137 | self.user_id = user_id 2138 | self.user_age = user_age 2139 | 2140 | def configure(binder): 2141 | binder.bind(UserID, to=123) 2142 | binder.bind(UserAge, to=32) 2143 | binder.bind(int, to=456) 2144 | 2145 | injector = Injector([configure]) 2146 | 2147 | test_class = injector.get(TestClass) 2148 | assert test_class.user_id == 123 2149 | assert test_class.user_age == 32 2150 | 2151 | 2152 | def test_inject_annotation_with_nested_annotated_type(): 2153 | UserID = Annotated[int, 'user_id'] 2154 | SpecialUserID = Annotated[UserID, 'special_user_id'] 2155 | 2156 | class TestClass: 2157 | def __init__(self, user_id: Inject[SpecialUserID]): 2158 | self.user_id = user_id 2159 | 2160 | def configure(binder): 2161 | binder.bind(SpecialUserID, to=123) 2162 | 2163 | injector = Injector([configure]) 2164 | 2165 | test_class = injector.get(TestClass) 2166 | assert test_class.user_id == 123 2167 | 2168 | 2169 | def test_noinject_annotation_with_annotated_type(): 2170 | UserID = Annotated[int, 'user_id'] 2171 | 2172 | @inject 2173 | class TestClass: 2174 | def __init__(self, user_id: NoInject[UserID] = None): 2175 | self.user_id = user_id 2176 | 2177 | def configure(binder): 2178 | binder.bind(UserID, to=123) 2179 | 2180 | injector = Injector([configure]) 2181 | 2182 | test_class = injector.get(TestClass) 2183 | assert test_class.user_id is None 2184 | 2185 | 2186 | def test_newtype_integration_with_annotated(): 2187 | UserID = NewType('UserID', int) 2188 | 2189 | @inject 2190 | class TestClass: 2191 | def __init__(self, user_id: UserID): 2192 | self.user_id = user_id 2193 | 2194 | def configure(binder): 2195 | binder.bind(UserID, to=123) 2196 | 2197 | injector = Injector([configure]) 2198 | 2199 | test_class = injector.get(TestClass) 2200 | assert test_class.user_id == 123 2201 | 2202 | 2203 | def test_newtype_with_injection_annotation(): 2204 | UserID = NewType('UserID', int) 2205 | 2206 | class TestClass: 2207 | def __init__(self, user_id: Inject[UserID]): 2208 | self.user_id = user_id 2209 | 2210 | def configure(binder): 2211 | binder.bind(UserID, to=123) 2212 | 2213 | injector = Injector([configure]) 2214 | 2215 | test_class = injector.get(TestClass) 2216 | assert test_class.user_id == 123 2217 | 2218 | 2219 | def test_dataclass_annotated_parameter(): 2220 | Foo = Annotated[int, object()] 2221 | 2222 | def configure(binder): 2223 | binder.bind(Foo, to=123) 2224 | 2225 | @inject 2226 | @dataclass 2227 | class MyClass: 2228 | foo: Foo 2229 | 2230 | injector = Injector([configure]) 2231 | instance = injector.get(MyClass) 2232 | assert instance.foo == 123 2233 | 2234 | 2235 | def test_module_provider_with_annotated(): 2236 | class MyModule(Module): 2237 | @provider 2238 | def provide_first(self) -> Annotated[str, 'first']: 2239 | return 'Bob' 2240 | 2241 | @provider 2242 | def provide_second(self) -> Annotated[str, 'second']: 2243 | return 'Iger' 2244 | 2245 | module = MyModule() 2246 | injector = Injector(module) 2247 | assert injector.get(Annotated[str, 'first']) == 'Bob' 2248 | assert injector.get(Annotated[str, 'second']) == 'Iger' 2249 | --------------------------------------------------------------------------------