pax_global_header00006660000000000000000000000064144603367260014525gustar00rootroot0000000000000052 comment=80041f485d19b1385c137d31effbff3aa073ad09 injector-0.21.0/000077500000000000000000000000001446033672600134225ustar00rootroot00000000000000injector-0.21.0/.github/000077500000000000000000000000001446033672600147625ustar00rootroot00000000000000injector-0.21.0/.github/workflows/000077500000000000000000000000001446033672600170175ustar00rootroot00000000000000injector-0.21.0/.github/workflows/ci.yml000066400000000000000000000015531446033672600201410ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "pypy3.7", "pypy3.8", "pypy3.9", "pypy3.10"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | pip install --upgrade -r requirements-dev.txt pip install . - name: Run tests run: | py.test -vv --cov=injector --cov-branch --cov-report html --cov-report term if which mypy; then mypy injector ; fi if which black; then black --check . ; fi check-manifest - name: Report coverage to Codecov uses: codecov/codecov-action@v1 injector-0.21.0/.gitignore000066400000000000000000000002331446033672600154100ustar00rootroot00000000000000/.* !/.gitignore !/.github .cache/ __pycache__/ docs/_build/ build/ htmlcov/ *,cover .mypy_cache/ .pytest_cache/ coverage.xml /dist/ /injector.egg-info/ injector-0.21.0/CHANGES000066400000000000000000000301441446033672600144170ustar00rootroot00000000000000Injector Change Log =================== 0.21.0 ------ - Improved the documentation, thanks to jonathanmach and Jakub Wilk - Fixed a thread-safety regression - Improved the type annotations, thanks to David Pärsson - Fixed singleton scope behavior with parent/child injectors, thanks to David Pärsson - Stopped using a deprecated test function, thanks to ljnsn 0.20.1 ------ - Added support for PEP 604 union types (Python 3.10+), thanks to David Pärsson - Fixed building with pypandoc 1.8+, thanks to Søren Fuglede Jørgensen 0.20.0 ------ - Fixed handling of Union combined with Annotated, thanks to Tobias Nilsson - Fixed AssitedBuilder/child Injector interaction, thanks to Erik Cederberg - Made get_bindings() and injections work even if a injectee's return type annotation is a forward reference that can't be resolved Backwards incompatible: - Dropped Python 3.6 support 0.19.0 ------ - Added the license to the source distribution, thanks to Joshua Adelman - Added Python 3.9 and 3.10 support, this includes fixing Python 3.10 compatibility, thanks to Torge Matthies - Improved the documentation, thanks to Takahiro Kojima - Improved the source distribution so that it can be used to build and install wheels, thanks to Janusz Skonieczny - Added requirements files for easier development, thanks to Greg Eremeev Backwards incompatible: - Removed Python 3.5 support 0.18.4 ------ - Fixed a bug where only one of multiple NoInject annotations was interpreted 0.18.3 ------ - Fixed Python 3.5.3 compatibility 0.18.2 ------ - Added remaining type hints to the codebase so that the client code can have better static typing safety - Fixed UnsatisfiedRequirement string representation (this time for real) - Added forward return type reference support to provider methods 0.18.1 ------ - Fixed UnsatisfiedRequirement instantiation (trying to get its string representation would fail) - Fixed injecting a subclass of a generic type on Python versions older than 3.7.0 - Fixed regression that caused BoundKey injection failure 0.18.0 ------ - Added new public :func:`get_bindings ` function to see what parameters will be injected into a function - Added new generic types using a draft implementation of `PEP 593 `_: :data:`Inject ` and :data:`NoInject `. Those serve as additional ways to declare (non)injectable parameters while :func:`inject ` won't go away any time soon :func:`noninjectable ` may be removed once `NoInject` is cofirmed to work. Backwards incompatible: - Removed previously deprecated `Key`, `BindingKey`, `SequenceKey` and `MappingKey` pseudo-types 0.17.0 ------ - Added support for using `typing.Dict` and `typing.List` in multibindings. See :meth:`multibind `. - Added multibinding-specific :func:`provider ` variant: :func:`multiprovider ` - Deprecated using :func:`provider ` for multibindings - Fixed failure to provide a default value to a `NewType`-aliased type with auto_bind enabled - Deprecated :func:`Key `, :func:`SequenceKey ` and :func:`MappingKey ` – use real types or type aliases instead - Deprecated using single-item lists and dictionaries for multibindings - use real types or type aliases instead Technically backwards incompatible: - typing.List and typing.Dict specializations are now explicitly disallowed as :meth:`bind ` interfaces and types returned by :func:`provider `-decorated methods 0.16.2 ------ - (Re)added support for decorating classes themselves with :func:`@inject `. This is the same as decorating their constructors. Among other things this gives us `dataclasses `_ integration. 0.16.1 ------ - Reuploaded to fix incorrectly formatted project description 0.16.0 ------ - Added support for overriding injectable parameters with positional arguments (previously only possible with keyword arguments) - Fixed crashes caused by typed self in method signatures - Improved typing coverage Backwards incompatible: - Dropped Python 3.4 support - Removed previously deprecated constructs: with_injector, Injector.install_into, Binder.bind_scope - Dependencies are no longer injected into Module.configure and raw module functions (previously deprecated) - Removed unofficial support for injecting into parent class constructors 0.15.0 ------ - Added type information for Injector.create_object() (patch #101 thanks to David Pärsson) - Made the code easier to understand (patch #105 thanks to Christian Clauss) - Opted the package into distributing type information and checking it (PEP 561) 0.14.1 ------ - Fixed regression that required all noninjectable parameters to be typed 0.14.0 ------ - Added NewType support - Added type hints Backwards incompatible: - Passing invalid parameter names to @noninjectable() will now result in an error - Dropped Python 3.3 support 0.13.4 ------ - Deprecated with_injector. There's no one migration path recommended, it depends on a particular case. - Deprecated install_into. 0.13.3 ------ - Fixed a bug with classes deriving from PyQt classes not being able to be instantiated manually (bug #75, patch #76 thanks to David Pärsson) 0.13.2 ------ - Fixed a bug with values shared between Injectors in a hierarchy (bugs #52 and #72) - Binding scopes explicitly (``Binder.bind_scope``) is no longer necessary and ``bind_scope`` is a no-op now. 0.13.1 ------ - Improved some error messages 0.13.0 ------ Backwards incompatible: - Dropped Python 3.2 support - Dropped Injector use_annotations constructor parameter. Whenever @inject is used parameter annotations will be used automatically. - Dropped Python 2 support (this includes PyPy) - Removed @provides decorator, use @provider instead - Removed support for passing keyword arguments to @inject 0.12.0 ------ - Fixed binding inference in presence of * and ** arguments (previously Injector would generate extra arguments, now it just ignores them) - Improved error reporting - Fixed compatibility with newer typing versions (that includes the one bundled with Python 3.6) Technically backwards incompatible: - Forward references as PEP 484 understands them are being resolved now when Python 3-style annotations are used. See https://www.python.org/dev/peps/pep-0484/#forward-references for details. Optional parameters are treated as compulsory for the purpose of injection. 0.11.1 ------ - 0.11.0 packages uploaded to PyPI are broken (can't be installed), this is a fix-only release. 0.11.0 ------ * The following way to declare dependencies is introduced and recommended now: .. code-block:: python class SomeClass: @inject def __init__(self, other: OtherClass): # ... The following ways are still supported but are deprecated and will be removed in the future: .. code-block:: python # Python 2-compatible style class SomeClass @inject(other=OtherClass) def __init__(self, other): # ... # Python 3 style without @inject-decoration but with use_annotations class SomeClass: def __init__(self, other: OtherClass): # ... injector = Injector(use_annotations=True) # ... * The following way to declare Module provider methods is introduced and recommended now: .. code-block:: python class MyModule(Module): @provider def provide_something(self, dependency: Dependency) -> Something: # ... @provider implies @inject. Previously it would look like this: .. code-block:: python class MyModule(Module): @provides(Something) @inject def provide_something(self, dependency: Dependency): # ... The :func:`~injector.provides` decorator will be removed in the future. * Added a :func:`~injector.noninjectable` decorator to mark parameters as not injectable (this serves as documentation and a way to avoid some runtime errors) Backwards incompatible: * Removed support for decorating classes with :func:`@inject `. Previously: .. code-block:: python @inject(something=Something) class Class: pass Now: .. code-block:: python class Class: @inject def __init__(self, something: Something): self.something = something * Removed support for injecting partially applied functions, previously: .. code-block:: python @inject(something=Something) def some_function(something): pass class Class: @inject(function=some_function) def __init__(self, function): # ... Now you need to move the function with injectable dependencies to a class. * Removed support for getting :class:`AssistedBuilder(callable=...) ` * Dropped Python 2.6 support * Changed the way :class:`~injector.AssistedBuilder` and :class:`~injector.ProviderOf` are used. Previously: .. code-block:: python builder1 = injector.get(AssistedBuilder(Something)) # or: builder1 = injector.get(AssistedBuilder(interface=Something)) builder2 = injector.get(AssistedBuilder(cls=SomethingElse)) provider = injector.get(ProviderOf(SomeOtherThing)) Now: .. code-block:: python builder1 = injector.get(AssistedBuilder[Something]) builder2 = injector.get(ClassAssistedBuilder[cls=SomethingElse]) provider = injector.get(ProviderOf[SomeOtherThing]) * Removed support for injecting into non-constructor methods 0.10.1 ------ - Fixed a false positive bug in dependency cycle detection (AssistedBuilder can be used to break dependency cycles now) 0.10.0 ------ - :meth:`injector.Provider.get()` now requires an :class:`injector.Injector` instance as its parameter - deprecated injecting arguments into modules (be it functions/callables, :class:`~injector.Module` constructors or :meth:`injector.Module.configure` methods) - removed `extends` decorator - few classes got useful __repr__ implementations - fixed injecting ProviderOf and AssistedBuilders when :class:`injector.Injector` auto_bind is set to False (previously would result in `UnsatisfiedRequirement` error) - fixed crash occurring when Python 3-function annotation use is enabled and __init__ method has a return value annotation ("injector.UnknownProvider: couldn't determine provider for None to None"), should also apply to free functions as well 0.9.1 ----- - Bug fix release. 0.9.0 ----- - Child :class:`~injector.Injector` can rebind dependancies bound in parent Injector (that changes :class:`~injector.Provider` semantics), thanks to Ilya Orlov - :class:`~injector.CallableProvider` callables can be injected into, thanks to Ilya Strukov - 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 0.8.0 ----- - Binding annotations are removed. Use :func:`~injector.Key` to create unique types instead. 0.7.9 ----- - Fixed regression with injecting unbound key resulting in None instead of raising an exception 0.7.8 ----- - Exception is raised when :class:`~injector.Injector` can't install itself into a class instance due to __slots__ presence - Some of exception messages are now more detailed to make debugging easier when injection fails - You can inject functions now - :class:`~injector.Injector` provides a wrapper that takes care of injecting dependencies into the original function 0.7.7 ----- - 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)`` 0.7.6 ----- - Auto-convert README.md to RST for PyPi. 0.7.5 ----- - Added a ChangeLog! - Added support for using Python3 annotations as binding types. injector-0.21.0/COPYING000066400000000000000000000027331446033672600144620ustar00rootroot00000000000000Copyright (c) 2010, Alec Thomas, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of SwapOff.org nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. injector-0.21.0/MANIFEST.in000066400000000000000000000004231446033672600151570ustar00rootroot00000000000000include *.py include *.toml include requirements-dev.in include *.txt include CHANGES include COPYING include README.md include mypy.ini include pytest.ini recursive-include docs *.html recursive-include docs *.py recursive-include docs *.rst recursive-include docs Makefile injector-0.21.0/README.md000066400000000000000000000202071446033672600147020ustar00rootroot00000000000000Injector - Python dependency injection framework, inspired by Guice =================================================================== [![image](https://github.com/alecthomas/injector/workflows/CI/badge.svg)](https://github.com/alecthomas/injector/actions?query=workflow%3ACI+branch%3Amaster) [![Coverage Status](https://codecov.io/gh/alecthomas/injector/branch/master/graph/badge.svg)](https://codecov.io/gh/alecthomas/injector) Introduction ------------ 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. If you're not sure what dependency injection is or you'd like to learn more about it see: * [The Clean Code Talks - Don't Look For Things! (a talk by Miško Hevery)]( https://www.youtube.com/watch?v=RlfLCWKxHJ0) * [Inversion of Control Containers and the Dependency Injection pattern (an article by Martin Fowler)]( https://martinfowler.com/articles/injection.html) The core values of Injector are: * Simplicity - while being inspired by Guice, Injector does not slavishly replicate its API. Providing a Pythonic API trumps faithfulness. Additionally some features are omitted because supporting them would be cumbersome and introduce a little bit too much "magic" (member injection, method injection). Connected to this, Injector tries to be as nonintrusive as possible. For example while you may declare a class' constructor to expect some injectable parameters, the class' constructor remains a standard constructor – you may instantiate the class just the same manually, if you want. * No global state – you can have as many [Injector](https://injector.readthedocs.io/en/latest/api.html#injector.Injector) instances as you like, each with a different configuration and each with different objects in different scopes. Code like this won't work for this very reason: ```python class MyClass: @inject def __init__(t: SomeType): # ... MyClass() ``` This is simply because there's no global `Injector` to use. You need to be explicit and use [Injector.get](https://injector.readthedocs.io/en/latest/api.html#injector.Injector.get), [Injector.create_object](https://injector.readthedocs.io/en/latest/api.html#injector.Injector.create_object) or inject `MyClass` into the place that needs it. * Cooperation with static type checking infrastructure – the API provides as much static type safety as possible and only breaks it where there's no other option. For example the [Injector.get](https://injector.readthedocs.io/en/latest/api.html#injector.Injector.get) method is typed such that `injector.get(SomeType)` is statically declared to return an instance of `SomeType`, therefore making it possible for tools such as [mypy](https://github.com/python/mypy) to type-check correctly the code using it. * The client code only knows about dependency injection to the extent it needs –  [`inject`](https://injector.readthedocs.io/en/latest/api.html#injector.inject), [`Inject`](https://injector.readthedocs.io/en/latest/api.html#injector.Inject) and [`NoInject`](https://injector.readthedocs.io/en/latest/api.html#injector.NoInject) are simple markers that don't really do anything on their own and your code can run just fine without Injector orchestrating things. ### How to get Injector? * GitHub (code repository, issues): https://github.com/alecthomas/injector * PyPI (installable, stable distributions): https://pypi.org/project/injector/. You can install it using pip: ```bash pip install injector ``` * Documentation: https://injector.readthedocs.org * Change log: https://injector.readthedocs.io/en/latest/changelog.html Injector works with CPython 3.7+ and PyPy 3 implementing Python 3.7+. A Quick Example --------------- ```python >>> from injector import Injector, inject >>> class Inner: ... def __init__(self): ... self.forty_two = 42 ... >>> class Outer: ... @inject ... def __init__(self, inner: Inner): ... self.inner = inner ... >>> injector = Injector() >>> outer = injector.get(Outer) >>> outer.inner.forty_two 42 ``` Or with `dataclasses` if you like: ```python from dataclasses import dataclass from injector import Injector, inject class Inner: def __init__(self): self.forty_two = 42 @inject @dataclass class Outer: inner: Inner injector = Injector() outer = injector.get(Outer) print(outer.inner.forty_two) # Prints 42 ``` A Full Example -------------- Here's a full example to give you a taste of how Injector works: ```python >>> from injector import Module, provider, Injector, inject, singleton ``` We'll use an in-memory SQLite database for our example: ```python >>> import sqlite3 ``` And make up an imaginary `RequestHandler` class that uses the SQLite connection: ```python >>> class RequestHandler: ... @inject ... def __init__(self, db: sqlite3.Connection): ... self._db = db ... ... def get(self): ... cursor = self._db.cursor() ... cursor.execute('SELECT key, value FROM data ORDER by key') ... return cursor.fetchall() ``` Next, for the sake of the example, we'll create a configuration type: ```python >>> class Configuration: ... def __init__(self, connection_string): ... self.connection_string = connection_string ``` Next, we bind the configuration to the injector, using a module: ```python >>> def configure_for_testing(binder): ... configuration = Configuration(':memory:') ... binder.bind(Configuration, to=configuration, scope=singleton) ``` 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: ```python >>> class DatabaseModule(Module): ... @singleton ... @provider ... def provide_sqlite_connection(self, configuration: Configuration) -> sqlite3.Connection: ... conn = sqlite3.connect(configuration.connection_string) ... cursor = conn.cursor() ... cursor.execute('CREATE TABLE IF NOT EXISTS data (key PRIMARY KEY, value)') ... cursor.execute('INSERT OR REPLACE INTO data VALUES ("hello", "world")') ... return conn ``` (Note how we have decoupled configuration from our database initialisation code.) 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`: ```python >>> injector = Injector([configure_for_testing, DatabaseModule()]) >>> handler = injector.get(RequestHandler) >>> tuple(map(str, handler.get()[0])) # py3/py2 compatibility hack ('hello', 'world') ``` We can also verify that our `Configuration` and `SQLite` connections are indeed singletons within the Injector: ```python >>> injector.get(Configuration) is injector.get(Configuration) True >>> injector.get(sqlite3.Connection) is injector.get(sqlite3.Connection) True ``` 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: 1. Forces decoupling. In our example, this is illustrated by decoupling our configuration and database configuration. 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. Footnote -------- This framework is similar to snake-guice, but aims for simplification. © Copyright 2010-2013 to Alec Thomas, under the BSD license injector-0.21.0/docs/000077500000000000000000000000001446033672600143525ustar00rootroot00000000000000injector-0.21.0/docs/Makefile000066400000000000000000000151621446033672600160170ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(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/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Injector.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Injector.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Injector" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Injector" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." injector-0.21.0/docs/_templates/000077500000000000000000000000001446033672600165075ustar00rootroot00000000000000injector-0.21.0/docs/_templates/sidebar.html000066400000000000000000000004741446033672600210130ustar00rootroot00000000000000

Injector

Dependency Injection Framework

injector-0.21.0/docs/api.rst000066400000000000000000000006631446033672600156620ustar00rootroot00000000000000Injector API reference ====================== .. note:: Unless specified otherwise, instance methods are **not** thread safe. The following functions are thread safe: * :meth:`Injector.get` * injection provided by :func:`inject` decorator (please note, however, that it doesn't say anything about decorated function thread safety) .. automodule:: injector :members: :undoc-members: :show-inheritance: injector-0.21.0/docs/changelog.rst000066400000000000000000000000301446033672600170240ustar00rootroot00000000000000.. include:: ../CHANGES injector-0.21.0/docs/conf.py000066400000000000000000000210271446033672600156530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Injector documentation build configuration file, created by # sphinx-quickstart on Mon Sep 16 02:58:17 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import injector # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Injector' copyright = u'2013, Alec Thomas' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = injector.__version__ # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { 'index': ('sidebar.html', 'sourcelink.html', 'searchbox.html'), '**': ('localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'), } # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Injectordoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [('index', 'Injector.tex', u'Injector Documentation', u'Alec Thomas', 'manual')] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [('index', 'injector', u'Injector Documentation', [u'Alec Thomas'], 1)] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( 'index', 'Injector', u'Injector Documentation', u'Alec Thomas', 'Injector', 'One line description of project.', 'Miscellaneous', ) ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} def setup(app): app.connect('autodoc-skip-member', skip_member) def skip_member(app, what, name, obj, skip, options): return ( skip or getattr(obj, '__doc__', None) is None or getattr(obj, '__private__', False) is True or getattr(getattr(obj, '__func__', None), '__private__', False) is True ) injector-0.21.0/docs/faq.rst000066400000000000000000000033561446033672600156620ustar00rootroot00000000000000.. _faq: Frequently Asked Questions ========================== If I use :func:`~injector.inject` or scope decorators on my classess will I be able to create instances of them without using Injector? --------------------------------------------------------------------------------------------------------------------------------------- Yes. Scope decorators don't change the way you can construct your class instances without Injector interaction. I'm calling this method (/function/class) but I'm getting "TypeError: XXX() takes exactly X arguments (Y given)" ---------------------------------------------------------------------------------------------------------------- Example code: .. code-block:: python class X: @inject def __init__(self, s: str): self.s = s def configure(binder): binder.bind(s, to='some string') injector = Injector(configure) x = X() Result? :: TypeError: __init__() takes exactly 2 arguments (1 given) Reason? There's *no* global state that :class:`Injector` modifies when it's instantiated and configured. Its whole knowledge about bindings etc. is stored in itself. Moreover :func:`inject` will *not* make dependencies appear out of thin air when you for example attempt to create an instance of a class manually (without ``Injector``'s help) - there's no global state ``@inject`` decorated methods can access. In order for ``X`` to be able to use bindings defined in ``@inject`` decoration :class:`Injector` needs to be used (directly or indirectly) to create an instance of ``X``. This means most of the time you want to just inject ``X`` where you need it, you can also use :meth:`Injector.get` to obtain an instance of the class (see its documentation for usage notes). injector-0.21.0/docs/index.rst000066400000000000000000000074111446033672600162160ustar00rootroot00000000000000.. Injector documentation master file, created by sphinx-quickstart on Mon Sep 16 02:58:17 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to Injector's documentation! ==================================== .. image:: https://github.com/alecthomas/injector/workflows/CI/badge.svg :alt: Build status :target: https://github.com/alecthomas/injector/actions?query=workflow%3ACI+branch%3Amaster .. image:: https://codecov.io/gh/alecthomas/injector/branch/master/graph/badge.svg :alt: Covergage status :target: https://codecov.io/gh/alecthomas/injector GitHub (code repository, issues): https://github.com/alecthomas/injector PyPI (installable, stable distributions): https://pypi.org/project/injector. You can install Injector using pip:: pip install injector Injector works with CPython 3.6+ and PyPy 3 implementing Python 3.6+. Introduction ------------ 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 `. If you're not sure what dependency injection is or you'd like to learn more about it see: * `The Clean Code Talks - Don't Look For Things! (a talk by Miško Hevery) `_ * `Inversion of Control Containers and the Dependency Injection pattern (an article by Martin Fowler) `_ The core values of Injector are: * Simplicity - while being inspired by Guice, Injector does not slavishly replicate its API. Providing a Pythonic API trumps faithfulness. Additionally some features are ommitted because supporting them would be cumbersome and introduce a little bit too much "magic" (member injection, method injection). Connected to this, Injector tries to be as nonintrusive as possible. For example while you may declare a class' constructor to expect some injectable parameters, the class' constructor remains a standard constructor – you may instaniate the class just the same manually, if you want. * No global state – you can have as many :class:`Injector` instances as you like, each with a different configuration and each with different objects in different scopes. Code like this won't work for this very reason:: # This will NOT work: class MyClass: @inject def __init__(self, t: SomeType): # ... MyClass() This is simply because there's no global :class:`Injector` to use. You need to be explicit and use :meth:`Injector.get `, :meth:`Injector.create_object ` or inject `MyClass` into the place that needs it. * Cooperation with static type checking infrastructure – the API provides as much static type safety as possible and only breaks it where there's no other option. For example the :meth:`Injector.get ` method is typed such that `injector.get(SomeType)` is statically declared to return an instance of `SomeType`, therefore making it possible for tools such as `mypy `_ to type-check correctly the code using it. Quick start ----------- See `the project's README `_ for an example of Injector use. Contents -------- .. toctree:: :maxdepth: 1 changelog terminology testing scopes logging api faq practices injector-0.21.0/docs/logging.rst000066400000000000000000000007141446033672600165340ustar00rootroot00000000000000Logging ======= Injector uses standard :mod:`logging` module, the logger name is ``injector``. By default ``injector`` logger is not configured to print logs anywhere. To enable ``get()`` tracing (and some other useful information) you need to set ``injector`` logger level to ``DEBUG``. You can do that by executing:: import logging logging.getLogger('injector').setLevel(logging.DEBUG) or by configuring :mod:`logging` module in any other way. injector-0.21.0/docs/practices.rst000066400000000000000000000145041446033672600170650ustar00rootroot00000000000000.. _practices: Good and bad practices ====================== Side effects ```````````` You should avoid creating side effects in your modules for two reasons: * Side effects will make it more difficult to test a module if you want to do it * Modules expose a way to acquire some resource but they don't provide any way to release it. If, for example, your module connects to a remote server while creating a service you have no way of closing that connection unless the service exposes it. Injecting into constructors vs injecting into other methods ``````````````````````````````````````````````````````````` .. note:: Injector 0.11+ doesn't support injecting into non-constructor methods, this section is kept for historical reasons. .. note:: Injector 0.11 deprecates using @inject with keyword arguments to declare bindings, this section remains unchanged for historical reasons. In general you should prefer injecting into constructors to injecting into other methods because: * it can expose potential issues earlier (at object construction time rather than at the method call) * it exposes class' dependencies more openly. Constructor injection: .. code-block:: python class Service1(object): @inject(http_client=HTTP) def __init__(self, http_client): self.http_client = http_client # some other code # tens or hundreds lines of code def method(self): # do something pass Regular method injection: .. code-block:: python class Service2(object): def __init__(self): # some other code # tens or hundreds lines of code @inject(http_client=HTTP) def method(self, http_client): # do something pass In first case you know all the dependencies by looking at the class' constructor, in the second you don't know about ``HTTP`` dependency until you see the method definition. Slightly different approach is suggested when it comes to Injector modules - in this case injecting into their constructors (or ``configure`` methods) would make the injection process dependent on the order of passing modules to Injector and therefore quite fragile. See this code sample: .. code-block:: python A = Key('A') B = Key('B') class ModuleA(Module): @inject(a=A) def configure(self, binder, a): pass class ModuleB(Module): @inject(b=B) def __init__(self, b): pass class ModuleC(Module): def configure(self, binder): binder.bind(A, to='a') binder.bind(B, to='b') # error, at the time of ModuleA processing A is unbound Injector([ModuleA, ModuleC]) # error, at the time of ModuleB processing B is unbound Injector([ModuleB, ModuleC]) # no error this time Injector([ModuleC, ModuleA, ModuleB]) Doing too much in modules and/or providers `````````````````````````````````````````` An implementation detail of Injector: Injector and accompanying classes are protected by a lock to make them thread safe. This has a downside though: in general only one thread can use dependency injection at any given moment. In best case scenario you "only" slow other threads' dependency injection down. In worst case scenario (performing blocking calls without timeouts) you can **deadlock** whole application. **It is advised to avoid performing any IO, particularly without a timeout set, inside modules code.** As an illustration: .. code-block:: python from threading import Thread from time import sleep from injector import inject, Injector, Module, provider class A: pass class SubA(A): pass class B: pass class BadModule(Module): @provider def provide_a(self, suba: SubA) -> A: return suba @provider def provide_suba(self) -> SubA: print('Providing SubA...') while True: print('Sleeping...') sleep(1) # This never executes return SubA() @provider def provide_b(self) -> B: return B() injector = Injector([BadModule]) thread = Thread(target=lambda: injector.get(A)) # to make sure the thread doesn't keep the application alive thread.daemon = True thread.start() # This will never finish injector.get(B) print('Got B') Here's the output of the application:: Providing SubA... Sleeping... Sleeping... Sleeping... (...) Injecting Injector and abusing Injector.get ``````````````````````````````````````````` Sometimes code like this is written: .. code-block:: python class A: pass class B: pass class C: @inject def __init__(self, injector: Injector): self.a = injector.get(A) self.b = injector.get(B) It is advised to use the following pattern instead: .. code-block:: python class A: pass class B: pass class C: @inject def __init__(self, a: A, b: B): self.a = a self.b = b The second form has the benefits of: * expressing clearly what the dependencies of ``C`` are * making testing of the ``C`` class easier - you can provide the dependencies (whether they are mocks or not) directly, instead of having to mock :class:`Injector` and make the mock handle :meth:`Injector.get` calls * following the common practice and being easier to understand Injecting dependencies only to pass them somewhere else ``````````````````````````````````````````````````````` A pattern similar to the one below can emerge: .. code-block:: python class A: pass class B: def __init__(self, a): self.a = a class C: @inject def __init__(self, a: A): self.b = B(a) Class ``C`` in this example has the responsibility of gathering dependencies of class ``B`` and constructing an object of type ``B``, there may be a valid reason for it but in general it defeats the purpose of using ``Injector`` and should be avoided. The appropriate pattern is: .. code-block:: python class A: pass class B: @inject def __init__(self, a: A): self.a = a class C: @inject def __init__(self, b: B): self.b = b injector-0.21.0/docs/scopes.rst000066400000000000000000000043441446033672600164050ustar00rootroot00000000000000.. _scopes: Scopes ====== Singletons `````````` Singletons are declared by binding them in the SingletonScope. This can be done in three ways: 1. Decorating the class with `@singleton`. 2. Decorating a `@provider` decorated Module method with `@singleton`. 3. Explicitly calling `binder.bind(X, scope=singleton)`. A (redundant) example showing all three methods:: @singleton class Thing: pass class ThingModule(Module): def configure(self, binder): binder.bind(Thing, scope=singleton) @singleton @provider def provide_thing(self) -> Thing: return Thing() 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. Implementing new Scopes ``````````````````````` In the above description of scopes, we glossed over a lot of detail. In particular, how one would go about implementing our own scopes. Basically, there are two steps. First, subclass `Scope` and implement `Scope.get`:: from injector import Scope class CustomScope(Scope): def get(self, key, provider): return provider Then create a global instance of :class:`ScopeDecorator` to allow classes to be easily annotated with your scope:: from injector import ScopeDecorator customscope = ScopeDecorator(CustomScope) This can be used like so:: @customscope class MyClass: pass Scopes are bound in modules with the :meth:`Binder.bind_scope` method:: class MyModule(Module): def configure(self, binder): binder.bind_scope(CustomScope) Scopes can be retrieved from the injector, as with any other instance. They are singletons across the life of the injector:: >>> injector = Injector([MyModule()]) >>> injector.get(CustomScope) is injector.get(CustomScope) True 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. injector-0.21.0/docs/terminology.rst000066400000000000000000000203511446033672600174550ustar00rootroot00000000000000Terminology =========== 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:: {str: 'an instance of a string'} For those new to dependency-injection and/or Guice, though, some of the terminology used may not be obvious. Provider ```````` A means of providing an instance of a type. Built-in providers include: * :class:`~injector.ClassProvider` - creates a new instance from a class * :class:`~injector.InstanceProvider` - returns an existing instance directly * :class:`~injector.CallableProvider` - provides an instance by calling a function In order to create custom provider you need to subclass :class:`~injector.Provider` and override its :meth:`~injector.Provider.get` method. Scope ````` 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. 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. The default scope is :class:`NoScope`. .. seealso:: :ref:`scopes` Binding ``````` A binding is the mapping of a unique binding key to a corresponding provider. For example:: >>> from injector import InstanceProvider >>> bindings = { ... (Name, None): InstanceProvider('Sherlock'), ... (Description, None): InstanceProvider('A man of astounding insight'), ... } Binder `````` The `Binder` is simply a convenient wrapper around the dictionary that maps types to providers. It provides methods that make declaring bindings easier. .. _module: Module `````` 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:: >>> from injector import Module >>> class MyModule(Module): ... def configure(self, binder): ... binder.bind(Name, to='Sherlock') ... binder.bind(Description, to='A man of astounding insight') For more complex instance construction, methods decorated with `@provider` will be called to resolve binding keys:: >>> from injector import provider >>> class MyModule(Module): ... def configure(self, binder): ... binder.bind(Name, to='Sherlock') ... ... @provider ... def describe(self) -> Description: ... return 'A man of astounding insight (at %s)' % time.time() Injection ````````` 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. Here is an example of injection on a module provider method, and on the constructor of a normal class:: from injector import inject class User: @inject def __init__(self, name: Name, description: Description): self.name = name self.description = description class UserModule(Module): def configure(self, binder): binder.bind(User) class UserAttributeModule(Module): def configure(self, binder): binder.bind(Name, to='Sherlock') @provider def describe(self, name: Name) -> Description: return '%s is a man of astounding insight' % name Injector ```````` The `Injector` brings everything together. It takes a list of `Module` s, and configures them with a binder, effectively creating a dependency graph:: from injector import Injector injector = Injector([UserModule(), UserAttributeModule()]) You can also pass classes instead of instances to `Injector`, it will instantiate them for you:: injector = Injector([UserModule, UserAttributeModule]) The injector can then be used to acquire instances of a type, either directly:: >>> injector.get(Name) 'Sherlock' >>> injector.get(Description) 'Sherlock is a man of astounding insight' Or transitively:: >>> user = injector.get(User) >>> isinstance(user, User) True >>> user.name 'Sherlock' >>> user.description 'Sherlock is a man of astounding insight' Assisted injection `````````````````` Sometimes there are classes that have injectable and non-injectable parameters in their constructors. Let's have for example:: class Database: pass class User: def __init__(self, name): self.name = name class UserUpdater: def __init__(self, db: Database, user): pass 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`. In this situation there's technique called Assisted injection:: from injector import ClassAssistedBuilder injector = Injector() builder = injector.get(ClassAssistedBuilder[UserUpdater]) user = User('John') user_updater = builder.build(user=user) 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. `AssistedBuilder[T]` and `ClassAssistedBuilder[T]` are injectable just as anything else, if you need instance of it you just ask for it like that:: class NeedsUserUpdater: @inject def __init__(self, builder: ClassAssistedBuilder[UserUpdater]): self.updater_builder = builder def method(self): updater = self.updater_builder.build(user=None) `ClassAssistedBuilder` means it'll construct a concrete class and no bindings will be used. If you want to follow bindings and construct class pointed to by a key you use `AssistedBuilder` and can do it like this:: >>> DB = Key('DB') >>> class DBImplementation: ... def __init__(self, uri): ... pass ... >>> def configure(binder): ... binder.bind(DB, to=DBImplementation) ... >>> injector = Injector(configure) >>> builder = injector.get(AssistedBuilder[DB]) >>> isinstance(builder.build(uri='x'), DBImplementation) True More information on this topic: - `"How to use Google Guice to create objects that require parameters?" on Stack Overflow `_ - `Google Guice assisted injection `_ Child injectors ``````````````` 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:: >>> def configure_parent(binder): ... binder.bind(str, to='asd') ... binder.bind(int, to=42) ... >>> def configure_child(binder): ... binder.bind(str, to='qwe') ... >>> parent = Injector(configure_parent) >>> child = parent.create_child_injector(configure_child) >>> parent.get(str), parent.get(int) ('asd', 42) >>> child.get(str), child.get(int) ('qwe', 42) **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:: >>> def configure_parent(binder): ... binder.bind(str, to='asd', scope=singleton) ... >>> def configure_child(binder): ... binder.bind(str, to='qwe', scope=singleton) ... >>> parent = Injector(configure_parent) >>> child = parent.create_child_injector(configure_child) >>> child.get(str) # this behaves as expected 'qwe' >>> parent.get(str) # wat 'qwe' injector-0.21.0/docs/testing.rst000066400000000000000000000021301446033672600165550ustar00rootroot00000000000000Testing with Injector ===================== When you use unit test framework such as `unittest2` or `nose` you can also profit from `injector`. However, manually creating injectors and test classes can be quite annoying. There is, however, `with_injector` method decorator which has parameters just as `Injector` construtor and installes configured injector into class instance on the time of method call:: import unittest from injector import Module, with_injector, inject class UsernameModule(Module): def configure(self, binder): binder.bind(str, 'Maria') class TestSomethingClass(unittest.TestCase): @with_injector(UsernameModule()) def setUp(self): pass @inject def test_username(self, username: str): self.assertEqual(username, 'Maria') **Each** method call re-initializes :class:`~injector.Injector` - if you want to you can also put :func:`~injector.with_injector` decorator on class constructor. After such call all :func:`~injector.inject`-decorated methods will work just as you'd expect them to work. injector-0.21.0/injector/000077500000000000000000000000001446033672600152375ustar00rootroot00000000000000injector-0.21.0/injector/__init__.py000066400000000000000000001516521446033672600173620ustar00rootroot00000000000000# encoding: utf-8 # # Copyright (C) 2010 Alec Thomas # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. # # Author: Alec Thomas """Injector - Python dependency injection framework, inspired by Guice :copyright: (c) 2012 by Alec Thomas :license: BSD """ import functools import inspect import itertools import logging import sys import threading import types from abc import ABCMeta, abstractmethod from collections import namedtuple from typing import ( Any, Callable, cast, Dict, Generic, Iterable, List, Optional, overload, Set, Tuple, Type, TypeVar, TYPE_CHECKING, Union, ) try: from typing import NoReturn except ImportError: from typing_extensions import NoReturn # This is a messy, type-wise, because we not only have two potentially conflicting imports here # The easiest way to make mypy happy here is to tell it the versions from typing_extensions are # canonical. Since this typing_extensions import is only for mypy it'll work even without # typing_extensions actually installed so all's good. if TYPE_CHECKING: from typing_extensions import _AnnotatedAlias, Annotated, get_type_hints else: # Ignoring errors here as typing_extensions stub doesn't know about those things yet try: from typing import _AnnotatedAlias, Annotated, get_type_hints except ImportError: from typing_extensions import _AnnotatedAlias, Annotated, get_type_hints __author__ = 'Alec Thomas ' __version__ = '0.21.0' __version_tag__ = '' log = logging.getLogger('injector') log.addHandler(logging.NullHandler()) if log.level == logging.NOTSET: log.setLevel(logging.WARN) T = TypeVar('T') K = TypeVar('K') V = TypeVar('V') def private(something: T) -> T: something.__private__ = True # type: ignore return something CallableT = TypeVar('CallableT', bound=Callable) def synchronized(lock: threading.RLock) -> Callable[[CallableT], CallableT]: def outside_wrapper(function: CallableT) -> CallableT: @functools.wraps(function) def wrapper(*args: Any, **kwargs: Any) -> Any: with lock: return function(*args, **kwargs) return cast(CallableT, wrapper) return outside_wrapper lock = threading.RLock() _inject_marker = object() _noinject_marker = object() InjectT = TypeVar('InjectT') Inject = Annotated[InjectT, _inject_marker] """An experimental way to declare injectable dependencies utilizing a `PEP 593`_ implementation in Python 3.9 and backported to Python 3.7+ in `typing_extensions`. Those two declarations are equivalent:: @inject def fun(t: SomeType) -> None: pass def fun(t: Inject[SomeType]) -> None: pass The advantage over using :func:`inject` is that if you have some noninjectable parameters it may be easier to spot what are they. Those two are equivalent:: @inject @noninjectable('s') def fun(t: SomeType, s: SomeOtherType) -> None: pass def fun(t: Inject[SomeType], s: SomeOtherType) -> None: pass .. seealso:: Function :func:`get_bindings` A way to inspect how various injection declarations interact with each other. .. versionadded:: 0.18.0 .. note:: Requires Python 3.7+. .. note:: If you're using mypy you need the version 0.750 or newer to fully type-check code using this construct. .. _PEP 593: https://www.python.org/dev/peps/pep-0593/ .. _typing_extensions: https://pypi.org/project/typing-extensions/ """ NoInject = Annotated[InjectT, _noinject_marker] """An experimental way to declare noninjectable dependencies utilizing a `PEP 593`_ implementation in Python 3.9 and backported to Python 3.7+ in `typing_extensions`. Since :func:`inject` declares all function's parameters to be injectable there needs to be a way to opt out of it. This has been provided by :func:`noninjectable` but `noninjectable` suffers from two issues: * You need to repeat the parameter name * The declaration may be relatively distance in space from the actual parameter declaration, thus hindering readability `NoInject` solves both of those concerns, for example (those two declarations are equivalent):: @inject @noninjectable('b') def fun(a: TypeA, b: TypeB) -> None: pass @inject def fun(a: TypeA, b: NoInject[TypeB]) -> None: pass .. seealso:: Function :func:`get_bindings` A way to inspect how various injection declarations interact with each other. .. versionadded:: 0.18.0 .. note:: Requires Python 3.7+. .. note:: If you're using mypy you need the version 0.750 or newer to fully type-check code using this construct. .. _PEP 593: https://www.python.org/dev/peps/pep-0593/ .. _typing_extensions: https://pypi.org/project/typing-extensions/ """ def reraise(original: Exception, exception: Exception, maximum_frames: int = 1) -> NoReturn: prev_cls, prev, tb = sys.exc_info() frames = inspect.getinnerframes(cast(types.TracebackType, tb)) if len(frames) > maximum_frames: exception = original raise exception.with_traceback(tb) class Error(Exception): """Base exception.""" class UnsatisfiedRequirement(Error): """Requirement could not be satisfied.""" def __init__(self, owner: Optional[object], interface: type) -> None: super().__init__(owner, interface) self.owner = owner self.interface = interface def __str__(self) -> str: on = '%s has an ' % _describe(self.owner) if self.owner else '' return '%sunsatisfied requirement on %s' % (on, _describe(self.interface)) class CallError(Error): """Call to callable object fails.""" def __str__(self) -> str: if len(self.args) == 1: return self.args[0] instance, method, args, kwargs, original_error, stack = self.args cls = instance.__class__.__name__ if instance is not None else '' full_method = '.'.join((cls, method.__name__)).strip('.') parameters = ', '.join( itertools.chain( (repr(arg) for arg in args), ('%s=%r' % (key, value) for (key, value) in kwargs.items()) ) ) return 'Call to %s(%s) failed: %s (injection stack: %r)' % ( full_method, parameters, original_error, [level[0] for level in stack], ) class CircularDependency(Error): """Circular dependency detected.""" class UnknownProvider(Error): """Tried to bind to a type whose provider couldn't be determined.""" class UnknownArgument(Error): """Tried to mark an unknown argument as noninjectable.""" class Provider(Generic[T]): """Provides class instances.""" __metaclass__ = ABCMeta @abstractmethod def get(self, injector: 'Injector') -> T: raise NotImplementedError # pragma: no cover class ClassProvider(Provider, Generic[T]): """Provides instances from a given class, created using an Injector.""" def __init__(self, cls: Type[T]) -> None: self._cls = cls def get(self, injector: 'Injector') -> T: return injector.create_object(self._cls) class CallableProvider(Provider, Generic[T]): """Provides something using a callable. The callable is called every time new value is requested from the provider. There's no need to explicitly use :func:`inject` or :data:`Inject` with the callable as it's assumed that, if the callable has annotated parameters, they're meant to be provided automatically. It wouldn't make sense any other way, as there's no mechanism to provide parameters to the callable at a later time, so either they'll be injected or there'll be a `CallError`. :: >>> class MyClass: ... def __init__(self, value: int) -> None: ... self.value = value ... >>> def factory(): ... print('providing') ... return MyClass(42) ... >>> def configure(binder): ... binder.bind(MyClass, to=CallableProvider(factory)) ... >>> injector = Injector(configure) >>> injector.get(MyClass) is injector.get(MyClass) providing providing False """ def __init__(self, callable: Callable[..., T]): self._callable = callable def get(self, injector: 'Injector') -> T: return injector.call_with_injection(self._callable) def __repr__(self) -> str: return '%s(%r)' % (type(self).__name__, self._callable) class InstanceProvider(Provider, Generic[T]): """Provide a specific instance. :: >>> class MyType: ... def __init__(self): ... self.contents = [] >>> def configure(binder): ... binder.bind(MyType, to=InstanceProvider(MyType())) ... >>> injector = Injector(configure) >>> injector.get(MyType) is injector.get(MyType) True >>> injector.get(MyType).contents.append('x') >>> injector.get(MyType).contents ['x'] """ def __init__(self, instance: T) -> None: self._instance = instance def get(self, injector: 'Injector') -> T: return self._instance def __repr__(self) -> str: return '%s(%r)' % (type(self).__name__, self._instance) @private class ListOfProviders(Provider, Generic[T]): """Provide a list of instances via other Providers.""" _providers: List[Provider[T]] def __init__(self) -> None: self._providers = [] def append(self, provider: Provider[T]) -> None: self._providers.append(provider) def __repr__(self) -> str: return '%s(%r)' % (type(self).__name__, self._providers) class MultiBindProvider(ListOfProviders[List[T]]): """Used by :meth:`Binder.multibind` to flatten results of providers that return sequences.""" def get(self, injector: 'Injector') -> List[T]: return [i for provider in self._providers for i in provider.get(injector)] class MapBindProvider(ListOfProviders[Dict[str, T]]): """A provider for map bindings.""" def get(self, injector: 'Injector') -> Dict[str, T]: map: Dict[str, T] = {} for provider in self._providers: map.update(provider.get(injector)) return map _BindingBase = namedtuple('_BindingBase', 'interface provider scope') @private class Binding(_BindingBase): """A binding from an (interface,) to a provider in a scope.""" def is_multibinding(self) -> bool: return _get_origin(_punch_through_alias(self.interface)) in {dict, list} @private class ImplicitBinding(Binding): """A binding that was created implicitly by auto-binding.""" pass _InstallableModuleType = Union[Callable[['Binder'], None], 'Module', Type['Module']] class Binder: """Bind interfaces to implementations. .. note:: This class is instantiated internally for you and there's no need to instantiate it on your own. """ _bindings: Dict[type, Binding] @private def __init__( self, injector: 'Injector', auto_bind: bool = True, parent: Optional['Binder'] = None ) -> None: """Create a new Binder. :param injector: Injector we are binding for. :param auto_bind: Whether to automatically bind missing types. :param parent: Parent binder. """ self.injector = injector self._auto_bind = auto_bind self._bindings = {} self.parent = parent def bind( self, interface: Type[T], to: Union[None, T, Callable[..., T], Provider[T]] = None, scope: Union[None, Type['Scope'], 'ScopeDecorator'] = None, ) -> None: """Bind an interface to an implementation. Binding `T` to an instance of `T` like :: binder.bind(A, to=A('some', 'thing')) is, for convenience, a shortcut for :: binder.bind(A, to=InstanceProvider(A('some', 'thing'))). Likewise, binding to a callable like :: binder.bind(A, to=some_callable) is a shortcut for :: binder.bind(A, to=CallableProvider(some_callable)) and, as such, if `some_callable` there has any annotated parameters they'll be provided automatically without having to use :func:`inject` or :data:`Inject` with the callable. `typing.List` and `typing.Dict` instances are reserved for multibindings and trying to bind them here will result in an error (use :meth:`multibind` instead):: binder.bind(List[str], to=['hello', 'there']) # Error :param interface: Type to bind. :param to: Instance or class to bind to, or an instance of :class:`Provider` subclass. :param scope: Optional :class:`Scope` in which to bind. """ if _get_origin(_punch_through_alias(interface)) in {dict, list}: raise Error( 'Type %s is reserved for multibindings. Use multibind instead of bind.' % (interface,) ) self._bindings[interface] = self.create_binding(interface, to, scope) @overload def multibind( self, interface: Type[List[T]], to: Union[List[T], Callable[..., List[T]], Provider[List[T]]], scope: Union[Type['Scope'], 'ScopeDecorator', None] = None, ) -> None: # pragma: no cover pass @overload def multibind( self, interface: Type[Dict[K, V]], to: Union[Dict[K, V], Callable[..., Dict[K, V]], Provider[Dict[K, V]]], scope: Union[Type['Scope'], 'ScopeDecorator', None] = None, ) -> None: # pragma: no cover pass def multibind( self, interface: type, to: Any, scope: Union['ScopeDecorator', Type['Scope'], None] = None ) -> None: """Creates or extends a multi-binding. A multi-binding contributes values to a list or to a dictionary. For example:: binder.multibind(List[str], to=['some', 'strings']) binder.multibind(List[str], to=['other', 'strings']) injector.get(List[str]) # ['some', 'strings', 'other', 'strings'] binder.multibind(Dict[str, int], to={'key': 11}) binder.multibind(Dict[str, int], to={'other_key': 33}) injector.get(Dict[str, int]) # {'key': 11, 'other_key': 33} .. versionchanged:: 0.17.0 Added support for using `typing.Dict` and `typing.List` instances as interfaces. Deprecated support for `MappingKey`, `SequenceKey` and single-item lists and dictionaries as interfaces. :param interface: typing.Dict or typing.List instance to bind to. :param to: Instance, class to bind to, or an explicit :class:`Provider` subclass. Must provide a list or a dictionary, depending on the interface. :param scope: Optional Scope in which to bind. """ if interface not in self._bindings: provider: ListOfProviders if ( isinstance(interface, dict) or isinstance(interface, type) and issubclass(interface, dict) or _get_origin(_punch_through_alias(interface)) is dict ): provider = MapBindProvider() else: provider = MultiBindProvider() binding = self.create_binding(interface, provider, scope) self._bindings[interface] = binding else: binding = self._bindings[interface] provider = binding.provider assert isinstance(provider, ListOfProviders) provider.append(self.provider_for(interface, to)) def install(self, module: _InstallableModuleType) -> None: """Install a module into this binder. In this context the module is one of the following: * function taking the :class:`Binder` as it's only parameter :: def configure(binder): bind(str, to='s') binder.install(configure) * instance of :class:`Module` (instance of it's subclass counts) :: class MyModule(Module): def configure(self, binder): binder.bind(str, to='s') binder.install(MyModule()) * subclass of :class:`Module` - the subclass needs to be instantiable so if it expects any parameters they need to be injected :: binder.install(MyModule) """ if type(module) is type and issubclass(cast(type, module), Module): instance = cast(type, module)() else: instance = module instance(self) def create_binding( self, interface: type, to: Any = None, scope: Union['ScopeDecorator', Type['Scope'], None] = None ) -> Binding: provider = self.provider_for(interface, to) scope = scope or getattr(to or interface, '__scope__', NoScope) if isinstance(scope, ScopeDecorator): scope = scope.scope return Binding(interface, provider, scope) def provider_for(self, interface: Any, to: Any = None) -> Provider: base_type = _punch_through_alias(interface) origin = _get_origin(base_type) if interface is Any: raise TypeError('Injecting Any is not supported') elif _is_specialization(interface, ProviderOf): (target,) = interface.__args__ if to is not None: raise Exception('ProviderOf cannot be bound to anything') return InstanceProvider(ProviderOf(self.injector, target)) elif isinstance(to, Provider): return to elif isinstance( to, ( types.FunctionType, types.LambdaType, types.MethodType, types.BuiltinFunctionType, types.BuiltinMethodType, ), ): return CallableProvider(to) elif issubclass(type(to), type): return ClassProvider(cast(type, to)) elif isinstance(interface, BoundKey): def proxy(injector: Injector) -> Any: binder = injector.binder kwarg_providers = { name: binder.provider_for(None, provider) for (name, provider) in interface.kwargs.items() } kwargs = {name: provider.get(injector) for (name, provider) in kwarg_providers.items()} return interface.interface(**kwargs) return CallableProvider(inject(proxy)) elif _is_specialization(interface, AssistedBuilder): (target,) = interface.__args__ builder = interface(self.injector, target) return InstanceProvider(builder) elif ( origin is None and isinstance(base_type, (tuple, type)) and interface is not Any and isinstance(to, base_type) or origin in {dict, list} and isinstance(to, origin) ): return InstanceProvider(to) elif issubclass(type(base_type), type) or isinstance(base_type, (tuple, list)): if to is not None: return InstanceProvider(to) return ClassProvider(base_type) else: raise UnknownProvider('couldn\'t determine provider for %r to %r' % (interface, to)) def _get_binding(self, key: type, *, only_this_binder: bool = False) -> Tuple[Binding, 'Binder']: binding = self._bindings.get(key) if binding: return binding, self if self.parent and not only_this_binder: return self.parent._get_binding(key) raise KeyError def get_binding(self, interface: type) -> Tuple[Binding, 'Binder']: is_scope = isinstance(interface, type) and issubclass(interface, Scope) is_assisted_builder = _is_specialization(interface, AssistedBuilder) try: return self._get_binding(interface, only_this_binder=is_scope or is_assisted_builder) except (KeyError, UnsatisfiedRequirement): if is_scope: scope = interface self.bind(scope, to=scope(self.injector)) return self._get_binding(interface) # The special interface is added here so that requesting a special # interface with auto_bind disabled works if self._auto_bind or self._is_special_interface(interface): binding = ImplicitBinding(*self.create_binding(interface)) self._bindings[interface] = binding return binding, self raise UnsatisfiedRequirement(None, interface) def has_binding_for(self, interface: type) -> bool: return interface in self._bindings def has_explicit_binding_for(self, interface: type) -> bool: return self.has_binding_for(interface) and not isinstance(self._bindings[interface], ImplicitBinding) def _is_special_interface(self, interface: type) -> bool: # "Special" interfaces are ones that you cannot bind yourself but # you can request them (for example you cannot bind ProviderOf(SomeClass) # to anything but you can inject ProviderOf(SomeClass) just fine return any(_is_specialization(interface, cls) for cls in [AssistedBuilder, ProviderOf]) def _is_specialization(cls: type, generic_class: Any) -> bool: # Starting with typing 3.5.3/Python 3.6 it is no longer necessarily true that # issubclass(SomeGeneric[X], SomeGeneric) so we need some other way to # determine whether a particular object is a generic class with type parameters # provided. Fortunately there seems to be __origin__ attribute that's useful here. # We need to special-case Annotated as its __origin__ behaves differently than # other typing generic classes. See https://github.com/python/typing/pull/635 # for some details. if generic_class is Annotated and isinstance(cls, _AnnotatedAlias): return True if not hasattr(cls, '__origin__'): return False origin = cast(Any, cls).__origin__ if not inspect.isclass(generic_class): generic_class = type(generic_class) if not inspect.isclass(origin): origin = type(origin) # __origin__ is generic_class is a special case to handle Union as # Union cannot be used in issubclass() check (it raises an exception # by design). return origin is generic_class or issubclass(origin, generic_class) def _punch_through_alias(type_: Any) -> type: if ( sys.version_info < (3, 10) and getattr(type_, '__qualname__', '') == 'NewType..new_type' or sys.version_info >= (3, 10) and type(type_).__module__ == 'typing' and type(type_).__name__ == 'NewType' ): return type_.__supertype__ else: return type_ def _get_origin(type_: type) -> Optional[type]: origin = getattr(type_, '__origin__', None) # Older typing behaves differently there and stores Dict and List as origin, we need to be flexible. if origin is List: return list elif origin is Dict: return dict return origin class Scope: """A Scope looks up the Provider for a binding. By default (ie. :class:`NoScope` ) this simply returns the default :class:`Provider` . """ __metaclass__ = ABCMeta def __init__(self, injector: 'Injector') -> None: self.injector = injector self.configure() def configure(self) -> None: """Configure the scope.""" @abstractmethod def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]: """Get a :class:`Provider` for a key. :param key: The key to return a provider for. :param provider: The default Provider associated with the key. :returns: A Provider instance that can provide an instance of key. """ raise NotImplementedError # pragma: no cover class ScopeDecorator: def __init__(self, scope: Type[Scope]) -> None: self.scope = scope def __call__(self, cls: T) -> T: cast(Any, cls).__scope__ = self.scope binding = getattr(cls, '__binding__', None) if binding: new_binding = Binding(interface=binding.interface, provider=binding.provider, scope=self.scope) setattr(cls, '__binding__', new_binding) return cls def __repr__(self) -> str: return 'ScopeDecorator(%s)' % self.scope.__name__ class NoScope(Scope): """An unscoped provider.""" def get(self, unused_key: Type[T], provider: Provider[T]) -> Provider[T]: return provider noscope = ScopeDecorator(NoScope) class SingletonScope(Scope): """A :class:`Scope` that returns a per-Injector instance for a key. :data:`singleton` can be used as a convenience class decorator. >>> class A: pass >>> injector = Injector() >>> provider = ClassProvider(A) >>> singleton = SingletonScope(injector) >>> a = singleton.get(A, provider) >>> b = singleton.get(A, provider) >>> a is b True """ _context: Dict[type, Provider] def configure(self) -> None: self._context = {} @synchronized(lock) def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]: try: return self._context[key] except KeyError: instance = self._get_instance(key, provider, self.injector) provider = InstanceProvider(instance) self._context[key] = provider return provider def _get_instance(self, key: Type[T], provider: Provider[T], injector: 'Injector') -> T: if injector.parent and not injector.binder.has_explicit_binding_for(key): try: return self._get_instance_from_parent(key, provider, injector.parent) except (CallError, UnsatisfiedRequirement): pass return provider.get(injector) def _get_instance_from_parent(self, key: Type[T], provider: Provider[T], parent: 'Injector') -> T: singleton_scope_binding, _ = parent.binder.get_binding(type(self)) singleton_scope = singleton_scope_binding.provider.get(parent) provider = singleton_scope.get(key, provider) return provider.get(parent) singleton = ScopeDecorator(SingletonScope) class ThreadLocalScope(Scope): """A :class:`Scope` that returns a per-thread instance for a key.""" def configure(self) -> None: self._locals = threading.local() def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]: try: return getattr(self._locals, repr(key)) except AttributeError: provider = InstanceProvider(provider.get(self.injector)) setattr(self._locals, repr(key), provider) return provider threadlocal = ScopeDecorator(ThreadLocalScope) class Module: """Configures injector and providers.""" def __call__(self, binder: Binder) -> None: """Configure the binder.""" self.__injector__ = binder.injector for unused_name, function in inspect.getmembers(self, inspect.ismethod): binding = None if hasattr(function, '__binding__'): binding = function.__binding__ if binding.interface == '__deferred__': # We could not evaluate a forward reference at @provider-decoration time, we need to # try again now. try: annotations = get_type_hints(function) except NameError as e: raise NameError( 'Cannot avaluate forward reference annotation(s) in method %r belonging to %r: %s' % (function.__name__, type(self), e) ) from e return_type = annotations['return'] binding = cast(Any, function.__func__).__binding__ = Binding( interface=return_type, provider=binding.provider, scope=binding.scope ) bind_method = binder.multibind if binding.is_multibinding() else binder.bind bind_method( # type: ignore binding.interface, to=types.MethodType(binding.provider, self), scope=binding.scope ) self.configure(binder) def configure(self, binder: Binder) -> None: """Override to configure bindings.""" class Injector: """ :param modules: Optional - a configuration module or iterable of configuration modules. Each module will be installed in current :class:`Binder` using :meth:`Binder.install`. Consult :meth:`Binder.install` documentation for the details. :param auto_bind: Whether to automatically bind missing types. :param parent: Parent injector. .. versionadded:: 0.7.5 ``use_annotations`` parameter .. versionchanged:: 0.13.0 ``use_annotations`` parameter is removed """ _stack: Tuple[Tuple[object, Callable, Tuple[Tuple[str, type], ...]], ...] binder: Binder def __init__( self, modules: Union[_InstallableModuleType, Iterable[_InstallableModuleType], None] = None, auto_bind: bool = True, parent: Optional['Injector'] = None, ) -> None: # Stack of keys currently being injected. Used to detect circular # dependencies. self._stack = () self.parent = parent # Binder self.binder = Binder(self, auto_bind=auto_bind, parent=parent.binder if parent is not None else None) if not modules: modules = [] elif not hasattr(modules, '__iter__'): modules = [cast(_InstallableModuleType, modules)] # This line is needed to pelase mypy. We know we have Iteable of modules here. modules = cast(Iterable[_InstallableModuleType], modules) # Bind some useful types self.binder.bind(Injector, to=self) self.binder.bind(Binder, to=self.binder) # Initialise modules for module in modules: self.binder.install(module) @property def _log_prefix(self) -> str: return '>' * (len(self._stack) + 1) + ' ' @synchronized(lock) def get(self, interface: Type[T], scope: Union[ScopeDecorator, Type[Scope], None] = None) -> T: """Get an instance of the given interface. .. note:: Although this method is part of :class:`Injector`'s public interface it's meant to be used in limited set of circumstances. For example, to create some kind of root object (application object) of your application (note that only one `get` call is needed, inside the `Application` class and any of its dependencies :func:`inject` can and should be used): .. code-block:: python class Application: @inject def __init__(self, dep1: Dep1, dep2: Dep2): self.dep1 = dep1 self.dep2 = dep2 def run(self): self.dep1.something() injector = Injector(configuration) application = injector.get(Application) application.run() :param interface: Interface whose implementation we want. :param scope: Class of the Scope in which to resolve. :returns: An implementation of interface. """ binding, binder = self.binder.get_binding(interface) scope = scope or binding.scope if isinstance(scope, ScopeDecorator): scope = scope.scope # Fetch the corresponding Scope instance from the Binder. scope_binding, _ = binder.get_binding(scope) scope_instance = scope_binding.provider.get(self) log.debug( '%sInjector.get(%r, scope=%r) using %r', self._log_prefix, interface, scope, binding.provider ) provider_instance = scope_instance.get(interface, binding.provider) result = provider_instance.get(self) log.debug('%s -> %r', self._log_prefix, result) return result def create_child_injector(self, *args: Any, **kwargs: Any) -> 'Injector': kwargs['parent'] = self return Injector(*args, **kwargs) def create_object(self, cls: Type[T], additional_kwargs: Any = None) -> T: """Create a new instance, satisfying any dependencies on cls.""" additional_kwargs = additional_kwargs or {} log.debug('%sCreating %r object with %r', self._log_prefix, cls, additional_kwargs) try: instance = cls.__new__(cls) except TypeError as e: reraise( e, CallError(cls, getattr(cls.__new__, '__func__', cls.__new__), (), {}, e, self._stack), maximum_frames=2, ) init = cls.__init__ try: self.call_with_injection(init, self_=instance, kwargs=additional_kwargs) except TypeError as e: # Mypy says "Cannot access "__init__" directly" init_function = instance.__init__.__func__ # type: ignore reraise(e, CallError(instance, init_function, (), additional_kwargs, e, self._stack)) return instance def call_with_injection( self, callable: Callable[..., T], self_: Any = None, args: Any = (), kwargs: Any = {} ) -> T: """Call a callable and provide it's dependencies if needed. :param self_: Instance of a class callable belongs to if it's a method, None otherwise. :param args: Arguments to pass to callable. :param kwargs: Keyword arguments to pass to callable. :type callable: callable :type args: tuple of objects :type kwargs: dict of string -> object :return: Value returned by callable. """ bindings = get_bindings(callable) signature = inspect.signature(callable) full_args = args if self_ is not None: full_args = (self_,) + full_args bound_arguments = signature.bind_partial(*full_args) needed = dict( (k, v) for (k, v) in bindings.items() if k not in kwargs and k not in bound_arguments.arguments ) dependencies = self.args_to_inject( function=callable, bindings=needed, owner_key=self_.__class__ if self_ is not None else callable.__module__, ) dependencies.update(kwargs) try: return callable(*full_args, **dependencies) except TypeError as e: reraise(e, CallError(self_, callable, args, dependencies, e, self._stack)) # Needed because of a mypy-related issue (https://github.com/python/mypy/issues/8129). assert False, "unreachable" # pragma: no cover @private @synchronized(lock) def args_to_inject( self, function: Callable, bindings: Dict[str, type], owner_key: object ) -> Dict[str, Any]: """Inject arguments into a function. :param function: The function. :param bindings: Map of argument name to binding key to inject. :param owner_key: A key uniquely identifying the *scope* of this function. For a method this will be the owning class. :returns: Dictionary of resolved arguments. """ dependencies = {} key = (owner_key, function, tuple(sorted(bindings.items()))) def repr_key(k: Tuple[object, Callable, Tuple[Tuple[str, type], ...]]) -> str: owner_key, function, bindings = k return '%s.%s(injecting %s)' % (tuple(map(_describe, k[:2])) + (dict(k[2]),)) log.debug('%sProviding %r for %r', self._log_prefix, bindings, function) if key in self._stack: raise CircularDependency( 'circular dependency detected: %s -> %s' % (' -> '.join(map(repr_key, self._stack)), repr_key(key)) ) self._stack += (key,) try: for arg, interface in bindings.items(): try: instance: Any = self.get(interface) except UnsatisfiedRequirement as e: if not e.owner: e = UnsatisfiedRequirement(owner_key, e.interface) raise e dependencies[arg] = instance finally: self._stack = tuple(self._stack[:-1]) return dependencies def get_bindings(callable: Callable) -> Dict[str, type]: """Get bindings of injectable parameters from a callable. If the callable is not decorated with :func:`inject` and does not have any of its parameters declared as injectable using :data:`Inject` an empty dictionary will be returned. Otherwise the returned dictionary will contain a mapping between parameter names and their types with the exception of parameters excluded from dependency injection (either with :func:`noninjectable`, :data:`NoInject` or only explicit injection with :data:`Inject` being used). For example:: >>> def function1(a: int) -> None: ... pass ... >>> get_bindings(function1) {} >>> @inject ... def function2(a: int) -> None: ... pass ... >>> get_bindings(function2) {'a': } >>> @inject ... @noninjectable('b') ... def function3(a: int, b: str) -> None: ... pass ... >>> get_bindings(function3) {'a': } >>> import sys, pytest >>> if sys.version_info < (3, 7, 0): ... pytest.skip('Python 3.7.0 required for sufficient Annotated support') >>> # The simple case of no @inject but injection requested with Inject[...] >>> def function4(a: Inject[int], b: str) -> None: ... pass ... >>> get_bindings(function4) {'a': } >>> # Using @inject with Inject is redundant but it should not break anything >>> @inject ... def function5(a: Inject[int], b: str) -> None: ... pass ... >>> get_bindings(function5) {'a': , 'b': } >>> # We need to be able to exclude a parameter from injection with NoInject >>> @inject ... def function6(a: int, b: NoInject[str]) -> None: ... pass ... >>> get_bindings(function6) {'a': } >>> # The presence of NoInject should not trigger anything on its own >>> def function7(a: int, b: NoInject[str]) -> None: ... pass ... >>> get_bindings(function7) {} This function is used internally so by calling it you can learn what exactly Injector is going to try to provide to a callable. """ look_for_explicit_bindings = False if not hasattr(callable, '__bindings__'): type_hints = get_type_hints(callable, include_extras=True) has_injectable_parameters = any( _is_specialization(v, Annotated) and _inject_marker in v.__metadata__ for v in type_hints.values() ) if not has_injectable_parameters: return {} else: look_for_explicit_bindings = True if look_for_explicit_bindings or cast(Any, callable).__bindings__ == 'deferred': read_and_store_bindings( callable, _infer_injected_bindings(callable, only_explicit_bindings=look_for_explicit_bindings) ) noninjectables: Set[str] = getattr(callable, '__noninjectables__', set()) return {k: v for k, v in cast(Any, callable).__bindings__.items() if k not in noninjectables} class _BindingNotYetAvailable(Exception): pass # See a comment in _infer_injected_bindings() for why this is useful. class _NoReturnAnnotationProxy: def __init__(self, callable: Callable) -> None: self.callable = callable def __getattribute__(self, name: str) -> Any: # get_type_hints() uses quite complex logic to determine the namespaces using which # any forward references should be resolved. Instead of mirroring this logic here # let's just take the easy way out and forward all attribute access to the original # callable except for the annotations – we want to filter them. callable = object.__getattribute__(self, 'callable') if name == '__annotations__': annotations = callable.__annotations__ return {name: value for (name, value) in annotations.items() if name != 'return'} return getattr(callable, name) def _infer_injected_bindings(callable: Callable, only_explicit_bindings: bool) -> Dict[str, type]: def _is_new_union_type(instance: Any) -> bool: new_union_type = getattr(types, 'UnionType', None) return new_union_type is not None and isinstance(instance, new_union_type) spec = inspect.getfullargspec(callable) try: # Return types don't matter for the purpose of dependency injection so instead of # obtaining type hints of the callable directly let's wrap it in _NoReturnAnnotationProxy. # The proxy removes the return type annotation (if present) from the annotations so that # get_type_hints() works even if the return type is a forward reference that can't be # resolved. bindings = get_type_hints(cast(Callable, _NoReturnAnnotationProxy(callable)), include_extras=True) except NameError as e: raise _BindingNotYetAvailable(e) # We don't care about the return value annotation as it doesn't matter # injection-wise. bindings.pop('return', None) # If we're dealing with a bound method get_type_hints will still return `self` annotation even though # it's already provided and we're not really interested in its type. So – drop it. if isinstance(callable, types.MethodType): self_name = spec.args[0] bindings.pop(self_name, None) # variadic arguments aren't supported at the moment (this may change # in the future if someone has a good idea how to utilize them) if spec.varargs: bindings.pop(spec.varargs, None) if spec.varkw: bindings.pop(spec.varkw, None) for k, v in list(bindings.items()): if _is_specialization(v, Annotated): v, metadata = v.__origin__, v.__metadata__ bindings[k] = v else: metadata = tuple() if only_explicit_bindings and _inject_marker not in metadata or _noinject_marker in metadata: del bindings[k] elif _is_specialization(v, Union) or _is_new_union_type(v): # We don't treat Optional parameters in any special way at the moment. union_members = v.__args__ new_members = tuple(set(union_members) - {type(None)}) # mypy stared complaining about this line for some reason: # error: Variable "new_members" is not valid as a type new_union = Union[new_members] # type: ignore # mypy complains about this construct: # error: The type alias is invalid in runtime context # See: https://github.com/python/mypy/issues/5354 union_metadata = { metadata for member in new_members for metadata in getattr(member, '__metadata__', tuple()) if _is_specialization(member, Annotated) } if ( only_explicit_bindings and _inject_marker not in union_metadata or _noinject_marker in union_metadata ): del bindings[k] else: bindings[k] = new_union # type: ignore return bindings def provider(function: CallableT) -> CallableT: """Decorator for :class:`Module` methods, registering a provider of a type. >>> class MyModule(Module): ... @provider ... def provide_name(self) -> str: ... return 'Bob' @provider-decoration implies @inject so you can omit it and things will work just the same: >>> class MyModule2(Module): ... def configure(self, binder): ... binder.bind(int, to=654) ... ... @provider ... def provide_str(self, i: int) -> str: ... return str(i) ... >>> injector = Injector(MyModule2) >>> injector.get(str) '654' """ _mark_provider_function(function, allow_multi=False) return function def multiprovider(function: CallableT) -> CallableT: """Like :func:`provider`, but for multibindings. Example usage:: class MyModule(Module): @multiprovider def provide_strs(self) -> List[str]: return ['str1'] class OtherModule(Module): @multiprovider def provide_strs_also(self) -> List[str]: return ['str2'] Injector([MyModule, OtherModule]).get(List[str]) # ['str1', 'str2'] See also: :meth:`Binder.multibind`.""" _mark_provider_function(function, allow_multi=True) return function def _mark_provider_function(function: Callable, *, allow_multi: bool) -> None: scope_ = getattr(function, '__scope__', None) try: annotations = get_type_hints(function) except NameError: return_type = '__deferred__' else: return_type = annotations['return'] _validate_provider_return_type(function, cast(type, return_type), allow_multi) function.__binding__ = Binding(return_type, inject(function), scope_) # type: ignore def _validate_provider_return_type(function: Callable, return_type: type, allow_multi: bool) -> None: origin = _get_origin(_punch_through_alias(return_type)) if origin in {dict, list} and not allow_multi: raise Error( 'Function %s needs to be decorated with multiprovider instead of provider if it is to ' 'provide values to a multibinding of type %s' % (function.__name__, return_type) ) ConstructorOrClassT = TypeVar('ConstructorOrClassT', bound=Union[Callable, Type]) @overload def inject(constructor_or_class: CallableT) -> CallableT: # pragma: no cover pass @overload def inject(constructor_or_class: Type[T]) -> Type[T]: # pragma: no cover pass def inject(constructor_or_class: ConstructorOrClassT) -> ConstructorOrClassT: """Decorator declaring parameters to be injected. eg. >>> class A: ... @inject ... def __init__(self, number: int, name: str): ... print([number, name]) ... >>> def configure(binder): ... binder.bind(A) ... binder.bind(int, to=123) ... binder.bind(str, to='Bob') Use the Injector to get a new instance of A: >>> a = Injector(configure).get(A) [123, 'Bob'] As a convenience one can decorate a class itself:: @inject class B: def __init__(self, dependency: Dependency): self.dependency = dependency This is equivalent to decorating its constructor. In particular this provides integration with `dataclasses `_ (the order of decorator application is important here):: @inject @dataclass class C: dependency: Dependency .. note:: This decorator is to be used on class constructors (or, as a convenience, on classes). Using it on non-constructor methods worked in the past but it was an implementation detail rather than a design decision. Third party libraries may, however, provide support for injecting dependencies into non-constructor methods or free functions in one form or another. .. seealso:: Generic type :data:`Inject` A more explicit way to declare parameters as injectable. Function :func:`get_bindings` A way to inspect how various injection declarations interact with each other. .. versionchanged:: 0.16.2 (Re)added support for decorating classes with @inject. """ if isinstance(constructor_or_class, type) and hasattr(constructor_or_class, '__init__'): inject(cast(Any, constructor_or_class).__init__) else: function = constructor_or_class try: bindings = _infer_injected_bindings(function, only_explicit_bindings=False) read_and_store_bindings(function, bindings) except _BindingNotYetAvailable: cast(Any, function).__bindings__ = 'deferred' return constructor_or_class def noninjectable(*args: str) -> Callable[[CallableT], CallableT]: """Mark some parameters as not injectable. This serves as documentation for people reading the code and will prevent Injector from ever attempting to provide the parameters. For example: >>> class Service: ... pass ... >>> class SomeClass: ... @inject ... @noninjectable('user_id') ... def __init__(self, service: Service, user_id: int): ... # ... ... pass :func:`noninjectable` decorations can be stacked on top of each other and the order in which a function is decorated with :func:`inject` and :func:`noninjectable` doesn't matter. .. seealso:: Generic type :data:`NoInject` A nicer way to declare parameters as noninjectable. Function :func:`get_bindings` A way to inspect how various injection declarations interact with each other. """ def decorator(function: CallableT) -> CallableT: argspec = inspect.getfullargspec(inspect.unwrap(function)) for arg in args: if arg not in argspec.args and arg not in argspec.kwonlyargs: raise UnknownArgument('Unable to mark unknown argument %s ' 'as non-injectable.' % arg) existing: Set[str] = getattr(function, '__noninjectables__', set()) merged = existing | set(args) cast(Any, function).__noninjectables__ = merged return function return decorator @private def read_and_store_bindings(f: Callable, bindings: Dict[str, type]) -> None: function_bindings = getattr(f, '__bindings__', None) or {} if function_bindings == 'deferred': function_bindings = {} merged_bindings = dict(function_bindings, **bindings) if hasattr(f, '__func__'): f = cast(Any, f).__func__ cast(Any, f).__bindings__ = merged_bindings class BoundKey(tuple): """A BoundKey provides a key to a type with pre-injected arguments. >>> class A: ... def __init__(self, a, b): ... self.a = a ... self.b = b >>> InjectedA = BoundKey(A, a=InstanceProvider(1), b=InstanceProvider(2)) >>> injector = Injector() >>> a = injector.get(InjectedA) >>> a.a, a.b (1, 2) """ def __new__(cls, interface: Type[T], **kwargs: Any) -> 'BoundKey': kwargs_tuple = tuple(sorted(kwargs.items())) return super(BoundKey, cls).__new__(cls, (interface, kwargs_tuple)) # type: ignore @property def interface(self) -> Type[T]: return self[0] @property def kwargs(self) -> Dict[str, Any]: return dict(self[1]) class AssistedBuilder(Generic[T]): def __init__(self, injector: Injector, target: Type[T]) -> None: self._injector = injector self._target = target def build(self, **kwargs: Any) -> T: binder = self._injector.binder binding, _ = binder.get_binding(self._target) provider = binding.provider if not isinstance(provider, ClassProvider): raise Error( 'Assisted interface building works only with ClassProviders, ' 'got %r for %r' % (provider, binding.interface) ) return self._build_class(cast(Type[T], provider._cls), **kwargs) def _build_class(self, cls: Type[T], **kwargs: Any) -> T: return self._injector.create_object(cls, additional_kwargs=kwargs) class ClassAssistedBuilder(AssistedBuilder[T]): def build(self, **kwargs: Any) -> T: return self._build_class(self._target, **kwargs) def _describe(c: Any) -> str: if hasattr(c, '__name__'): return cast(str, c.__name__) if type(c) in (tuple, list): return '[%s]' % c[0].__name__ return str(c) class ProviderOf(Generic[T]): """Can be used to get a provider of an interface, for example: >>> def provide_int(): ... print('providing') ... return 123 >>> >>> def configure(binder): ... binder.bind(int, to=provide_int) >>> >>> injector = Injector(configure) >>> provider = injector.get(ProviderOf[int]) >>> value = provider.get() providing >>> value 123 """ def __init__(self, injector: Injector, interface: Type[T]): self._injector = injector self._interface = interface def __repr__(self) -> str: return '%s(%r, %r)' % (type(self).__name__, self._injector, self._interface) def get(self) -> T: """Get an implementation for the specified interface.""" return self._injector.get(self._interface) def is_decorated_with_inject(function: Callable[..., Any]) -> bool: """See if given callable is declared to want some dependencies injected. Example use: >>> def fun(i: int) -> str: ... return str(i) >>> is_decorated_with_inject(fun) False >>> >>> @inject ... def fun2(i: int) -> str: ... return str(i) >>> is_decorated_with_inject(fun2) True """ return hasattr(function, '__bindings__') injector-0.21.0/injector/py.typed000066400000000000000000000000001446033672600167240ustar00rootroot00000000000000injector-0.21.0/injector_test.py000066400000000000000000001307631446033672600166620ustar00rootroot00000000000000# encoding: utf-8 # # Copyright (C) 2010 Alec Thomas # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. # # Author: Alec Thomas """Functional tests for the "Injector" dependency injection framework.""" from contextlib import contextmanager from typing import Any, NewType, Optional, Union import abc import sys import threading import traceback import warnings from typing import Dict, List, NewType import pytest from injector import ( Binder, CallError, Inject, Injector, NoInject, Scope, InstanceProvider, ClassProvider, get_bindings, inject, multiprovider, noninjectable, singleton, threadlocal, UnsatisfiedRequirement, CircularDependency, Module, SingletonScope, ScopeDecorator, AssistedBuilder, provider, ProviderOf, ClassAssistedBuilder, Error, UnknownArgument, ) class EmptyClass: pass class DependsOnEmptyClass: @inject def __init__(self, b: EmptyClass): """Construct a new DependsOnEmptyClass.""" self.b = b def prepare_nested_injectors(): def configure(binder): binder.bind(str, to='asd') parent = Injector(configure) child = parent.create_child_injector() return parent, child def check_exception_contains_stuff(exception, stuff): stringified = str(exception) for thing in stuff: assert thing in stringified, '%r should be present in the exception representation: %s' % ( thing, stringified, ) def test_child_injector_inherits_parent_bindings(): parent, child = prepare_nested_injectors() assert child.get(str) == parent.get(str) def test_child_injector_overrides_parent_bindings(): parent, child = prepare_nested_injectors() child.binder.bind(str, to='qwe') assert (parent.get(str), child.get(str)) == ('asd', 'qwe') def test_child_injector_rebinds_arguments_for_parent_scope(): class Cls: val = "" class A(Cls): @inject def __init__(self, val: str): self.val = val def configure_parent(binder): binder.bind(Cls, to=A) binder.bind(str, to="Parent") def configure_child(binder): binder.bind(str, to="Child") parent = Injector(configure_parent) assert parent.get(Cls).val == "Parent" child = parent.create_child_injector(configure_child) assert child.get(Cls).val == "Child" def test_scopes_are_only_bound_to_root_injector(): parent, child = prepare_nested_injectors() class A: pass parent.binder.bind(A, to=A, scope=singleton) assert parent.get(A) is child.get(A) def test_get_default_injected_instances(): def configure(binder): binder.bind(DependsOnEmptyClass) binder.bind(EmptyClass) injector = Injector(configure) assert injector.get(Injector) is injector assert injector.get(Binder) is injector.binder def test_instantiate_injected_method(): a = DependsOnEmptyClass('Bob') assert a.b == 'Bob' def test_method_decorator_is_wrapped(): assert DependsOnEmptyClass.__init__.__doc__ == 'Construct a new DependsOnEmptyClass.' assert DependsOnEmptyClass.__init__.__name__ == '__init__' def test_decorator_works_for_function_with_no_args(): @inject def wrapped(*args, **kwargs): pass def test_providers_arent_called_for_dependencies_that_are_already_provided(): def configure(binder): binder.bind(int, to=lambda: 1 / 0) class A: @inject def __init__(self, i: int): pass injector = Injector(configure) builder = injector.get(AssistedBuilder[A]) with pytest.raises(ZeroDivisionError): builder.build() builder.build(i=3) def test_inject_direct(): def configure(binder): binder.bind(DependsOnEmptyClass) binder.bind(EmptyClass) injector = Injector(configure) a = injector.get(DependsOnEmptyClass) assert isinstance(a, DependsOnEmptyClass) assert isinstance(a.b, EmptyClass) def test_configure_multiple_modules(): def configure_a(binder): binder.bind(DependsOnEmptyClass) def configure_b(binder): binder.bind(EmptyClass) injector = Injector([configure_a, configure_b]) a = injector.get(DependsOnEmptyClass) assert isinstance(a, DependsOnEmptyClass) assert isinstance(a.b, EmptyClass) def test_inject_with_missing_dependency(): def configure(binder): binder.bind(DependsOnEmptyClass) injector = Injector(configure, auto_bind=False) with pytest.raises(UnsatisfiedRequirement): injector.get(EmptyClass) def test_inject_named_interface(): class A: @inject def __init__(self, b: EmptyClass): self.b = b def configure(binder): binder.bind(A) binder.bind(EmptyClass) injector = Injector(configure) a = injector.get(A) assert isinstance(a, A) assert isinstance(a.b, EmptyClass) class TransitiveC: pass class TransitiveB: @inject def __init__(self, c: TransitiveC): self.c = c class TransitiveA: @inject def __init__(self, b: TransitiveB): self.b = b def test_transitive_injection(): def configure(binder): binder.bind(TransitiveA) binder.bind(TransitiveB) binder.bind(TransitiveC) injector = Injector(configure) a = injector.get(TransitiveA) assert isinstance(a, TransitiveA) assert isinstance(a.b, TransitiveB) assert isinstance(a.b.c, TransitiveC) def test_transitive_injection_with_missing_dependency(): def configure(binder): binder.bind(TransitiveA) binder.bind(TransitiveB) injector = Injector(configure, auto_bind=False) with pytest.raises(UnsatisfiedRequirement): injector.get(TransitiveA) with pytest.raises(UnsatisfiedRequirement): injector.get(TransitiveB) def test_inject_singleton(): class A: @inject def __init__(self, b: EmptyClass): self.b = b def configure(binder): binder.bind(A) binder.bind(EmptyClass, scope=SingletonScope) injector1 = Injector(configure) a1 = injector1.get(A) a2 = injector1.get(A) assert a1.b is a2.b @singleton class SingletonB: pass def test_inject_decorated_singleton_class(): class A: @inject def __init__(self, b: SingletonB): self.b = b def configure(binder): binder.bind(A) binder.bind(SingletonB) injector1 = Injector(configure) a1 = injector1.get(A) a2 = injector1.get(A) assert a1.b is a2.b assert a1 is not a2 def test_injecting_an_auto_bound_decorated_singleton_class(): class A: @inject def __init__(self, b: SingletonB): self.b = b injector1 = Injector() a1 = injector1.get(A) a2 = injector1.get(A) assert a1.b is a2.b assert a1 is not a2 def test_a_decorated_singleton_is_shared_between_parent_and_child_injectors_when_parent_creates_it_first(): parent_injector = Injector() child_injector = parent_injector.create_child_injector() assert parent_injector.get(SingletonB) is child_injector.get(SingletonB) def test_a_decorated_singleton_is_shared_between_parent_and_child_injectors_when_child_creates_it_first(): parent_injector = Injector() child_injector = parent_injector.create_child_injector() assert child_injector.get(SingletonB) is parent_injector.get(SingletonB) # Test for https://github.com/python-injector/injector/issues/207 def test_a_decorated_singleton_is_shared_among_child_injectors(): parent_injector = Injector() child_injector_1 = parent_injector.create_child_injector() child_injector_2 = parent_injector.create_child_injector() assert child_injector_1.get(SingletonB) is child_injector_2.get(SingletonB) def test_a_decorated_singleton_should_not_override_explicit_binds(): parent_injector = Injector() child_injector = parent_injector.create_child_injector() grand_child_injector = child_injector.create_child_injector() bound_singleton = SingletonB() child_injector.binder.bind(SingletonB, to=bound_singleton) assert parent_injector.get(SingletonB) is not bound_singleton assert child_injector.get(SingletonB) is bound_singleton assert grand_child_injector.get(SingletonB) is bound_singleton def test_binding_a_singleton_to_a_child_injector_does_not_affect_the_parent_injector(): parent_injector = Injector() child_injector = parent_injector.create_child_injector() child_injector.binder.bind(EmptyClass, scope=singleton) assert child_injector.get(EmptyClass) is child_injector.get(EmptyClass) assert child_injector.get(EmptyClass) is not parent_injector.get(EmptyClass) assert parent_injector.get(EmptyClass) is not parent_injector.get(EmptyClass) def test_a_decorated_singleton_should_not_override_a_child_provider(): parent_injector = Injector() provided_instance = SingletonB() class MyModule(Module): @provider def provide_name(self) -> SingletonB: return provided_instance child_injector = parent_injector.create_child_injector([MyModule]) assert child_injector.get(SingletonB) is provided_instance assert parent_injector.get(SingletonB) is not provided_instance assert parent_injector.get(SingletonB) is parent_injector.get(SingletonB) # Test for https://github.com/python-injector/injector/issues/207 def test_a_decorated_singleton_is_created_as_close_to_the_root_where_dependencies_fulfilled(): class NonInjectableD: @inject def __init__(self, required) -> None: self.required = required @singleton class SingletonC: @inject def __init__(self, d: NonInjectableD): self.d = d parent_injector = Injector() child_injector_1 = parent_injector.create_child_injector() child_injector_2 = parent_injector.create_child_injector() child_injector_2_1 = child_injector_2.create_child_injector() provided_d = NonInjectableD(required=True) child_injector_2.binder.bind(NonInjectableD, to=provided_d) assert child_injector_2_1.get(SingletonC) is child_injector_2.get(SingletonC) assert child_injector_2.get(SingletonC).d is provided_d with pytest.raises(CallError): parent_injector.get(SingletonC) with pytest.raises(CallError): child_injector_1.get(SingletonC) def test_a_bound_decorated_singleton_is_created_as_close_to_the_root_where_it_exists_when_auto_bind_is_disabled(): parent_injector = Injector(auto_bind=False) child_injector_1 = parent_injector.create_child_injector(auto_bind=False) child_injector_2 = parent_injector.create_child_injector(auto_bind=False) child_injector_2_1 = child_injector_2.create_child_injector(auto_bind=False) child_injector_2.binder.bind(SingletonB) assert child_injector_2_1.get(SingletonB) is child_injector_2_1.get(SingletonB) assert child_injector_2_1.get(SingletonB) is child_injector_2.get(SingletonB) with pytest.raises(UnsatisfiedRequirement): parent_injector.get(SingletonB) with pytest.raises(UnsatisfiedRequirement): child_injector_1.get(SingletonB) def test_threadlocal(): @threadlocal class A: def __init__(self): pass def configure(binder): binder.bind(A) injector = Injector(configure) a1 = injector.get(A) a2 = injector.get(A) assert a1 is a2 a3 = [None] ready = threading.Event() def inject_a3(): a3[0] = injector.get(A) ready.set() threading.Thread(target=inject_a3).start() ready.wait(1.0) assert a2 is not a3[0] and a3[0] is not None class Interface2: pass def test_injecting_interface_implementation(): class Implementation: pass class A: @inject def __init__(self, i: Interface2): self.i = i def configure(binder): binder.bind(A) binder.bind(Interface2, to=Implementation) injector = Injector(configure) a = injector.get(A) assert isinstance(a.i, Implementation) class CyclicInterface: pass class CyclicA: @inject def __init__(self, i: CyclicInterface): self.i = i class CyclicB: @inject def __init__(self, a: CyclicA): self.a = a def test_cyclic_dependencies(): def configure(binder): binder.bind(CyclicInterface, to=CyclicB) binder.bind(CyclicA) injector = Injector(configure) with pytest.raises(CircularDependency): injector.get(CyclicA) class CyclicInterface2: pass class CyclicA2: @inject def __init__(self, i: CyclicInterface2): self.i = i class CyclicB2: @inject def __init__(self, a_builder: AssistedBuilder[CyclicA2]): self.a = a_builder.build(i=self) def test_dependency_cycle_can_be_worked_broken_by_assisted_building(): def configure(binder): binder.bind(CyclicInterface2, to=CyclicB2) binder.bind(CyclicA2) injector = Injector(configure) # Previously it'd detect a circular dependency here: # 1. Constructing CyclicA2 requires CyclicInterface2 (bound to CyclicB2) # 2. Constructing CyclicB2 requires assisted build of CyclicA2 # 3. Constructing CyclicA2 triggers circular dependency check assert isinstance(injector.get(CyclicA2), CyclicA2) class Interface5: constructed = False def __init__(self): Interface5.constructed = True def test_that_injection_is_lazy(): class A: @inject def __init__(self, i: Interface5): self.i = i def configure(binder): binder.bind(Interface5) binder.bind(A) injector = Injector(configure) assert not (Interface5.constructed) injector.get(A) assert Interface5.constructed def test_module_provider(): class MyModule(Module): @provider def provide_name(self) -> str: return 'Bob' module = MyModule() injector = Injector(module) assert injector.get(str) == 'Bob' def test_module_class_gets_instantiated(): name = 'Meg' class MyModule(Module): def configure(self, binder): binder.bind(str, to=name) injector = Injector(MyModule) assert injector.get(str) == name def test_inject_and_provide_coexist_happily(): class MyModule(Module): @provider def provide_weight(self) -> float: return 50.0 @provider def provide_age(self) -> int: return 25 # TODO(alec) Make provider/inject order independent. @provider @inject def provide_description(self, age: int, weight: float) -> str: return 'Bob is %d and weighs %0.1fkg' % (age, weight) assert Injector(MyModule()).get(str) == 'Bob is 25 and weighs 50.0kg' Names = NewType('Names', List[str]) Passwords = NewType('Ages', Dict[str, str]) def test_multibind(): # First let's have some explicit multibindings def configure(binder): binder.multibind(List[str], to=['not a name']) binder.multibind(Dict[str, str], to={'asd': 'qwe'}) # To make sure Lists and Dicts of different subtypes are treated distinctly binder.multibind(List[int], to=[1, 2, 3]) binder.multibind(Dict[str, int], to={'weight': 12}) # To see that NewTypes are treated distinctly binder.multibind(Names, to=['Bob']) binder.multibind(Passwords, to={'Bob': 'password1'}) # Then @multiprovider-decorated Module methods class CustomModule(Module): @multiprovider def provide_some_ints(self) -> List[int]: return [4, 5, 6] @multiprovider def provide_some_strs(self) -> List[str]: return ['not a name either'] @multiprovider def provide_str_to_str_mapping(self) -> Dict[str, str]: return {'xxx': 'yyy'} @multiprovider def provide_str_to_int_mapping(self) -> Dict[str, int]: return {'height': 33} @multiprovider def provide_names(self) -> Names: return ['Alice', 'Clarice'] @multiprovider def provide_passwords(self) -> Passwords: return {'Alice': 'aojrioeg3', 'Clarice': 'clarice30'} injector = Injector([configure, CustomModule]) assert injector.get(List[str]) == ['not a name', 'not a name either'] assert injector.get(List[int]) == [1, 2, 3, 4, 5, 6] assert injector.get(Dict[str, str]) == {'asd': 'qwe', 'xxx': 'yyy'} assert injector.get(Dict[str, int]) == {'weight': 12, 'height': 33} assert injector.get(Names) == ['Bob', 'Alice', 'Clarice'] assert injector.get(Passwords) == {'Bob': 'password1', 'Alice': 'aojrioeg3', 'Clarice': 'clarice30'} def test_regular_bind_and_provider_dont_work_with_multibind(): # We only want multibind and multiprovider to work to avoid confusion Names = NewType('Names', List[str]) Passwords = NewType('Passwords', Dict[str, str]) class MyModule(Module): with pytest.raises(Error): @provider def provide_strs(self) -> List[str]: return [] with pytest.raises(Error): @provider def provide_names(self) -> Names: return [] with pytest.raises(Error): @provider def provide_strs_in_dict(self) -> Dict[str, str]: return {} with pytest.raises(Error): @provider def provide_passwords(self) -> Passwords: return {} injector = Injector() binder = injector.binder with pytest.raises(Error): binder.bind(List[str], to=[]) with pytest.raises(Error): binder.bind(Names, to=[]) with pytest.raises(Error): binder.bind(Dict[str, str], to={}) with pytest.raises(Error): binder.bind(Passwords, to={}) def test_auto_bind(): class A: pass injector = Injector() assert isinstance(injector.get(A), A) def test_auto_bind_with_newtype(): # Reported in https://github.com/alecthomas/injector/issues/117 class A: pass AliasOfA = NewType('AliasOfA', A) injector = Injector() assert isinstance(injector.get(AliasOfA), A) class Request: pass class RequestScope(Scope): def configure(self): self.context = None @contextmanager def __call__(self, request): assert self.context is None self.context = {} binder = self.injector.get(Binder) binder.bind(Request, to=request, scope=RequestScope) yield self.context = None def get(self, key, provider): if self.context is None: raise UnsatisfiedRequirement(None, key) try: return self.context[key] except KeyError: provider = InstanceProvider(provider.get(self.injector)) self.context[key] = provider return provider request = ScopeDecorator(RequestScope) @request class Handler: def __init__(self, request): self.request = request class RequestModule(Module): @provider @inject def handler(self, request: Request) -> Handler: return Handler(request) def test_custom_scope(): injector = Injector([RequestModule()], auto_bind=False) with pytest.raises(UnsatisfiedRequirement): injector.get(Handler) scope = injector.get(RequestScope) request = Request() with scope(request): handler = injector.get(Handler) assert handler.request is request with pytest.raises(UnsatisfiedRequirement): injector.get(Handler) def test_binder_install(): class ModuleA(Module): def configure(self, binder): binder.bind(str, to='hello world') class ModuleB(Module): def configure(self, binder): binder.install(ModuleA()) injector = Injector([ModuleB()]) assert injector.get(str) == 'hello world' def test_binder_provider_for_method_with_explicit_provider(): injector = Injector() binder = injector.binder provider = binder.provider_for(int, to=InstanceProvider(1)) assert type(provider) is InstanceProvider assert provider.get(injector) == 1 def test_binder_provider_for_method_with_instance(): injector = Injector() binder = injector.binder provider = binder.provider_for(int, to=1) assert type(provider) is InstanceProvider assert provider.get(injector) == 1 def test_binder_provider_for_method_with_class(): injector = Injector() binder = injector.binder provider = binder.provider_for(int) assert type(provider) is ClassProvider assert provider.get(injector) == 0 def test_binder_provider_for_method_with_class_to_specific_subclass(): class A: pass class B(A): pass injector = Injector() binder = injector.binder provider = binder.provider_for(A, B) assert type(provider) is ClassProvider assert isinstance(provider.get(injector), B) def test_binder_provider_for_type_with_metaclass(): # use a metaclass cross python2/3 way # otherwise should be: # class A(object, metaclass=abc.ABCMeta): # passa A = abc.ABCMeta('A', (object,), {}) injector = Injector() binder = injector.binder assert isinstance(binder.provider_for(A, None).get(injector), A) class ClassA: def __init__(self, parameter): pass class ClassB: @inject def __init__(self, a: ClassA): pass def test_injecting_undecorated_class_with_missing_dependencies_raises_the_right_error(): injector = Injector() try: injector.get(ClassB) except CallError as ce: check_exception_contains_stuff(ce, ('ClassA.__init__', 'ClassB')) def test_call_to_method_with_legitimate_call_error_raises_type_error(): class A: def __init__(self): max() injector = Injector() with pytest.raises(TypeError): injector.get(A) def test_call_error_str_representation_handles_single_arg(): ce = CallError('zxc') assert str(ce) == 'zxc' class NeedsAssistance: @inject def __init__(self, a: str, b): self.a = a self.b = b def test_assisted_builder_works_when_got_directly_from_injector(): injector = Injector() builder = injector.get(AssistedBuilder[NeedsAssistance]) obj = builder.build(b=123) assert (obj.a, obj.b) == (str(), 123) def test_assisted_builder_works_when_injected(): class X: @inject def __init__(self, builder: AssistedBuilder[NeedsAssistance]): self.obj = builder.build(b=234) injector = Injector() x = injector.get(X) assert (x.obj.a, x.obj.b) == (str(), 234) class Interface: b = 0 def test_assisted_builder_uses_bindings(): def configure(binder): binder.bind(Interface, to=NeedsAssistance) injector = Injector(configure) builder = injector.get(AssistedBuilder[Interface]) x = builder.build(b=333) assert (type(x), x.b) == (NeedsAssistance, 333) def test_assisted_builder_uses_concrete_class_when_specified(): class X: pass def configure(binder): # meant only to show that provider isn't called binder.bind(X, to=lambda: 1 / 0) injector = Injector(configure) builder = injector.get(ClassAssistedBuilder[X]) builder.build() def test_assisted_builder_injection_is_safe_to_use_with_multiple_injectors(): class X: @inject def __init__(self, builder: AssistedBuilder[NeedsAssistance]): self.builder = builder i1, i2 = Injector(), Injector() b1 = i1.get(X).builder b2 = i2.get(X).builder assert (b1._injector, b2._injector) == (i1, i2) def test_assisted_builder_injection_is_safe_to_use_with_child_injectors(): class X: @inject def __init__(self, builder: AssistedBuilder[NeedsAssistance]): self.builder = builder i1 = Injector() i2 = i1.create_child_injector() b1 = i1.get(X).builder b2 = i2.get(X).builder assert (b1._injector, b2._injector) == (i1, i2) class TestThreadSafety: def setup_method(self): self.event = threading.Event() def configure(binder): binder.bind(str, to=lambda: self.event.wait() and 'this is str') class XXX: @inject def __init__(self, s: str): pass self.injector = Injector(configure) self.cls = XXX def gather_results(self, count): objects = [] lock = threading.Lock() def target(): o = self.injector.get(self.cls) with lock: objects.append(o) threads = [threading.Thread(target=target) for i in range(count)] for t in threads: t.start() self.event.set() for t in threads: t.join() return objects def test_injection_is_thread_safe(self): objects = self.gather_results(2) assert len(objects) == 2 def test_singleton_scope_is_thread_safe(self): self.injector.binder.bind(self.cls, scope=singleton) a, b = self.gather_results(2) assert a is b def test_provider_and_scope_decorator_collaboration(): @provider @singleton def provider_singleton() -> int: return 10 @singleton @provider def singleton_provider() -> int: return 10 assert provider_singleton.__binding__.scope == SingletonScope assert singleton_provider.__binding__.scope == SingletonScope def test_injecting_into_method_of_object_that_is_falseish_works(): # regression test class X(dict): @inject def __init__(self, s: str): pass injector = Injector() injector.get(X) Name = NewType("Name", str) Message = NewType("Message", str) def test_callable_provider_injection(): @inject def create_message(name: Name): return "Hello, " + name def configure(binder): binder.bind(Name, to="John") binder.bind(Message, to=create_message) injector = Injector([configure]) msg = injector.get(Message) assert msg == "Hello, John" def test_providerof(): counter = [0] def provide_str(): counter[0] += 1 return 'content' def configure(binder): binder.bind(str, to=provide_str) injector = Injector(configure) assert counter[0] == 0 provider = injector.get(ProviderOf[str]) assert counter[0] == 0 assert provider.get() == 'content' assert counter[0] == 1 assert provider.get() == injector.get(str) assert counter[0] == 3 def test_providerof_cannot_be_bound(): def configure(binder): binder.bind(ProviderOf[int], to=InstanceProvider(None)) with pytest.raises(Exception): Injector(configure) def test_providerof_is_safe_to_use_with_multiple_injectors(): def configure1(binder): binder.bind(int, to=1) def configure2(binder): binder.bind(int, to=2) injector1 = Injector(configure1) injector2 = Injector(configure2) provider_of = ProviderOf[int] provider1 = injector1.get(provider_of) provider2 = injector2.get(provider_of) assert provider1.get() == 1 assert provider2.get() == 2 def test_special_interfaces_work_with_auto_bind_disabled(): class InjectMe: pass def configure(binder): binder.bind(InjectMe, to=InstanceProvider(InjectMe())) injector = Injector(configure, auto_bind=False) # This line used to fail with: # Traceback (most recent call last): # File "/projects/injector/injector_test.py", line 1171, # in test_auto_bind_disabled_regressions # injector.get(ProviderOf(InjectMe)) # File "/projects/injector/injector.py", line 687, in get # binding = self.binder.get_binding(None, key) # File "/projects/injector/injector.py", line 459, in get_binding # raise UnsatisfiedRequirement(cls, key) # UnsatisfiedRequirement: unsatisfied requirement on # injector.get(ProviderOf[InjectMe]) # This used to fail with an error similar to the ProviderOf one injector.get(ClassAssistedBuilder[InjectMe]) def test_binding_an_instance_regression(): text = b'hello'.decode() def configure(binder): # Yes, this binding doesn't make sense strictly speaking but # it's just a sample case. binder.bind(bytes, to=text) injector = Injector(configure) # This used to return empty bytes instead of the expected string assert injector.get(bytes) == text class PartialB: @inject def __init__(self, a: EmptyClass, b: str): self.a = a self.b = b def test_class_assisted_builder_of_partially_injected_class_old(): class C: @inject def __init__(self, a: EmptyClass, builder: ClassAssistedBuilder[PartialB]): self.a = a self.b = builder.build(b='C') c = Injector().get(C) assert isinstance(c, C) assert isinstance(c.b, PartialB) assert isinstance(c.b.a, EmptyClass) class ImplicitA: pass class ImplicitB: @inject def __init__(self, a: ImplicitA): self.a = a class ImplicitC: @inject def __init__(self, b: ImplicitB): self.b = b def test_implicit_injection_for_python3(): injector = Injector() c = injector.get(ImplicitC) assert isinstance(c, ImplicitC) assert isinstance(c.b, ImplicitB) assert isinstance(c.b.a, ImplicitA) def test_annotation_based_injection_works_in_provider_methods(): class MyModule(Module): def configure(self, binder): binder.bind(int, to=42) @provider def provide_str(self, i: int) -> str: return str(i) @singleton @provider def provide_object(self) -> object: return object() injector = Injector(MyModule) assert injector.get(str) == '42' assert injector.get(object) is injector.get(object) class Fetcher: def fetch(self, user_id): assert user_id == 333 return {'name': 'John'} class Processor: @noninjectable('provider_id') @inject @noninjectable('user_id') def __init__(self, fetcher: Fetcher, user_id: int, provider_id: str): assert provider_id == 'not injected' data = fetcher.fetch(user_id) self.name = data['name'] def test_assisted_building_is_supported(): def configure(binder): binder.bind(int, to=897) binder.bind(str, to='injected') injector = Injector(configure) processor_builder = injector.get(AssistedBuilder[Processor]) with pytest.raises(CallError): processor_builder.build() processor = processor_builder.build(user_id=333, provider_id='not injected') assert processor.name == 'John' def test_raises_when_noninjectable_arguments_defined_with_invalid_arguments(): with pytest.raises(UnknownArgument): class A: @inject @noninjectable('c') def __init__(self, b: str): self.b = b def test_can_create_instance_with_untyped_noninjectable_argument(): class Parent: @inject @noninjectable('child1', 'child2') def __init__(self, child1, *, child2): self.child1 = child1 self.child2 = child2 injector = Injector() parent_builder = injector.get(AssistedBuilder[Parent]) parent = parent_builder.build(child1='injected1', child2='injected2') assert parent.child1 == 'injected1' assert parent.child2 == 'injected2' def test_implicit_injection_fails_when_annotations_are_missing(): class A: def __init__(self, n): self.n = n injector = Injector() with pytest.raises(CallError): injector.get(A) def test_injection_works_in_presence_of_return_value_annotation(): # Code with PEP 484-compatible type hints will have __init__ methods # annotated as returning None[1] and this didn't work well with Injector. # # [1] https://www.python.org/dev/peps/pep-0484/#the-meaning-of-annotations class A: @inject def __init__(self, s: str) -> None: self.s = s def configure(binder): binder.bind(str, to='this is string') injector = Injector([configure]) # Used to fail with: # injector.UnknownProvider: couldn't determine provider for None to None a = injector.get(A) # Just a sanity check, if the code above worked we're almost certain # we're good but just in case the return value annotation handling changed # something: assert a.s == 'this is string' def test_things_dont_break_in_presence_of_args_or_kwargs(): class A: @inject def __init__(self, s: str, *args: int, **kwargs: str): assert not args assert not kwargs injector = Injector() # The following line used to fail with something like this: # Traceback (most recent call last): # File "/ve/injector/injector_test_py3.py", line 192, # in test_things_dont_break_in_presence_of_args_or_kwargs # injector.get(A) # File "/ve/injector/injector.py", line 707, in get # result = scope_instance.get(key, binding.provider).get(self) # File "/ve/injector/injector.py", line 142, in get # return injector.create_object(self._cls) # File "/ve/injector/injector.py", line 744, in create_object # init(instance, **additional_kwargs) # File "/ve/injector/injector.py", line 1082, in inject # kwargs=kwargs # File "/ve/injector/injector.py", line 851, in call_with_injection # **dependencies) # File "/ve/injector/injector_test_py3.py", line 189, in __init__ # assert not kwargs # AssertionError: assert not {'args': 0, 'kwargs': ''} injector.get(A) def test_forward_references_in_annotations_are_handled(): # See https://www.python.org/dev/peps/pep-0484/#forward-references for details class CustomModule(Module): @provider def provide_x(self) -> 'X': return X('hello') @inject def fun(s: 'X') -> 'X': return s # The class needs to be module-global in order for the string -> object # resolution mechanism to work. I could make it work with locals but it # doesn't seem worth it. global X class X: def __init__(self, message: str) -> None: self.message = message try: injector = Injector(CustomModule) assert injector.call_with_injection(fun).message == 'hello' finally: del X def test_more_useful_exception_is_raised_when_parameters_type_is_any(): @inject def fun(a: Any) -> None: pass injector = Injector() # This was the exception before: # # TypeError: Cannot instantiate # # Now: # # injector.CallError: Call to AnyMeta.__new__() failed: Cannot instantiate # (injection stack: ['injector_test_py3']) # # In this case the injection stack doesn't provide too much information but # it quickly gets helpful when the stack gets deeper. with pytest.raises((CallError, TypeError)): injector.call_with_injection(fun) def test_optionals_are_ignored_for_now(): @inject def fun(s: str = None): return s assert Injector().call_with_injection(fun) == '' def test_explicitly_passed_parameters_override_injectable_values(): # The class needs to be defined globally for the 'X' forward reference to be able to be resolved. global X # We test a method on top of regular function to exercise the code path that's # responsible for handling methods. class X: @inject def method(self, s: str) -> str: return s @inject def method_typed_self(self: 'X', s: str) -> str: return s @inject def function(s: str) -> str: return s injection_counter = 0 def provide_str() -> str: nonlocal injection_counter injection_counter += 1 return 'injected string' def configure(binder: Binder) -> None: binder.bind(str, to=provide_str) injector = Injector([configure]) x = X() try: assert injection_counter == 0 assert injector.call_with_injection(x.method) == 'injected string' assert injection_counter == 1 assert injector.call_with_injection(x.method_typed_self) == 'injected string' assert injection_counter == 2 assert injector.call_with_injection(function) == 'injected string' assert injection_counter == 3 assert injector.call_with_injection(x.method, args=('passed string',)) == 'passed string' assert injection_counter == 3 assert injector.call_with_injection(x.method_typed_self, args=('passed string',)) == 'passed string' assert injection_counter == 3 assert injector.call_with_injection(function, args=('passed string',)) == 'passed string' assert injection_counter == 3 assert injector.call_with_injection(x.method, kwargs={'s': 'passed string'}) == 'passed string' assert injection_counter == 3 assert ( injector.call_with_injection(x.method_typed_self, kwargs={'s': 'passed string'}) == 'passed string' ) assert injection_counter == 3 assert injector.call_with_injection(function, kwargs={'s': 'passed string'}) == 'passed string' assert injection_counter == 3 finally: del X class AssistedB: @inject def __init__(self, a: EmptyClass, b: str): self.a = a self.b = b def test_class_assisted_builder_of_partially_injected_class(): class C: @inject def __init__(self, a: EmptyClass, builder: ClassAssistedBuilder[AssistedB]): self.a = a self.b = builder.build(b='C') c = Injector().get(C) assert isinstance(c, C) assert isinstance(c.b, AssistedB) assert isinstance(c.b.a, EmptyClass) # The test taken from Alec Thomas' pull request: https://github.com/alecthomas/injector/pull/73 def test_child_scope(): TestKey = NewType('TestKey', str) TestKey2 = NewType('TestKey2', str) def parent_module(binder): binder.bind(TestKey, to='in parent', scope=singleton) def first_child_module(binder): binder.bind(TestKey2, to='in first child', scope=singleton) def second_child_module(binder): binder.bind(TestKey2, to='in second child', scope=singleton) injector = Injector(modules=[parent_module]) first_child_injector = injector.create_child_injector(modules=[first_child_module]) second_child_injector = injector.create_child_injector(modules=[second_child_module]) assert first_child_injector.get(TestKey) is first_child_injector.get(TestKey) assert first_child_injector.get(TestKey) is second_child_injector.get(TestKey) assert first_child_injector.get(TestKey2) is not second_child_injector.get(TestKey2) def test_custom_scopes_work_as_expected_with_child_injectors(): class CustomSingletonScope(SingletonScope): pass custom_singleton = ScopeDecorator(CustomSingletonScope) def parent_module(binder): binder.bind(str, to='parent value', scope=custom_singleton) def child_module(binder): binder.bind(str, to='child value', scope=custom_singleton) parent = Injector(modules=[parent_module]) child = parent.create_child_injector(modules=[child_module]) print('parent, child: %s, %s' % (parent, child)) assert parent.get(str) == 'parent value' assert child.get(str) == 'child value' # Test for https://github.com/alecthomas/injector/issues/75 def test_inject_decorator_does_not_break_manual_construction_of_pyqt_objects(): class PyQtFake: @inject def __init__(self): pass def __getattribute__(self, item): if item == '__injector__': raise RuntimeError( 'A PyQt class would raise this exception if getting ' 'self.__injector__ before __init__ is called and ' 'self.__injector__ has not been set by Injector.' ) return object.__getattribute__(self, item) instance = PyQtFake() # This used to raise the exception assert isinstance(instance, PyQtFake) def test_using_an_assisted_builder_with_a_provider_raises_an_injector_error(): class MyModule(Module): @provider def provide_a(self, builder: AssistedBuilder[EmptyClass]) -> EmptyClass: return builder.build() injector = Injector(MyModule) with pytest.raises(Error): injector.get(EmptyClass) def test_newtype_integration_works(): UserID = NewType('UserID', int) def configure(binder): binder.bind(UserID, to=123) injector = Injector([configure]) assert injector.get(UserID) == 123 @pytest.mark.skipif(sys.version_info < (3, 6), reason="Requires Python 3.6+") def test_dataclass_integration_works(): import dataclasses # Python 3.6+-only syntax below exec( """ @inject @dataclasses.dataclass class Data: name: str """, locals(), globals(), ) def configure(binder): binder.bind(str, to='data') injector = Injector([configure]) assert injector.get(Data).name == 'data' def test_binder_does_not_have_a_binding_for_an_unbound_type(): injector = Injector() assert not injector.binder.has_binding_for(int) assert not injector.binder.has_explicit_binding_for(int) def test_binder_has_binding_for_explicitly_bound_type(): def configure(binder): binder.bind(int, to=123) injector = Injector([configure]) assert injector.binder.has_binding_for(int) assert injector.binder.has_explicit_binding_for(int) def test_binder_has_implicit_binding_for_implicitly_bound_type(): injector = Injector() injector.get(int) assert injector.binder.has_binding_for(int) assert not injector.binder.has_explicit_binding_for(int) def test_get_bindings(): def function1(a: int) -> None: pass assert get_bindings(function1) == {} @inject def function2(a: int) -> None: pass assert get_bindings(function2) == {'a': int} @inject @noninjectable('b') def function3(a: int, b: str) -> None: pass assert get_bindings(function3) == {'a': int} # Let's verify that the inject/noninjectable ordering doesn't matter @noninjectable('b') @inject def function3b(a: int, b: str) -> None: pass assert get_bindings(function3b) == {'a': int} # The simple case of no @inject but injection requested with Inject[...] def function4(a: Inject[int], b: str) -> None: pass assert get_bindings(function4) == {'a': int} # Using @inject with Inject is redundant but it should not break anything @inject def function5(a: Inject[int], b: str) -> None: pass assert get_bindings(function5) == {'a': int, 'b': str} # We need to be able to exclude a parameter from injection with NoInject @inject def function6(a: int, b: NoInject[str]) -> None: pass assert get_bindings(function6) == {'a': int} # The presence of NoInject should not trigger anything on its own def function7(a: int, b: NoInject[str]) -> None: pass assert get_bindings(function7) == {} # There was a bug where in case of multiple NoInject-decorated parameters only the first one was # actually made noninjectable and we tried to inject something we couldn't possibly provide # into the second one. @inject def function8(a: NoInject[int], b: NoInject[int]) -> None: pass assert get_bindings(function8) == {} # Default arguments to NoInject annotations should behave the same as noninjectable decorator w.r.t 'None' @inject @noninjectable('b') def function9(self, a: int, b: Optional[str] = None): pass @inject def function10(self, a: int, b: NoInject[Optional[str]] = None): # b:s type is Union[NoInject[Union[str, None]], None] pass assert get_bindings(function9) == {'a': int} == get_bindings(function10) # If there's a return type annottion that contains an a forward reference that can't be # resolved (for whatever reason) we don't want that to break things for us – return types # don't matter for the purpose of dependency injection. @inject def function11(a: int) -> 'InvalidForwardReference': pass assert get_bindings(function11) == {'a': int} # Tests https://github.com/alecthomas/injector/issues/202 @pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+") def test_get_bindings_for_pep_604(): @inject def function1(a: int | None) -> None: pass assert get_bindings(function1) == {'a': int} @inject def function1(a: int | str) -> None: pass assert get_bindings(function1) == {'a': Union[int, str]} injector-0.21.0/mypy.ini000066400000000000000000000002131446033672600151150ustar00rootroot00000000000000[mypy] ignore_missing_imports = true follow_imports = error warn_no_return = true warn_redundant_casts = true disallow_untyped_defs = true injector-0.21.0/pyproject.toml000066400000000000000000000001421446033672600163330ustar00rootroot00000000000000[tool.black] line-length = 110 target_version = ['py36', 'py37'] skip_string_normalization = true injector-0.21.0/pytest.ini000066400000000000000000000003071446033672600154530ustar00rootroot00000000000000[pytest] addopts = -v --tb=native --doctest-glob=*.md --doctest-modules --cov-report term --cov-report html --cov-report xml --cov=injector --cov-branch norecursedirs = __pycache__ *venv* .git build injector-0.21.0/requirements-dev.in000066400000000000000000000006601446033672600172530ustar00rootroot00000000000000# Our direct dependencies used in development/CI. # # We generate requirements-dev.txt from this file by running # # pip install -r requirements-dev.in && pip freeze > requirements-dev.txt # # and then modifying the file manually to restrict black and mypy to CPython pytest pytest-cov>=2.5.1 mypy;implementation_name=="cpython" black;implementation_name=="cpython" check-manifest typing_extensions>=3.7.4;python_version<"3.9" injector-0.21.0/requirements-dev.txt000066400000000000000000000005661446033672600174710ustar00rootroot00000000000000black==23.3.0;implementation_name=="cpython" build==0.10.0 check-manifest==0.49 click==8.1.3 coverage==7.2.7 exceptiongroup==1.1.1 iniconfig==2.0.0 mypy==1.4.1;implementation_name=="cpython" mypy-extensions==1.0.0 packaging==23.1 pathspec==0.11.1 platformdirs==3.8.0 pluggy==1.2.0 pyproject_hooks==1.0.0 pytest==7.4.0 pytest-cov==4.1.0 tomli==2.0.1 typing_extensions==4.7.0 injector-0.21.0/requirements.txt000066400000000000000000000000561446033672600167070ustar00rootroot00000000000000typing_extensions>=3.7.4;python_version<"3.9" injector-0.21.0/setup.cfg000066400000000000000000000000311446033672600152350ustar00rootroot00000000000000[wheel] universal = True injector-0.21.0/setup.py000066400000000000000000000042441446033672600151400ustar00rootroot00000000000000from setuptools import setup, Command import sys import warnings warnings.filterwarnings("always", module=__name__) def obtain_requirements(file_name): with open(file_name) as fd_in: for line in fd_in: if '#' not in line: yield line.strip() class PyTest(Command): user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): import subprocess errno = subprocess.call([sys.executable, '-m', 'pytest']) raise SystemExit(errno) def read_injector_variable(name): prefix = '%s = ' % (name,) with open('injector/__init__.py') as f: for line in f: if line.startswith(prefix): return line.replace(prefix, '').strip().strip("'") raise AssertionError('variable %s not found' % (name,)) version = read_injector_variable('__version__') version_tag = read_injector_variable('__version_tag__') requirements = list(obtain_requirements('requirements.txt')) requirements_dev = list(obtain_requirements('requirements-dev.txt')) try: import pypandoc long_description = pypandoc.convert_file('README.md', 'rst') except ImportError: warnings.warn('Could not locate pandoc, using Markdown long_description.', ImportWarning) with open('README.md') as f: long_description = f.read() description = long_description.splitlines()[0].strip() setup( name='injector', url='https://github.com/alecthomas/injector', download_url='https://pypi.org/project/injector/', version=version, options=dict(egg_info=dict(tag_build=version_tag)), description=description, long_description=long_description, license='BSD', platforms=['any'], packages=['injector'], package_data={'injector': ['py.typed']}, author='Alec Thomas', author_email='alec@swapoff.org', cmdclass={'test': PyTest}, extras_require={'dev': requirements_dev}, keywords=[ 'Dependency Injection', 'DI', 'Dependency Injection framework', 'Inversion of Control', 'IoC', 'Inversion of Control container', ], install_requires=requirements, )