├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .readthedocs.yaml ├── CHANGES ├── COPYING ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── _templates │ └── sidebar.html ├── api.rst ├── changelog.rst ├── conf.py ├── faq.rst ├── index.rst ├── logging.rst ├── practices.rst ├── scopes.rst ├── terminology.rst └── testing.rst ├── injector ├── __init__.py └── py.typed ├── injector_test.py ├── mypy.ini ├── pyproject.toml ├── pytest.ini ├── requirements-dev.in ├── requirements-dev.txt ├── requirements-docs.in ├── requirements-docs.txt ├── requirements.txt ├── setup.cfg └── setup.py /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest] 15 | python-version: 16 | [ 17 | 3.8, 18 | 3.9, 19 | "3.10", 20 | "3.11", 21 | "3.12", 22 | "3.13", 23 | "pypy3.8", 24 | "pypy3.9", 25 | "pypy3.10", 26 | "pypy3.11", 27 | ] 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Set up Python ${{ matrix.python-version }} 31 | uses: actions/setup-python@v4 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | - name: Install dependencies 35 | run: | 36 | pip install --upgrade -r requirements-dev.txt 37 | pip install . 38 | - name: Run tests 39 | run: | 40 | py.test -vv --cov=injector --cov-branch --cov-report html --cov-report term 41 | if which mypy; then mypy injector ; fi 42 | if which black; then black --check . ; fi 43 | check-manifest 44 | - name: Report coverage to Codecov 45 | uses: codecov/codecov-action@v1 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.* 2 | 3 | !/.gitignore 4 | !/.github 5 | 6 | .cache/ 7 | __pycache__/ 8 | docs/_build/ 9 | build/ 10 | htmlcov/ 11 | *,cover 12 | .mypy_cache/ 13 | .pytest_cache/ 14 | coverage.xml 15 | /dist/ 16 | /injector.egg-info/ 17 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | version: 2 5 | 6 | build: 7 | os: ubuntu-22.04 8 | tools: 9 | python: "3.12" 10 | 11 | sphinx: 12 | configuration: docs/conf.py 13 | # TODO: Enable this when we get rid of the existing warnings 14 | # fail_on_warning: true 15 | 16 | python: 17 | install: 18 | - method: pip 19 | path: . 20 | - requirements: requirements-docs.txt 21 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Injector Change Log 2 | =================== 3 | 4 | 0.22.0 5 | ------ 6 | 7 | Date: 2024-07-08 8 | 9 | Added: 10 | 11 | - Added support for injecting `PEP 593 `_ 12 | `Annotated `_ 13 | 14 | Removed: 15 | 16 | - Dropped Python 3.7 support 17 | 18 | 0.21.0 19 | ------ 20 | 21 | - Improved the documentation, thanks to jonathanmach and Jakub Wilk 22 | - Fixed a thread-safety regression 23 | - Improved the type annotations, thanks to David Pärsson 24 | - Fixed singleton scope behavior with parent/child injectors, thanks to David Pärsson 25 | - Stopped using a deprecated test function, thanks to ljnsn 26 | 27 | 0.20.1 28 | ------ 29 | 30 | - Added support for PEP 604 union types (Python 3.10+), thanks to David Pärsson 31 | - Fixed building with pypandoc 1.8+, thanks to Søren Fuglede Jørgensen 32 | 33 | 0.20.0 34 | ------ 35 | 36 | - Fixed handling of Union combined with Annotated, thanks to Tobias Nilsson 37 | - Fixed AssitedBuilder/child Injector interaction, thanks to Erik Cederberg 38 | - Made get_bindings() and injections work even if a injectee's return type 39 | annotation is a forward reference that can't be resolved 40 | 41 | Backwards incompatible: 42 | 43 | - Dropped Python 3.6 support 44 | 45 | 0.19.0 46 | ------ 47 | 48 | - Added the license to the source distribution, thanks to Joshua Adelman 49 | - Added Python 3.9 and 3.10 support, this includes fixing Python 3.10 compatibility, thanks to Torge Matthies 50 | - Improved the documentation, thanks to Takahiro Kojima 51 | - Improved the source distribution so that it can be used to build and install wheels, thanks to Janusz Skonieczny 52 | - Added requirements files for easier development, thanks to Greg Eremeev 53 | 54 | Backwards incompatible: 55 | 56 | - Removed Python 3.5 support 57 | 58 | 0.18.4 59 | ------ 60 | 61 | - Fixed a bug where only one of multiple NoInject annotations was interpreted 62 | 63 | 0.18.3 64 | ------ 65 | 66 | - Fixed Python 3.5.3 compatibility 67 | 68 | 0.18.2 69 | ------ 70 | 71 | - Added remaining type hints to the codebase so that the client code can have better static typing safety 72 | - Fixed UnsatisfiedRequirement string representation (this time for real) 73 | - Added forward return type reference support to provider methods 74 | 75 | 0.18.1 76 | ------ 77 | 78 | - Fixed UnsatisfiedRequirement instantiation (trying to get its string representation would fail) 79 | - Fixed injecting a subclass of a generic type on Python versions older than 3.7.0 80 | - Fixed regression that caused BoundKey injection failure 81 | 82 | 0.18.0 83 | ------ 84 | 85 | - Added new public :func:`get_bindings ` function to see what parameters will be injected 86 | into a function 87 | - Added new generic types using a draft implementation of `PEP 593 `_: 88 | :data:`Inject ` and :data:`NoInject `. Those serve as additional ways to 89 | declare (non)injectable parameters while :func:`inject ` won't go away any time soon 90 | :func:`noninjectable ` may be removed once `NoInject` is cofirmed to work. 91 | 92 | Backwards incompatible: 93 | 94 | - Removed previously deprecated `Key`, `BindingKey`, `SequenceKey` and `MappingKey` pseudo-types 95 | 96 | 0.17.0 97 | ------ 98 | 99 | - Added support for using `typing.Dict` and `typing.List` in multibindings. See :meth:`multibind `. 100 | - Added multibinding-specific :func:`provider ` variant: :func:`multiprovider ` 101 | - Deprecated using :func:`provider ` for multibindings 102 | - Fixed failure to provide a default value to a `NewType`-aliased type with auto_bind enabled 103 | - Deprecated :func:`Key `, :func:`SequenceKey ` and 104 | :func:`MappingKey ` – use real types or type aliases instead 105 | - Deprecated using single-item lists and dictionaries for multibindings - use real types or type aliases instead 106 | 107 | Technically backwards incompatible: 108 | 109 | - typing.List and typing.Dict specializations are now explicitly disallowed as :meth:`bind ` 110 | interfaces and types returned by :func:`provider `-decorated methods 111 | 112 | 0.16.2 113 | ------ 114 | 115 | - (Re)added support for decorating classes themselves with :func:`@inject `. This is the same 116 | as decorating their constructors. Among other things this gives us 117 | `dataclasses `_ integration. 118 | 119 | 0.16.1 120 | ------ 121 | 122 | - Reuploaded to fix incorrectly formatted project description 123 | 124 | 0.16.0 125 | ------ 126 | 127 | - Added support for overriding injectable parameters with positional arguments (previously only 128 | possible with keyword arguments) 129 | - Fixed crashes caused by typed self in method signatures 130 | - Improved typing coverage 131 | 132 | Backwards incompatible: 133 | 134 | - Dropped Python 3.4 support 135 | - Removed previously deprecated constructs: with_injector, Injector.install_into, Binder.bind_scope 136 | - Dependencies are no longer injected into Module.configure and raw module functions (previously 137 | deprecated) 138 | - Removed unofficial support for injecting into parent class constructors 139 | 140 | 0.15.0 141 | ------ 142 | 143 | - Added type information for Injector.create_object() (patch #101 thanks to David Pärsson) 144 | - Made the code easier to understand (patch #105 thanks to Christian Clauss) 145 | - Opted the package into distributing type information and checking it (PEP 561) 146 | 147 | 0.14.1 148 | ------ 149 | 150 | - Fixed regression that required all noninjectable parameters to be typed 151 | 152 | 0.14.0 153 | ------ 154 | 155 | - Added NewType support 156 | - Added type hints 157 | 158 | Backwards incompatible: 159 | 160 | - Passing invalid parameter names to @noninjectable() will now result in an error 161 | - Dropped Python 3.3 support 162 | 163 | 0.13.4 164 | ------ 165 | 166 | - Deprecated with_injector. There's no one migration path recommended, it depends on 167 | a particular case. 168 | - Deprecated install_into. 169 | 170 | 0.13.3 171 | ------ 172 | 173 | - Fixed a bug with classes deriving from PyQt classes not being able to be 174 | instantiated manually (bug #75, patch #76 thanks to David Pärsson) 175 | 176 | 0.13.2 177 | ------ 178 | 179 | - Fixed a bug with values shared between Injectors in a hierarchy (bugs #52 and #72) 180 | - Binding scopes explicitly (``Binder.bind_scope``) is no longer necessary and ``bind_scope`` is a no-op now. 181 | 182 | 0.13.1 183 | ------ 184 | 185 | - Improved some error messages 186 | 187 | 0.13.0 188 | ------ 189 | 190 | Backwards incompatible: 191 | 192 | - Dropped Python 3.2 support 193 | - Dropped Injector use_annotations constructor parameter. Whenever @inject is 194 | used parameter annotations will be used automatically. 195 | - Dropped Python 2 support (this includes PyPy) 196 | - Removed @provides decorator, use @provider instead 197 | - Removed support for passing keyword arguments to @inject 198 | 199 | 0.12.0 200 | ------ 201 | 202 | - Fixed binding inference in presence of * and ** arguments (previously Injector 203 | would generate extra arguments, now it just ignores them) 204 | - Improved error reporting 205 | - Fixed compatibility with newer typing versions (that includes the one 206 | bundled with Python 3.6) 207 | 208 | Technically backwards incompatible: 209 | 210 | - Forward references as PEP 484 understands them are being resolved now when 211 | Python 3-style annotations are used. See 212 | https://www.python.org/dev/peps/pep-0484/#forward-references for details. 213 | 214 | Optional parameters are treated as compulsory for the purpose of injection. 215 | 216 | 0.11.1 217 | ------ 218 | 219 | - 0.11.0 packages uploaded to PyPI are broken (can't be installed), this is 220 | a fix-only release. 221 | 222 | 0.11.0 223 | ------ 224 | 225 | * The following way to declare dependencies is introduced and recommended 226 | now: 227 | 228 | .. code-block:: python 229 | 230 | class SomeClass: 231 | @inject 232 | def __init__(self, other: OtherClass): 233 | # ... 234 | 235 | The following ways are still supported but are deprecated and will be 236 | removed in the future: 237 | 238 | .. code-block:: python 239 | 240 | # Python 2-compatible style 241 | class SomeClass 242 | @inject(other=OtherClass) 243 | def __init__(self, other): 244 | # ... 245 | 246 | # Python 3 style without @inject-decoration but with use_annotations 247 | class SomeClass: 248 | def __init__(self, other: OtherClass): 249 | # ... 250 | 251 | injector = Injector(use_annotations=True) 252 | # ... 253 | 254 | * The following way to declare Module provider methods is introduced and 255 | recommended now: 256 | 257 | .. code-block:: python 258 | 259 | class MyModule(Module): 260 | @provider 261 | def provide_something(self, dependency: Dependency) -> Something: 262 | # ... 263 | 264 | @provider implies @inject. 265 | 266 | Previously it would look like this: 267 | 268 | .. code-block:: python 269 | 270 | class MyModule(Module): 271 | @provides(Something) 272 | @inject 273 | def provide_something(self, dependency: Dependency): 274 | # ... 275 | 276 | The :func:`~injector.provides` decorator will be removed in the future. 277 | 278 | * Added a :func:`~injector.noninjectable` decorator to mark parameters as not injectable 279 | (this serves as documentation and a way to avoid some runtime errors) 280 | 281 | 282 | Backwards incompatible: 283 | 284 | * Removed support for decorating classes with :func:`@inject `. Previously: 285 | 286 | .. code-block:: python 287 | 288 | @inject(something=Something) 289 | class Class: 290 | pass 291 | 292 | Now: 293 | 294 | .. code-block:: python 295 | 296 | class Class: 297 | @inject 298 | def __init__(self, something: Something): 299 | self.something = something 300 | 301 | * Removed support for injecting partially applied functions, previously: 302 | 303 | .. code-block:: python 304 | 305 | @inject(something=Something) 306 | def some_function(something): 307 | pass 308 | 309 | 310 | class Class: 311 | @inject(function=some_function) 312 | def __init__(self, function): 313 | # ... 314 | 315 | Now you need to move the function with injectable dependencies to a class. 316 | 317 | * Removed support for getting :class:`AssistedBuilder(callable=...) ` 318 | * Dropped Python 2.6 support 319 | * Changed the way :class:`~injector.AssistedBuilder` and :class:`~injector.ProviderOf` are used. 320 | Previously: 321 | 322 | .. code-block:: python 323 | 324 | builder1 = injector.get(AssistedBuilder(Something)) 325 | # or: builder1 = injector.get(AssistedBuilder(interface=Something)) 326 | builder2 = injector.get(AssistedBuilder(cls=SomethingElse)) 327 | provider = injector.get(ProviderOf(SomeOtherThing)) 328 | 329 | Now: 330 | 331 | .. code-block:: python 332 | 333 | builder1 = injector.get(AssistedBuilder[Something]) 334 | builder2 = injector.get(ClassAssistedBuilder[cls=SomethingElse]) 335 | provider = injector.get(ProviderOf[SomeOtherThing]) 336 | 337 | * Removed support for injecting into non-constructor methods 338 | 339 | 0.10.1 340 | ------ 341 | 342 | - Fixed a false positive bug in dependency cycle detection (AssistedBuilder can be 343 | used to break dependency cycles now) 344 | 345 | 0.10.0 346 | ------ 347 | 348 | - :meth:`injector.Provider.get()` now requires an :class:`injector.Injector` instance as 349 | its parameter 350 | - deprecated injecting arguments into modules (be it functions/callables, 351 | :class:`~injector.Module` constructors or :meth:`injector.Module.configure` methods) 352 | - removed `extends` decorator 353 | - few classes got useful __repr__ implementations 354 | - fixed injecting ProviderOf and AssistedBuilders when :class:`injector.Injector` 355 | auto_bind is set to False (previously would result in `UnsatisfiedRequirement` 356 | error) 357 | - fixed crash occurring when Python 3-function annotation use is enabled and 358 | __init__ method has a return value annotation ("injector.UnknownProvider: 359 | couldn't determine provider for None to None"), should also apply to free 360 | functions as well 361 | 362 | 0.9.1 363 | ----- 364 | - Bug fix release. 365 | 366 | 0.9.0 367 | ----- 368 | 369 | - Child :class:`~injector.Injector` can rebind dependancies bound in parent Injector (that changes :class:`~injector.Provider` semantics), thanks to Ilya Orlov 370 | - :class:`~injector.CallableProvider` callables can be injected into, thanks to Ilya Strukov 371 | - 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 372 | 373 | 0.8.0 374 | ----- 375 | 376 | - Binding annotations are removed. Use :func:`~injector.Key` to create unique types instead. 377 | 378 | 379 | 0.7.9 380 | ----- 381 | 382 | - Fixed regression with injecting unbound key resulting in None instead of raising an exception 383 | 384 | 385 | 0.7.8 386 | ----- 387 | 388 | - Exception is raised when :class:`~injector.Injector` can't install itself into a class instance due to __slots__ presence 389 | - Some of exception messages are now more detailed to make debugging easier when injection fails 390 | - You can inject functions now - :class:`~injector.Injector` provides a wrapper that takes care of injecting dependencies into the original function 391 | 392 | 0.7.7 393 | ----- 394 | 395 | - 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)`` 396 | 397 | 0.7.6 398 | ----- 399 | 400 | - Auto-convert README.md to RST for PyPi. 401 | 402 | 0.7.5 403 | ----- 404 | 405 | - Added a ChangeLog! 406 | - Added support for using Python3 annotations as binding types. 407 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Alec Thomas, Google Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | - Neither the name of SwapOff.org nor the names of its contributors may 13 | be used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.py 2 | include *.toml 3 | include requirements-*.in 4 | include *.txt 5 | include CHANGES 6 | include COPYING 7 | include README.md 8 | include mypy.ini 9 | include pytest.ini 10 | recursive-include docs *.html 11 | recursive-include docs *.py 12 | recursive-include docs *.rst 13 | recursive-include docs Makefile 14 | exclude .readthedocs.yaml 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Injector - Python dependency injection framework, inspired by Guice 2 | =================================================================== 3 | 4 | [![image](https://github.com/alecthomas/injector/workflows/CI/badge.svg)](https://github.com/alecthomas/injector/actions?query=workflow%3ACI+branch%3Amaster) 5 | [![Coverage Status](https://codecov.io/gh/alecthomas/injector/branch/master/graph/badge.svg)](https://codecov.io/gh/alecthomas/injector) 6 | 7 | Introduction 8 | ------------ 9 | 10 | While dependency injection is easy to do in Python due to its support for keyword arguments, the ease with which objects can be mocked and its dynamic nature, a framework for assisting in this process can remove a lot of boiler-plate from larger applications. That's where Injector can help. It automatically and transitively provides dependencies for you. As an added benefit, Injector encourages nicely compartmentalised code through the use of modules. 11 | 12 | If you're not sure what dependency injection is or you'd like to learn more about it see: 13 | 14 | * [The Clean Code Talks - Don't Look For Things! (a talk by Miško Hevery)]( 15 | https://www.youtube.com/watch?v=RlfLCWKxHJ0) 16 | * [Inversion of Control Containers and the Dependency Injection pattern (an article by Martin Fowler)]( 17 | https://martinfowler.com/articles/injection.html) 18 | 19 | The core values of Injector are: 20 | 21 | * Simplicity - while being inspired by Guice, Injector does not slavishly replicate its API. 22 | Providing a Pythonic API trumps faithfulness. Additionally some features are omitted 23 | because supporting them would be cumbersome and introduce a little bit too much "magic" 24 | (member injection, method injection). 25 | 26 | Connected to this, Injector tries to be as nonintrusive as possible. For example while you may 27 | declare a class' constructor to expect some injectable parameters, the class' constructor 28 | remains a standard constructor – you may instantiate the class just the same manually, if you want. 29 | 30 | * No global state – you can have as many [Injector](https://injector.readthedocs.io/en/latest/api.html#injector.Injector) 31 | instances as you like, each with a different configuration and each with different objects in different 32 | scopes. Code like this won't work for this very reason: 33 | 34 | ```python 35 | class MyClass: 36 | @inject 37 | def __init__(t: SomeType): 38 | # ... 39 | 40 | MyClass() 41 | ``` 42 | 43 | This is simply because there's no global `Injector` to use. You need to be explicit and use 44 | [Injector.get](https://injector.readthedocs.io/en/latest/api.html#injector.Injector.get), 45 | [Injector.create_object](https://injector.readthedocs.io/en/latest/api.html#injector.Injector.create_object) 46 | or inject `MyClass` into the place that needs it. 47 | 48 | * Cooperation with static type checking infrastructure – the API provides as much static type safety 49 | as possible and only breaks it where there's no other option. For example the 50 | [Injector.get](https://injector.readthedocs.io/en/latest/api.html#injector.Injector.get) method 51 | is typed such that `injector.get(SomeType)` is statically declared to return an instance of 52 | `SomeType`, therefore making it possible for tools such as [mypy](https://github.com/python/mypy) to 53 | type-check correctly the code using it. 54 | 55 | * The client code only knows about dependency injection to the extent it needs –  56 | [`inject`](https://injector.readthedocs.io/en/latest/api.html#injector.inject), 57 | [`Inject`](https://injector.readthedocs.io/en/latest/api.html#injector.Inject) and 58 | [`NoInject`](https://injector.readthedocs.io/en/latest/api.html#injector.NoInject) are simple markers 59 | that don't really do anything on their own and your code can run just fine without Injector 60 | orchestrating things. 61 | 62 | ### How to get Injector? 63 | 64 | * GitHub (code repository, issues): https://github.com/alecthomas/injector 65 | 66 | * PyPI (installable, stable distributions): https://pypi.org/project/injector/. You can install it using pip: 67 | 68 | ```bash 69 | pip install injector 70 | ``` 71 | 72 | * Documentation: https://injector.readthedocs.org 73 | * Change log: https://injector.readthedocs.io/en/latest/changelog.html 74 | 75 | Injector works with CPython 3.8+ and PyPy 3 implementing Python 3.8+. 76 | 77 | A Quick Example 78 | --------------- 79 | 80 | 81 | ```python 82 | >>> from injector import Injector, inject 83 | >>> class Inner: 84 | ... def __init__(self): 85 | ... self.forty_two = 42 86 | ... 87 | >>> class Outer: 88 | ... @inject 89 | ... def __init__(self, inner: Inner): 90 | ... self.inner = inner 91 | ... 92 | >>> injector = Injector() 93 | >>> outer = injector.get(Outer) 94 | >>> outer.inner.forty_two 95 | 42 96 | 97 | ``` 98 | 99 | Or with `dataclasses` if you like: 100 | 101 | ```python 102 | from dataclasses import dataclass 103 | from injector import Injector, inject 104 | class Inner: 105 | def __init__(self): 106 | self.forty_two = 42 107 | 108 | @inject 109 | @dataclass 110 | class Outer: 111 | inner: Inner 112 | 113 | injector = Injector() 114 | outer = injector.get(Outer) 115 | print(outer.inner.forty_two) # Prints 42 116 | ``` 117 | 118 | 119 | A Full Example 120 | -------------- 121 | 122 | Here's a full example to give you a taste of how Injector works: 123 | 124 | 125 | ```python 126 | >>> from injector import Module, provider, Injector, inject, singleton 127 | 128 | ``` 129 | 130 | We'll use an in-memory SQLite database for our example: 131 | 132 | 133 | ```python 134 | >>> import sqlite3 135 | 136 | ``` 137 | 138 | And make up an imaginary `RequestHandler` class that uses the SQLite connection: 139 | 140 | 141 | ```python 142 | >>> class RequestHandler: 143 | ... @inject 144 | ... def __init__(self, db: sqlite3.Connection): 145 | ... self._db = db 146 | ... 147 | ... def get(self): 148 | ... cursor = self._db.cursor() 149 | ... cursor.execute('SELECT key, value FROM data ORDER by key') 150 | ... return cursor.fetchall() 151 | 152 | ``` 153 | 154 | Next, for the sake of the example, we'll create a configuration type: 155 | 156 | 157 | ```python 158 | >>> class Configuration: 159 | ... def __init__(self, connection_string): 160 | ... self.connection_string = connection_string 161 | 162 | ``` 163 | 164 | Next, we bind the configuration to the injector, using a module: 165 | 166 | 167 | ```python 168 | >>> def configure_for_testing(binder): 169 | ... configuration = Configuration(':memory:') 170 | ... binder.bind(Configuration, to=configuration, scope=singleton) 171 | 172 | ``` 173 | 174 | Next we create a module that initialises the DB. It depends on the configuration provided by the above module to create a new DB connection, then populates it with some dummy data, and provides a `Connection` object: 175 | 176 | 177 | ```python 178 | >>> class DatabaseModule(Module): 179 | ... @singleton 180 | ... @provider 181 | ... def provide_sqlite_connection(self, configuration: Configuration) -> sqlite3.Connection: 182 | ... conn = sqlite3.connect(configuration.connection_string) 183 | ... cursor = conn.cursor() 184 | ... cursor.execute('CREATE TABLE IF NOT EXISTS data (key PRIMARY KEY, value)') 185 | ... cursor.execute('INSERT OR REPLACE INTO data VALUES ("hello", "world")') 186 | ... return conn 187 | 188 | ``` 189 | 190 | (Note how we have decoupled configuration from our database initialisation code.) 191 | 192 | Finally, we initialise an `Injector` and use it to instantiate a `RequestHandler` instance. This first transitively constructs a `sqlite3.Connection` object, and the Configuration dictionary that it in turn requires, then instantiates our `RequestHandler`: 193 | 194 | 195 | ```python 196 | >>> injector = Injector([configure_for_testing, DatabaseModule()]) 197 | >>> handler = injector.get(RequestHandler) 198 | >>> tuple(map(str, handler.get()[0])) # py3/py2 compatibility hack 199 | ('hello', 'world') 200 | 201 | ``` 202 | 203 | We can also verify that our `Configuration` and `SQLite` connections are indeed singletons within the Injector: 204 | 205 | 206 | ```python 207 | >>> injector.get(Configuration) is injector.get(Configuration) 208 | True 209 | >>> injector.get(sqlite3.Connection) is injector.get(sqlite3.Connection) 210 | True 211 | 212 | ``` 213 | 214 | You're probably thinking something like: "this is a large amount of work just to give me a database connection", and you are correct; dependency injection is typically not that useful for smaller projects. It comes into its own on large projects where the up-front effort pays for itself in two ways: 215 | 216 | 1. Forces decoupling. In our example, this is illustrated by decoupling our configuration and database configuration. 217 | 2. After a type is configured, it can be injected anywhere with no additional effort. Simply `@inject` and it appears. We don't really illustrate that here, but you can imagine adding an arbitrary number of `RequestHandler` subclasses, all of which will automatically have a DB connection provided. 218 | 219 | Footnote 220 | -------- 221 | 222 | This framework is similar to snake-guice, but aims for simplification. 223 | 224 | © Copyright 2010-2013 to Alec Thomas, under the BSD license 225 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Injector.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Injector.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Injector" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Injector" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_templates/sidebar.html: -------------------------------------------------------------------------------- 1 |

Injector

2 |

Dependency Injection Framework

3 | 8 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | Injector API reference 2 | ====================== 3 | 4 | .. note:: 5 | 6 | Unless specified otherwise, instance methods are **not** thread safe. 7 | 8 | The following functions are thread safe: 9 | 10 | * :meth:`Injector.get` 11 | * injection provided by :func:`inject` decorator (please note, however, that it doesn't say anything about decorated function thread safety) 12 | 13 | 14 | .. automodule:: injector 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGES 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Injector documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Sep 16 02:58:17 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import injector 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | # needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | # source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'Injector' 44 | copyright = u'2013, Alec Thomas' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = injector.__version__ 52 | # The full version, including alpha/beta/rc tags. 53 | release = version 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | # language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | # today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | # today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | # default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | # add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | # add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | # show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | # modindex_common_prefix = [] 88 | 89 | # If true, keep warnings as "system message" paragraphs in the built documents. 90 | # keep_warnings = False 91 | 92 | 93 | # -- Options for HTML output --------------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. See the documentation for 96 | # a list of builtin themes. 97 | html_theme = 'furo' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | # html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | # html_theme_path = [] 106 | 107 | # The name for this set of Sphinx documents. If None, it defaults to 108 | # " v documentation". 109 | # html_title = None 110 | 111 | # A shorter title for the navigation bar. Default is the same as html_title. 112 | # html_short_title = None 113 | 114 | # The name of an image file (relative to this directory) to place at the top 115 | # of the sidebar. 116 | # html_logo = None 117 | 118 | # The name of an image file (within the static path) to use as favicon of the 119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | # html_favicon = None 122 | 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = ['_static'] 127 | 128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 129 | # using the given strftime format. 130 | # html_last_updated_fmt = '%b %d, %Y' 131 | 132 | # If true, SmartyPants will be used to convert quotes and dashes to 133 | # typographically correct entities. 134 | # html_use_smartypants = True 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | # html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | # html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | # html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | # html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | # html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | # html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | # html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | # html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | # html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'Injectordoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | # The font size ('10pt', '11pt' or '12pt'). 176 | #'pointsize': '10pt', 177 | # Additional stuff for the LaTeX preamble. 178 | #'preamble': '', 179 | } 180 | 181 | # Grouping the document tree into LaTeX files. List of tuples 182 | # (source start file, target name, title, author, documentclass [howto/manual]). 183 | latex_documents = [('index', 'Injector.tex', u'Injector Documentation', u'Alec Thomas', 'manual')] 184 | 185 | # The name of an image file (relative to this directory) to place at the top of 186 | # the title page. 187 | # latex_logo = None 188 | 189 | # For "manual" documents, if this is true, then toplevel headings are parts, 190 | # not chapters. 191 | # latex_use_parts = False 192 | 193 | # If true, show page references after internal links. 194 | # latex_show_pagerefs = False 195 | 196 | # If true, show URL addresses after external links. 197 | # latex_show_urls = False 198 | 199 | # Documents to append as an appendix to all manuals. 200 | # latex_appendices = [] 201 | 202 | # If false, no module index is generated. 203 | # latex_domain_indices = True 204 | 205 | 206 | # -- Options for manual page output -------------------------------------------- 207 | 208 | # One entry per manual page. List of tuples 209 | # (source start file, name, description, authors, manual section). 210 | man_pages = [('index', 'injector', u'Injector Documentation', [u'Alec Thomas'], 1)] 211 | 212 | # If true, show URL addresses after external links. 213 | # man_show_urls = False 214 | 215 | 216 | # -- Options for Texinfo output ------------------------------------------------ 217 | 218 | # Grouping the document tree into Texinfo files. List of tuples 219 | # (source start file, target name, title, author, 220 | # dir menu entry, description, category) 221 | texinfo_documents = [ 222 | ( 223 | 'index', 224 | 'Injector', 225 | u'Injector Documentation', 226 | u'Alec Thomas', 227 | 'Injector', 228 | 'One line description of project.', 229 | 'Miscellaneous', 230 | ) 231 | ] 232 | 233 | # Documents to append as an appendix to all manuals. 234 | # texinfo_appendices = [] 235 | 236 | # If false, no module index is generated. 237 | # texinfo_domain_indices = True 238 | 239 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 240 | # texinfo_show_urls = 'footnote' 241 | 242 | # If true, do not generate a @detailmenu in the "Top" node's menu. 243 | # texinfo_no_detailmenu = False 244 | 245 | 246 | # Example configuration for intersphinx: refer to the Python standard library. 247 | intersphinx_mapping = {'http://docs.python.org/': None} 248 | 249 | 250 | def setup(app): 251 | app.connect('autodoc-skip-member', skip_member) 252 | 253 | 254 | def skip_member(app, what, name, obj, skip, options): 255 | return ( 256 | skip 257 | or getattr(obj, '__doc__', None) is None 258 | or getattr(obj, '__private__', False) is True 259 | or getattr(getattr(obj, '__func__', None), '__private__', False) is True 260 | ) 261 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | .. _faq: 2 | 3 | Frequently Asked Questions 4 | ========================== 5 | 6 | If I use :func:`~injector.inject` or scope decorators on my classess will I be able to create instances of them without using Injector? 7 | --------------------------------------------------------------------------------------------------------------------------------------- 8 | 9 | Yes. Scope decorators don't change the way you can construct your class 10 | instances without Injector interaction. 11 | 12 | I'm calling this method (/function/class) but I'm getting "TypeError: XXX() takes exactly X arguments (Y given)" 13 | ---------------------------------------------------------------------------------------------------------------- 14 | 15 | Example code: 16 | 17 | .. code-block:: python 18 | 19 | class X: 20 | @inject 21 | def __init__(self, s: str): 22 | self.s = s 23 | 24 | def configure(binder): 25 | binder.bind(s, to='some string') 26 | 27 | injector = Injector(configure) 28 | x = X() 29 | 30 | Result? 31 | 32 | :: 33 | 34 | TypeError: __init__() takes exactly 2 arguments (1 given) 35 | 36 | Reason? There's *no* global state that :class:`Injector` modifies when 37 | it's instantiated and configured. Its whole knowledge about bindings etc. 38 | is stored in itself. Moreover :func:`inject` will *not* make 39 | dependencies appear out of thin air when you for example attempt to create 40 | an instance of a class manually (without ``Injector``'s help) - there's no 41 | global state ``@inject`` decorated methods can access. 42 | 43 | In order for ``X`` to be able to use bindings defined in ``@inject`` 44 | decoration :class:`Injector` needs to be used (directly or indirectly) 45 | to create an instance of ``X``. This means most of the time you want to just 46 | inject ``X`` where you need it, you can also use :meth:`Injector.get` to obtain 47 | an instance of the class (see its documentation for usage notes). 48 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Injector documentation master file, created by 2 | sphinx-quickstart on Mon Sep 16 02:58:17 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Injector's documentation! 7 | ==================================== 8 | 9 | .. image:: https://github.com/alecthomas/injector/workflows/CI/badge.svg 10 | :alt: Build status 11 | :target: https://github.com/alecthomas/injector/actions?query=workflow%3ACI+branch%3Amaster 12 | 13 | .. image:: https://codecov.io/gh/alecthomas/injector/branch/master/graph/badge.svg 14 | :alt: Covergage status 15 | :target: https://codecov.io/gh/alecthomas/injector 16 | 17 | 18 | GitHub (code repository, issues): https://github.com/alecthomas/injector 19 | 20 | PyPI (installable, stable distributions): https://pypi.org/project/injector. You can install Injector using pip:: 21 | 22 | pip install injector 23 | 24 | Injector works with CPython 3.6+ and PyPy 3 implementing Python 3.6+. 25 | 26 | Introduction 27 | ------------ 28 | 29 | While dependency injection is easy to do in Python due to its support for keyword arguments, the ease with which objects can be mocked and its dynamic natura, a framework for assisting in this process can remove a lot of boiler-plate from larger applications. That's where Injector can help. It automatically and transitively provides dependencies for you. As an added benefit, Injector encourages nicely compartmentalised code through the use of :ref:`modules `. 30 | 31 | If you're not sure what dependency injection is or you'd like to learn more about it see: 32 | 33 | * `The Clean Code Talks - Don't Look For Things! (a talk by Miško Hevery) 34 | `_ 35 | * `Inversion of Control Containers and the Dependency Injection pattern (an article by Martin Fowler) 36 | `_ 37 | 38 | The core values of Injector are: 39 | 40 | * Simplicity - while being inspired by Guice, Injector does not slavishly replicate its API. 41 | Providing a Pythonic API trumps faithfulness. Additionally some features are ommitted 42 | because supporting them would be cumbersome and introduce a little bit too much "magic" 43 | (member injection, method injection). 44 | 45 | Connected to this, Injector tries to be as nonintrusive as possible. For example while you may 46 | declare a class' constructor to expect some injectable parameters, the class' constructor 47 | remains a standard constructor – you may instaniate the class just the same manually, if you want. 48 | 49 | * No global state – you can have as many :class:`Injector` instances as you like, each with 50 | a different configuration and each with different objects in different scopes. Code like this 51 | won't work for this very reason:: 52 | 53 | # This will NOT work: 54 | 55 | class MyClass: 56 | @inject 57 | def __init__(self, t: SomeType): 58 | # ... 59 | 60 | MyClass() 61 | 62 | This is simply because there's no global :class:`Injector` to use. You need to be explicit and use 63 | :meth:`Injector.get `, 64 | :meth:`Injector.create_object ` or inject `MyClass` into the place 65 | that needs it. 66 | 67 | * Cooperation with static type checking infrastructure – the API provides as much static type safety 68 | as possible and only breaks it where there's no other option. For example the 69 | :meth:`Injector.get ` method is typed such that `injector.get(SomeType)` 70 | is statically declared to return an instance of `SomeType`, therefore making it possible for tools 71 | such as `mypy `_ to type-check correctly the code using it. 72 | 73 | Quick start 74 | ----------- 75 | 76 | See `the project's README `_ for an 77 | example of Injector use. 78 | 79 | Contents 80 | -------- 81 | 82 | .. toctree:: 83 | :maxdepth: 1 84 | 85 | changelog 86 | terminology 87 | testing 88 | scopes 89 | logging 90 | api 91 | faq 92 | practices 93 | -------------------------------------------------------------------------------- /docs/logging.rst: -------------------------------------------------------------------------------- 1 | Logging 2 | ======= 3 | 4 | Injector uses standard :mod:`logging` module, the logger name is ``injector``. 5 | 6 | By default ``injector`` logger is not configured to print logs anywhere. 7 | 8 | To enable ``get()`` tracing (and some other useful information) you need to set ``injector`` logger level to ``DEBUG``. You can do that by executing:: 9 | 10 | import logging 11 | 12 | logging.getLogger('injector').setLevel(logging.DEBUG) 13 | 14 | or by configuring :mod:`logging` module in any other way. 15 | -------------------------------------------------------------------------------- /docs/practices.rst: -------------------------------------------------------------------------------- 1 | .. _practices: 2 | 3 | Good and bad practices 4 | ====================== 5 | 6 | Side effects 7 | ```````````` 8 | 9 | You should avoid creating side effects in your modules for two reasons: 10 | 11 | * Side effects will make it more difficult to test a module if you want to do it 12 | * Modules expose a way to acquire some resource but they don't provide any way 13 | to release it. If, for example, your module connects to a remote server while 14 | creating a service you have no way of closing that connection unless the 15 | service exposes it. 16 | 17 | 18 | Injecting into constructors vs injecting into other methods 19 | ``````````````````````````````````````````````````````````` 20 | 21 | .. note:: 22 | 23 | Injector 0.11+ doesn't support injecting into non-constructor methods, 24 | this section is kept for historical reasons. 25 | 26 | .. note:: 27 | 28 | Injector 0.11 deprecates using @inject with keyword arguments to declare 29 | bindings, this section remains unchanged for historical reasons. 30 | 31 | In general you should prefer injecting into constructors to injecting into 32 | other methods because: 33 | 34 | * it can expose potential issues earlier (at object construction time rather 35 | than at the method call) 36 | * it exposes class' dependencies more openly. Constructor injection: 37 | 38 | .. code-block:: python 39 | 40 | class Service1(object): 41 | @inject(http_client=HTTP) 42 | def __init__(self, http_client): 43 | self.http_client = http_client 44 | # some other code 45 | 46 | # tens or hundreds lines of code 47 | 48 | def method(self): 49 | # do something 50 | pass 51 | 52 | Regular method injection: 53 | 54 | .. code-block:: python 55 | 56 | class Service2(object): 57 | def __init__(self): 58 | # some other code 59 | 60 | # tens or hundreds lines of code 61 | 62 | @inject(http_client=HTTP) 63 | def method(self, http_client): 64 | # do something 65 | pass 66 | 67 | 68 | In first case you know all the dependencies by looking at the class' 69 | constructor, in the second you don't know about ``HTTP`` dependency until 70 | you see the method definition. 71 | 72 | Slightly different approach is suggested when it comes to Injector modules - 73 | in this case injecting into their constructors (or ``configure`` methods) 74 | would make the injection process dependent on the order of passing modules 75 | to Injector and therefore quite fragile. See this code sample: 76 | 77 | .. code-block:: python 78 | 79 | A = Key('A') 80 | B = Key('B') 81 | 82 | class ModuleA(Module): 83 | @inject(a=A) 84 | def configure(self, binder, a): 85 | pass 86 | 87 | class ModuleB(Module): 88 | @inject(b=B) 89 | def __init__(self, b): 90 | pass 91 | 92 | class ModuleC(Module): 93 | def configure(self, binder): 94 | binder.bind(A, to='a') 95 | binder.bind(B, to='b') 96 | 97 | 98 | # error, at the time of ModuleA processing A is unbound 99 | Injector([ModuleA, ModuleC]) 100 | 101 | # error, at the time of ModuleB processing B is unbound 102 | Injector([ModuleB, ModuleC]) 103 | 104 | # no error this time 105 | Injector([ModuleC, ModuleA, ModuleB]) 106 | 107 | 108 | Doing too much in modules and/or providers 109 | `````````````````````````````````````````` 110 | 111 | An implementation detail of Injector: Injector and accompanying classes are 112 | protected by a lock to make them thread safe. This has a downside though: 113 | in general only one thread can use dependency injection at any given moment. 114 | 115 | In best case scenario you "only" slow other threads' dependency injection 116 | down. In worst case scenario (performing blocking calls without timeouts) you 117 | can **deadlock** whole application. 118 | 119 | **It is advised to avoid performing any IO, particularly without a timeout 120 | set, inside modules code.** 121 | 122 | As an illustration: 123 | 124 | .. code-block:: python 125 | 126 | from threading import Thread 127 | from time import sleep 128 | 129 | from injector import inject, Injector, Module, provider 130 | 131 | class A: pass 132 | class SubA(A): pass 133 | class B: pass 134 | 135 | 136 | class BadModule(Module): 137 | @provider 138 | def provide_a(self, suba: SubA) -> A: 139 | return suba 140 | 141 | @provider 142 | def provide_suba(self) -> SubA: 143 | print('Providing SubA...') 144 | while True: 145 | print('Sleeping...') 146 | sleep(1) 147 | 148 | # This never executes 149 | return SubA() 150 | 151 | @provider 152 | def provide_b(self) -> B: 153 | return B() 154 | 155 | 156 | injector = Injector([BadModule]) 157 | 158 | thread = Thread(target=lambda: injector.get(A)) 159 | 160 | # to make sure the thread doesn't keep the application alive 161 | thread.daemon = True 162 | thread.start() 163 | 164 | # This will never finish 165 | injector.get(B) 166 | print('Got B') 167 | 168 | 169 | Here's the output of the application:: 170 | 171 | Providing SubA... 172 | Sleeping... 173 | Sleeping... 174 | Sleeping... 175 | (...) 176 | 177 | 178 | Injecting Injector and abusing Injector.get 179 | ``````````````````````````````````````````` 180 | 181 | Sometimes code like this is written: 182 | 183 | .. code-block:: python 184 | 185 | class A: 186 | pass 187 | 188 | class B: 189 | pass 190 | 191 | class C: 192 | @inject 193 | def __init__(self, injector: Injector): 194 | self.a = injector.get(A) 195 | self.b = injector.get(B) 196 | 197 | 198 | It is advised to use the following pattern instead: 199 | 200 | .. code-block:: python 201 | 202 | class A: 203 | pass 204 | 205 | class B: 206 | pass 207 | 208 | class C: 209 | @inject 210 | def __init__(self, a: A, b: B): 211 | self.a = a 212 | self.b = b 213 | 214 | 215 | The second form has the benefits of: 216 | 217 | * expressing clearly what the dependencies of ``C`` are 218 | * making testing of the ``C`` class easier - you can provide the dependencies 219 | (whether they are mocks or not) directly, instead of having to mock 220 | :class:`Injector` and make the mock handle :meth:`Injector.get` calls 221 | * following the common practice and being easier to understand 222 | 223 | 224 | Injecting dependencies only to pass them somewhere else 225 | ``````````````````````````````````````````````````````` 226 | 227 | A pattern similar to the one below can emerge: 228 | 229 | .. code-block:: python 230 | 231 | class A: 232 | pass 233 | 234 | class B: 235 | def __init__(self, a): 236 | self.a = a 237 | 238 | class C: 239 | @inject 240 | def __init__(self, a: A): 241 | self.b = B(a) 242 | 243 | Class ``C`` in this example has the responsibility of gathering dependencies of 244 | class ``B`` and constructing an object of type ``B``, there may be a valid reason 245 | for it but in general it defeats the purpose of using ``Injector`` and should 246 | be avoided. 247 | 248 | The appropriate pattern is: 249 | 250 | .. code-block:: python 251 | 252 | class A: 253 | pass 254 | 255 | class B: 256 | @inject 257 | def __init__(self, a: A): 258 | self.a = a 259 | 260 | class C: 261 | @inject 262 | def __init__(self, b: B): 263 | self.b = b 264 | -------------------------------------------------------------------------------- /docs/scopes.rst: -------------------------------------------------------------------------------- 1 | .. _scopes: 2 | 3 | Scopes 4 | ====== 5 | 6 | Singletons 7 | `````````` 8 | 9 | Singletons are declared by binding them in the SingletonScope. This can be done in three ways: 10 | 11 | 1. Decorating the class with `@singleton`. 12 | 2. Decorating a `@provider` decorated Module method with `@singleton`. 13 | 3. Explicitly calling `binder.bind(X, scope=singleton)`. 14 | 15 | A (redundant) example showing all three methods:: 16 | 17 | @singleton 18 | class Thing: pass 19 | class ThingModule(Module): 20 | def configure(self, binder): 21 | binder.bind(Thing, scope=singleton) 22 | @singleton 23 | @provider 24 | def provide_thing(self) -> Thing: 25 | return Thing() 26 | 27 | If using hierarchies of injectors, classes decorated with `@singleton` will be created by and bound to the parent/ancestor injector closest to the root that can provide all of its dependencies. 28 | 29 | Implementing new Scopes 30 | ``````````````````````` 31 | 32 | In the above description of scopes, we glossed over a lot of detail. In particular, how one would go about implementing our own scopes. 33 | 34 | Basically, there are two steps. First, subclass `Scope` and implement `Scope.get`:: 35 | 36 | from injector import Scope 37 | class CustomScope(Scope): 38 | def get(self, key, provider): 39 | return provider 40 | 41 | Then create a global instance of :class:`ScopeDecorator` to allow classes to be easily annotated with your scope:: 42 | 43 | from injector import ScopeDecorator 44 | customscope = ScopeDecorator(CustomScope) 45 | 46 | This can be used like so:: 47 | 48 | @customscope 49 | class MyClass: 50 | pass 51 | 52 | Scopes are bound in modules with the :meth:`Binder.install` method:: 53 | 54 | class MyModule(Module): 55 | def configure(self, binder): 56 | binder.install(CustomScope) 57 | 58 | Scopes can be retrieved from the injector, as with any other instance. They are singletons across the life of the injector:: 59 | 60 | >>> injector = Injector([MyModule()]) 61 | >>> injector.get(CustomScope) is injector.get(CustomScope) 62 | True 63 | 64 | For scopes with a transient lifetime, such as those tied to HTTP requests, the usual solution is to use a thread or greenlet-local cache inside the scope. The scope is "entered" in some low-level code by calling a method on the scope instance that creates this cache. Once the request is complete, the scope is "left" and the cache cleared. 65 | -------------------------------------------------------------------------------- /docs/terminology.rst: -------------------------------------------------------------------------------- 1 | Terminology 2 | =========== 3 | 4 | At its heart, Injector is simply a dictionary for mapping types to things that create instances of those types. This could be as simple as:: 5 | 6 | {str: 'an instance of a string'} 7 | 8 | For those new to dependency-injection and/or Guice, though, some of the terminology used may not be obvious. 9 | 10 | Provider 11 | ```````` 12 | 13 | A means of providing an instance of a type. Built-in providers include: 14 | 15 | * :class:`~injector.ClassProvider` - creates a new instance from a class 16 | * :class:`~injector.InstanceProvider` - returns an existing instance directly 17 | * :class:`~injector.CallableProvider` - provides an instance by calling a function 18 | 19 | In order to create custom provider you need to subclass :class:`~injector.Provider` and override its :meth:`~injector.Provider.get` method. 20 | 21 | Scope 22 | ````` 23 | 24 | By default, providers are executed each time an instance is required. Scopes allow this behaviour to be customised. For example, `SingletonScope` (typically used through the class decorator `singleton`), can be used to always provide the same instance of a class. 25 | 26 | Other examples of where scopes might be a threading scope, where instances are provided per-thread, or a request scope, where instances are provided per-HTTP-request. 27 | 28 | The default scope is :class:`NoScope`. 29 | 30 | .. seealso:: :ref:`scopes` 31 | 32 | Binding 33 | ``````` 34 | 35 | A binding is the mapping of a unique binding key to a corresponding provider. For example:: 36 | 37 | >>> from injector import InstanceProvider 38 | >>> bindings = { 39 | ... (Name, None): InstanceProvider('Sherlock'), 40 | ... (Description, None): InstanceProvider('A man of astounding insight'), 41 | ... } 42 | 43 | 44 | Binder 45 | `````` 46 | 47 | The `Binder` is simply a convenient wrapper around the dictionary that maps types to providers. It provides methods that make declaring bindings easier. 48 | 49 | 50 | .. _module: 51 | 52 | Module 53 | `````` 54 | 55 | A `Module` configures bindings. It provides methods that simplify the process of binding a key to a provider. For example the above bindings would be created with:: 56 | 57 | >>> from injector import Module 58 | >>> class MyModule(Module): 59 | ... def configure(self, binder): 60 | ... binder.bind(Name, to='Sherlock') 61 | ... binder.bind(Description, to='A man of astounding insight') 62 | 63 | For more complex instance construction, methods decorated with `@provider` will be called to resolve binding keys:: 64 | 65 | >>> from injector import provider 66 | >>> class MyModule(Module): 67 | ... def configure(self, binder): 68 | ... binder.bind(Name, to='Sherlock') 69 | ... 70 | ... @provider 71 | ... def describe(self) -> Description: 72 | ... return 'A man of astounding insight (at %s)' % time.time() 73 | 74 | Injection 75 | ````````` 76 | 77 | Injection is the process of providing an instance of a type, to a method that uses that instance. It is achieved with the `inject` decorator. Keyword arguments to inject define which arguments in its decorated method should be injected, and with what. 78 | 79 | Here is an example of injection on a module provider method, and on the constructor of a normal class:: 80 | 81 | from typing import NewType 82 | 83 | from injector import Binder, Module, inject, provider 84 | 85 | Name = NewType("Name", str) 86 | Description = NewType("Description", str) 87 | 88 | class User: 89 | @inject 90 | def __init__(self, name: Name, description: Description): 91 | self.name = name 92 | self.description = description 93 | 94 | class UserModule(Module): 95 | def configure(self, binder: Binder): 96 | binder.bind(User) 97 | 98 | class UserAttributeModule(Module): 99 | def configure(self, binder: Binder): 100 | binder.bind(Name, to='Sherlock') 101 | 102 | @provider 103 | def describe(self, name: Name) -> Description: 104 | return '%s is a man of astounding insight' % name 105 | 106 | 107 | Injector 108 | ```````` 109 | 110 | The `Injector` brings everything together. It takes a list of `Module` s, and configures them with a binder, effectively creating a dependency graph:: 111 | 112 | from injector import Injector 113 | injector = Injector([UserModule(), UserAttributeModule()]) 114 | 115 | You can also pass classes instead of instances to `Injector`, it will instantiate them for you:: 116 | 117 | injector = Injector([UserModule, UserAttributeModule]) 118 | 119 | The injector can then be used to acquire instances of a type, either directly:: 120 | 121 | >>> injector.get(Name) 122 | 'Sherlock' 123 | >>> injector.get(Description) 124 | 'Sherlock is a man of astounding insight' 125 | 126 | Or transitively:: 127 | 128 | >>> user = injector.get(User) 129 | >>> isinstance(user, User) 130 | True 131 | >>> user.name 132 | 'Sherlock' 133 | >>> user.description 134 | 'Sherlock is a man of astounding insight' 135 | 136 | Assisted injection 137 | `````````````````` 138 | 139 | Sometimes there are classes that have injectable and non-injectable parameters in their constructors. Let's have for example:: 140 | 141 | class Database: pass 142 | 143 | 144 | class User: 145 | def __init__(self, name): 146 | self.name = name 147 | 148 | 149 | class UserUpdater: 150 | def __init__(self, db: Database, user): 151 | pass 152 | 153 | You may want to have database connection `db` injected into `UserUpdater` constructor, but in the same time provide `user` object by yourself, and assuming that `user` object is a value object and there's many users in your application it doesn't make much sense to inject objects of class `User`. 154 | 155 | In this situation there's technique called Assisted injection:: 156 | 157 | from injector import ClassAssistedBuilder 158 | injector = Injector() 159 | builder = injector.get(ClassAssistedBuilder[UserUpdater]) 160 | user = User('John') 161 | user_updater = builder.build(user=user) 162 | 163 | This way we don't get `UserUpdater` directly but rather a builder object. Such builder has `build(**kwargs)` method which takes non-injectable parameters, combines them with injectable dependencies of `UserUpdater` and calls `UserUpdater` initializer using all of them. 164 | 165 | `AssistedBuilder[T]` and `ClassAssistedBuilder[T]` are injectable just as anything 166 | else, if you need instance of it you just ask for it like that:: 167 | 168 | class NeedsUserUpdater: 169 | @inject 170 | def __init__(self, builder: ClassAssistedBuilder[UserUpdater]): 171 | self.updater_builder = builder 172 | 173 | def method(self): 174 | updater = self.updater_builder.build(user=None) 175 | 176 | `ClassAssistedBuilder` means it'll construct a concrete class and no bindings will be used. 177 | 178 | If you want to follow bindings and construct class pointed to by a key you use `AssistedBuilder` and can do it like this:: 179 | 180 | >>> DB = Key('DB') 181 | >>> class DBImplementation: 182 | ... def __init__(self, uri): 183 | ... pass 184 | ... 185 | >>> def configure(binder): 186 | ... binder.bind(DB, to=DBImplementation) 187 | ... 188 | >>> injector = Injector(configure) 189 | >>> builder = injector.get(AssistedBuilder[DB]) 190 | >>> isinstance(builder.build(uri='x'), DBImplementation) 191 | True 192 | 193 | More information on this topic: 194 | 195 | - `"How to use Google Guice to create objects that require parameters?" on Stack Overflow `_ 196 | - `Google Guice assisted injection `_ 197 | 198 | 199 | Child injectors 200 | ``````````````` 201 | 202 | Concept similar to Guice's child injectors is supported by `Injector`. This way you can have one injector that inherits bindings from other injector (parent) but these bindings can be overriden in it and it doesn't affect parent injector bindings:: 203 | 204 | >>> def configure_parent(binder): 205 | ... binder.bind(str, to='asd') 206 | ... binder.bind(int, to=42) 207 | ... 208 | >>> def configure_child(binder): 209 | ... binder.bind(str, to='qwe') 210 | ... 211 | >>> parent = Injector(configure_parent) 212 | >>> child = parent.create_child_injector(configure_child) 213 | >>> parent.get(str), parent.get(int) 214 | ('asd', 42) 215 | >>> child.get(str), child.get(int) 216 | ('qwe', 42) 217 | 218 | **Note**: Default scopes are bound only to root injector. Binding them manually to child injectors will result in unexpected behaviour. **Note 2**: Once a binding key is present in parent injector scope (like `singleton` scope), provider saved there takes predecence when binding is overridden in child injector in the same scope. This behaviour is subject to change:: 219 | 220 | 221 | >>> def configure_parent(binder): 222 | ... binder.bind(str, to='asd', scope=singleton) 223 | ... 224 | >>> def configure_child(binder): 225 | ... binder.bind(str, to='qwe', scope=singleton) 226 | ... 227 | >>> parent = Injector(configure_parent) 228 | >>> child = parent.create_child_injector(configure_child) 229 | >>> child.get(str) # this behaves as expected 230 | 'qwe' 231 | >>> parent.get(str) # wat 232 | 'qwe' 233 | -------------------------------------------------------------------------------- /docs/testing.rst: -------------------------------------------------------------------------------- 1 | Testing with Injector 2 | ===================== 3 | 4 | When you use unit test framework such as `unittest2` or `nose` you can also profit from `injector`. :: 5 | 6 | import unittest 7 | from injector import Injector, Module 8 | 9 | 10 | class UsernameModule(Module): 11 | def configure(self, binder): 12 | binder.bind(str, 'Maria') 13 | 14 | 15 | class TestSomethingClass(unittest.TestCase): 16 | 17 | def setUp(self): 18 | self.__injector = Injector(UsernameModule()) 19 | 20 | def test_username(self): 21 | username = self.__injector.get(str) 22 | self.assertEqual(username, 'Maria') 23 | 24 | -------------------------------------------------------------------------------- /injector/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # 3 | # Copyright (C) 2010 Alec Thomas 4 | # All rights reserved. 5 | # 6 | # This software is licensed as described in the file COPYING, which 7 | # you should have received as part of this distribution. 8 | # 9 | # Author: Alec Thomas 10 | 11 | """Injector - Python dependency injection framework, inspired by Guice 12 | 13 | :copyright: (c) 2012 by Alec Thomas 14 | :license: BSD 15 | """ 16 | 17 | import functools 18 | import inspect 19 | import itertools 20 | import logging 21 | import sys 22 | import threading 23 | import types 24 | from abc import ABCMeta, abstractmethod 25 | from collections import namedtuple 26 | from typing import ( 27 | Any, 28 | Callable, 29 | cast, 30 | Dict, 31 | Generic, 32 | Iterable, 33 | List, 34 | Optional, 35 | overload, 36 | Set, 37 | Tuple, 38 | Type, 39 | TypeVar, 40 | TYPE_CHECKING, 41 | Union, 42 | ) 43 | 44 | try: 45 | from typing import NoReturn 46 | except ImportError: 47 | from typing_extensions import NoReturn 48 | 49 | # This is a messy, type-wise, because we not only have two potentially conflicting imports here 50 | # The easiest way to make mypy happy here is to tell it the versions from typing_extensions are 51 | # canonical. Since this typing_extensions import is only for mypy it'll work even without 52 | # typing_extensions actually installed so all's good. 53 | if TYPE_CHECKING: 54 | from typing_extensions import _AnnotatedAlias, Annotated, get_type_hints 55 | else: 56 | # Ignoring errors here as typing_extensions stub doesn't know about those things yet 57 | try: 58 | from typing import _AnnotatedAlias, Annotated, get_type_hints 59 | except ImportError: 60 | from typing_extensions import _AnnotatedAlias, Annotated, get_type_hints 61 | 62 | 63 | __author__ = 'Alec Thomas ' 64 | __version__ = '0.22.0' 65 | __version_tag__ = '' 66 | 67 | log = logging.getLogger('injector') 68 | log.addHandler(logging.NullHandler()) 69 | 70 | if log.level == logging.NOTSET: 71 | log.setLevel(logging.WARN) 72 | 73 | T = TypeVar('T') 74 | K = TypeVar('K') 75 | V = TypeVar('V') 76 | 77 | 78 | def private(something: T) -> T: 79 | something.__private__ = True # type: ignore 80 | return something 81 | 82 | 83 | CallableT = TypeVar('CallableT', bound=Callable) 84 | 85 | 86 | def synchronized(lock: threading.RLock) -> Callable[[CallableT], CallableT]: 87 | def outside_wrapper(function: CallableT) -> CallableT: 88 | @functools.wraps(function) 89 | def wrapper(*args: Any, **kwargs: Any) -> Any: 90 | with lock: 91 | return function(*args, **kwargs) 92 | 93 | return cast(CallableT, wrapper) 94 | 95 | return outside_wrapper 96 | 97 | 98 | lock = threading.RLock() 99 | 100 | 101 | _inject_marker = object() 102 | _noinject_marker = object() 103 | 104 | InjectT = TypeVar('InjectT') 105 | Inject = Annotated[InjectT, _inject_marker] 106 | """An experimental way to declare injectable dependencies utilizing a `PEP 593`_ implementation 107 | in Python 3.9 and backported to Python 3.7+ in `typing_extensions`. 108 | 109 | Those two declarations are equivalent:: 110 | 111 | @inject 112 | def fun(t: SomeType) -> None: 113 | pass 114 | 115 | def fun(t: Inject[SomeType]) -> None: 116 | pass 117 | 118 | The advantage over using :func:`inject` is that if you have some noninjectable parameters 119 | it may be easier to spot what are they. Those two are equivalent:: 120 | 121 | @inject 122 | @noninjectable('s') 123 | def fun(t: SomeType, s: SomeOtherType) -> None: 124 | pass 125 | 126 | def fun(t: Inject[SomeType], s: SomeOtherType) -> None: 127 | pass 128 | 129 | .. seealso:: 130 | 131 | Function :func:`get_bindings` 132 | A way to inspect how various injection declarations interact with each other. 133 | 134 | .. versionadded:: 0.18.0 135 | .. note:: Requires Python 3.7+. 136 | .. note:: 137 | 138 | If you're using mypy you need the version 0.750 or newer to fully type-check code using this 139 | construct. 140 | 141 | .. _PEP 593: https://www.python.org/dev/peps/pep-0593/ 142 | .. _typing_extensions: https://pypi.org/project/typing-extensions/ 143 | """ 144 | 145 | NoInject = Annotated[InjectT, _noinject_marker] 146 | """An experimental way to declare noninjectable dependencies utilizing a `PEP 593`_ implementation 147 | in Python 3.9 and backported to Python 3.7+ in `typing_extensions`. 148 | 149 | Since :func:`inject` declares all function's parameters to be injectable there needs to be a way 150 | to opt out of it. This has been provided by :func:`noninjectable` but `noninjectable` suffers from 151 | two issues: 152 | 153 | * You need to repeat the parameter name 154 | * The declaration may be relatively distance in space from the actual parameter declaration, thus 155 | hindering readability 156 | 157 | `NoInject` solves both of those concerns, for example (those two declarations are equivalent):: 158 | 159 | @inject 160 | @noninjectable('b') 161 | def fun(a: TypeA, b: TypeB) -> None: 162 | pass 163 | 164 | @inject 165 | def fun(a: TypeA, b: NoInject[TypeB]) -> None: 166 | pass 167 | 168 | .. seealso:: 169 | 170 | Function :func:`get_bindings` 171 | A way to inspect how various injection declarations interact with each other. 172 | 173 | .. versionadded:: 0.18.0 174 | .. note:: Requires Python 3.7+. 175 | .. note:: 176 | 177 | If you're using mypy you need the version 0.750 or newer to fully type-check code using this 178 | construct. 179 | 180 | .. _PEP 593: https://www.python.org/dev/peps/pep-0593/ 181 | .. _typing_extensions: https://pypi.org/project/typing-extensions/ 182 | """ 183 | 184 | 185 | def reraise(original: Exception, exception: Exception, maximum_frames: int = 1) -> NoReturn: 186 | prev_cls, prev, tb = sys.exc_info() 187 | frames = inspect.getinnerframes(cast(types.TracebackType, tb)) 188 | if len(frames) > maximum_frames: 189 | exception = original 190 | raise exception.with_traceback(tb) 191 | 192 | 193 | class Error(Exception): 194 | """Base exception.""" 195 | 196 | 197 | class UnsatisfiedRequirement(Error): 198 | """Requirement could not be satisfied.""" 199 | 200 | def __init__(self, owner: Optional[object], interface: type) -> None: 201 | super().__init__(owner, interface) 202 | self.owner = owner 203 | self.interface = interface 204 | 205 | def __str__(self) -> str: 206 | on = '%s has an ' % _describe(self.owner) if self.owner else '' 207 | return '%sunsatisfied requirement on %s' % (on, _describe(self.interface)) 208 | 209 | 210 | class CallError(Error): 211 | """Call to callable object fails.""" 212 | 213 | def __str__(self) -> str: 214 | if len(self.args) == 1: 215 | return self.args[0] 216 | 217 | instance, method, args, kwargs, original_error, stack = self.args 218 | cls = instance.__class__.__name__ if instance is not None else '' 219 | 220 | full_method = '.'.join((cls, method.__name__)).strip('.') 221 | 222 | parameters = ', '.join( 223 | itertools.chain( 224 | (repr(arg) for arg in args), ('%s=%r' % (key, value) for (key, value) in kwargs.items()) 225 | ) 226 | ) 227 | return 'Call to %s(%s) failed: %s (injection stack: %r)' % ( 228 | full_method, 229 | parameters, 230 | original_error, 231 | [level[0] for level in stack], 232 | ) 233 | 234 | 235 | class CircularDependency(Error): 236 | """Circular dependency detected.""" 237 | 238 | 239 | class UnknownProvider(Error): 240 | """Tried to bind to a type whose provider couldn't be determined.""" 241 | 242 | 243 | class UnknownArgument(Error): 244 | """Tried to mark an unknown argument as noninjectable.""" 245 | 246 | 247 | class Provider(Generic[T]): 248 | """Provides class instances.""" 249 | 250 | __metaclass__ = ABCMeta 251 | 252 | @abstractmethod 253 | def get(self, injector: 'Injector') -> T: 254 | raise NotImplementedError # pragma: no cover 255 | 256 | 257 | class ClassProvider(Provider, Generic[T]): 258 | """Provides instances from a given class, created using an Injector.""" 259 | 260 | def __init__(self, cls: Type[T]) -> None: 261 | self._cls = cls 262 | 263 | def get(self, injector: 'Injector') -> T: 264 | return injector.create_object(self._cls) 265 | 266 | 267 | class CallableProvider(Provider, Generic[T]): 268 | """Provides something using a callable. 269 | 270 | The callable is called every time new value is requested from the provider. 271 | 272 | There's no need to explicitly use :func:`inject` or :data:`Inject` with the callable as it's 273 | assumed that, if the callable has annotated parameters, they're meant to be provided 274 | automatically. It wouldn't make sense any other way, as there's no mechanism to provide 275 | parameters to the callable at a later time, so either they'll be injected or there'll be 276 | a `CallError`. 277 | 278 | :: 279 | 280 | >>> class MyClass: 281 | ... def __init__(self, value: int) -> None: 282 | ... self.value = value 283 | ... 284 | >>> def factory(): 285 | ... print('providing') 286 | ... return MyClass(42) 287 | ... 288 | >>> def configure(binder): 289 | ... binder.bind(MyClass, to=CallableProvider(factory)) 290 | ... 291 | >>> injector = Injector(configure) 292 | >>> injector.get(MyClass) is injector.get(MyClass) 293 | providing 294 | providing 295 | False 296 | """ 297 | 298 | def __init__(self, callable: Callable[..., T]): 299 | self._callable = callable 300 | 301 | def get(self, injector: 'Injector') -> T: 302 | return injector.call_with_injection(self._callable) 303 | 304 | def __repr__(self) -> str: 305 | return '%s(%r)' % (type(self).__name__, self._callable) 306 | 307 | 308 | class InstanceProvider(Provider, Generic[T]): 309 | """Provide a specific instance. 310 | 311 | :: 312 | 313 | >>> class MyType: 314 | ... def __init__(self): 315 | ... self.contents = [] 316 | >>> def configure(binder): 317 | ... binder.bind(MyType, to=InstanceProvider(MyType())) 318 | ... 319 | >>> injector = Injector(configure) 320 | >>> injector.get(MyType) is injector.get(MyType) 321 | True 322 | >>> injector.get(MyType).contents.append('x') 323 | >>> injector.get(MyType).contents 324 | ['x'] 325 | """ 326 | 327 | def __init__(self, instance: T) -> None: 328 | self._instance = instance 329 | 330 | def get(self, injector: 'Injector') -> T: 331 | return self._instance 332 | 333 | def __repr__(self) -> str: 334 | return '%s(%r)' % (type(self).__name__, self._instance) 335 | 336 | 337 | @private 338 | class ListOfProviders(Provider, Generic[T]): 339 | """Provide a list of instances via other Providers.""" 340 | 341 | _providers: List[Provider[T]] 342 | 343 | def __init__(self) -> None: 344 | self._providers = [] 345 | 346 | def append(self, provider: Provider[T]) -> None: 347 | self._providers.append(provider) 348 | 349 | def __repr__(self) -> str: 350 | return '%s(%r)' % (type(self).__name__, self._providers) 351 | 352 | 353 | class MultiBindProvider(ListOfProviders[List[T]]): 354 | """Used by :meth:`Binder.multibind` to flatten results of providers that 355 | return sequences.""" 356 | 357 | def get(self, injector: 'Injector') -> List[T]: 358 | return [i for provider in self._providers for i in provider.get(injector)] 359 | 360 | 361 | class MapBindProvider(ListOfProviders[Dict[str, T]]): 362 | """A provider for map bindings.""" 363 | 364 | def get(self, injector: 'Injector') -> Dict[str, T]: 365 | map: Dict[str, T] = {} 366 | for provider in self._providers: 367 | map.update(provider.get(injector)) 368 | return map 369 | 370 | 371 | _BindingBase = namedtuple('_BindingBase', 'interface provider scope') 372 | 373 | 374 | @private 375 | class Binding(_BindingBase): 376 | """A binding from an (interface,) to a provider in a scope.""" 377 | 378 | def is_multibinding(self) -> bool: 379 | return _get_origin(_punch_through_alias(self.interface)) in {dict, list} 380 | 381 | 382 | @private 383 | class ImplicitBinding(Binding): 384 | """A binding that was created implicitly by auto-binding.""" 385 | 386 | pass 387 | 388 | 389 | _InstallableModuleType = Union[Callable[['Binder'], None], 'Module', Type['Module']] 390 | 391 | 392 | class Binder: 393 | """Bind interfaces to implementations. 394 | 395 | .. note:: This class is instantiated internally for you and there's no need 396 | to instantiate it on your own. 397 | """ 398 | 399 | _bindings: Dict[type, Binding] 400 | 401 | @private 402 | def __init__( 403 | self, injector: 'Injector', auto_bind: bool = True, parent: Optional['Binder'] = None 404 | ) -> None: 405 | """Create a new Binder. 406 | 407 | :param injector: Injector we are binding for. 408 | :param auto_bind: Whether to automatically bind missing types. 409 | :param parent: Parent binder. 410 | """ 411 | self.injector = injector 412 | self._auto_bind = auto_bind 413 | self._bindings = {} 414 | self.parent = parent 415 | 416 | def bind( 417 | self, 418 | interface: Type[T], 419 | to: Union[None, T, Callable[..., T], Provider[T]] = None, 420 | scope: Union[None, Type['Scope'], 'ScopeDecorator'] = None, 421 | ) -> None: 422 | """Bind an interface to an implementation. 423 | 424 | Binding `T` to an instance of `T` like 425 | 426 | :: 427 | 428 | binder.bind(A, to=A('some', 'thing')) 429 | 430 | is, for convenience, a shortcut for 431 | 432 | :: 433 | 434 | binder.bind(A, to=InstanceProvider(A('some', 'thing'))). 435 | 436 | Likewise, binding to a callable like 437 | 438 | :: 439 | 440 | binder.bind(A, to=some_callable) 441 | 442 | is a shortcut for 443 | 444 | :: 445 | 446 | binder.bind(A, to=CallableProvider(some_callable)) 447 | 448 | and, as such, if `some_callable` there has any annotated parameters they'll be provided 449 | automatically without having to use :func:`inject` or :data:`Inject` with the callable. 450 | 451 | `typing.List` and `typing.Dict` instances are reserved for multibindings and trying to bind them 452 | here will result in an error (use :meth:`multibind` instead):: 453 | 454 | binder.bind(List[str], to=['hello', 'there']) # Error 455 | 456 | :param interface: Type to bind. 457 | :param to: Instance or class to bind to, or an instance of 458 | :class:`Provider` subclass. 459 | :param scope: Optional :class:`Scope` in which to bind. 460 | """ 461 | if _get_origin(_punch_through_alias(interface)) in {dict, list}: 462 | raise Error( 463 | 'Type %s is reserved for multibindings. Use multibind instead of bind.' % (interface,) 464 | ) 465 | self._bindings[interface] = self.create_binding(interface, to, scope) 466 | 467 | @overload 468 | def multibind( 469 | self, 470 | interface: Type[List[T]], 471 | to: Union[List[T], Callable[..., List[T]], Provider[List[T]]], 472 | scope: Union[Type['Scope'], 'ScopeDecorator', None] = None, 473 | ) -> None: # pragma: no cover 474 | pass 475 | 476 | @overload 477 | def multibind( 478 | self, 479 | interface: Type[Dict[K, V]], 480 | to: Union[Dict[K, V], Callable[..., Dict[K, V]], Provider[Dict[K, V]]], 481 | scope: Union[Type['Scope'], 'ScopeDecorator', None] = None, 482 | ) -> None: # pragma: no cover 483 | pass 484 | 485 | def multibind( 486 | self, interface: type, to: Any, scope: Union['ScopeDecorator', Type['Scope'], None] = None 487 | ) -> None: 488 | """Creates or extends a multi-binding. 489 | 490 | A multi-binding contributes values to a list or to a dictionary. For example:: 491 | 492 | binder.multibind(List[str], to=['some', 'strings']) 493 | binder.multibind(List[str], to=['other', 'strings']) 494 | injector.get(List[str]) # ['some', 'strings', 'other', 'strings'] 495 | 496 | binder.multibind(Dict[str, int], to={'key': 11}) 497 | binder.multibind(Dict[str, int], to={'other_key': 33}) 498 | injector.get(Dict[str, int]) # {'key': 11, 'other_key': 33} 499 | 500 | .. versionchanged:: 0.17.0 501 | Added support for using `typing.Dict` and `typing.List` instances as interfaces. 502 | Deprecated support for `MappingKey`, `SequenceKey` and single-item lists and 503 | dictionaries as interfaces. 504 | 505 | :param interface: typing.Dict or typing.List instance to bind to. 506 | :param to: Instance, class to bind to, or an explicit :class:`Provider` 507 | subclass. Must provide a list or a dictionary, depending on the interface. 508 | :param scope: Optional Scope in which to bind. 509 | """ 510 | if interface not in self._bindings: 511 | provider: ListOfProviders 512 | if ( 513 | isinstance(interface, dict) 514 | or isinstance(interface, type) 515 | and issubclass(interface, dict) 516 | or _get_origin(_punch_through_alias(interface)) is dict 517 | ): 518 | provider = MapBindProvider() 519 | else: 520 | provider = MultiBindProvider() 521 | binding = self.create_binding(interface, provider, scope) 522 | self._bindings[interface] = binding 523 | else: 524 | binding = self._bindings[interface] 525 | provider = binding.provider 526 | assert isinstance(provider, ListOfProviders) 527 | provider.append(self.provider_for(interface, to)) 528 | 529 | def install(self, module: _InstallableModuleType) -> None: 530 | """Install a module into this binder. 531 | 532 | In this context the module is one of the following: 533 | 534 | * function taking the :class:`Binder` as its only parameter 535 | 536 | :: 537 | 538 | def configure(binder): 539 | bind(str, to='s') 540 | 541 | binder.install(configure) 542 | 543 | * instance of :class:`Module` (instance of its subclass counts) 544 | 545 | :: 546 | 547 | class MyModule(Module): 548 | def configure(self, binder): 549 | binder.bind(str, to='s') 550 | 551 | binder.install(MyModule()) 552 | 553 | * subclass of :class:`Module` - the subclass needs to be instantiable so if it 554 | expects any parameters they need to be injected 555 | 556 | :: 557 | 558 | binder.install(MyModule) 559 | """ 560 | if type(module) is type and issubclass(cast(type, module), Module): 561 | instance = cast(type, module)() 562 | else: 563 | instance = module 564 | instance(self) 565 | 566 | def create_binding( 567 | self, interface: type, to: Any = None, scope: Union['ScopeDecorator', Type['Scope'], None] = None 568 | ) -> Binding: 569 | provider = self.provider_for(interface, to) 570 | scope = scope or getattr(to or interface, '__scope__', NoScope) 571 | if isinstance(scope, ScopeDecorator): 572 | scope = scope.scope 573 | return Binding(interface, provider, scope) 574 | 575 | def provider_for(self, interface: Any, to: Any = None) -> Provider: 576 | base_type = _punch_through_alias(interface) 577 | origin = _get_origin(base_type) 578 | 579 | if interface is Any: 580 | raise TypeError('Injecting Any is not supported') 581 | elif _is_specialization(interface, ProviderOf): 582 | (target,) = interface.__args__ 583 | if to is not None: 584 | raise Exception('ProviderOf cannot be bound to anything') 585 | return InstanceProvider(ProviderOf(self.injector, target)) 586 | elif isinstance(to, Provider): 587 | return to 588 | elif isinstance( 589 | to, 590 | ( 591 | types.FunctionType, 592 | types.LambdaType, 593 | types.MethodType, 594 | types.BuiltinFunctionType, 595 | types.BuiltinMethodType, 596 | ), 597 | ): 598 | return CallableProvider(to) 599 | elif issubclass(type(to), type): 600 | return ClassProvider(cast(type, to)) 601 | elif isinstance(interface, BoundKey): 602 | 603 | def proxy(injector: Injector) -> Any: 604 | binder = injector.binder 605 | kwarg_providers = { 606 | name: binder.provider_for(None, provider) for (name, provider) in interface.kwargs.items() 607 | } 608 | kwargs = {name: provider.get(injector) for (name, provider) in kwarg_providers.items()} 609 | return interface.interface(**kwargs) 610 | 611 | return CallableProvider(inject(proxy)) 612 | elif _is_specialization(interface, AssistedBuilder): 613 | (target,) = interface.__args__ 614 | builder = interface(self.injector, target) 615 | return InstanceProvider(builder) 616 | elif ( 617 | origin is None 618 | and isinstance(base_type, (tuple, type)) 619 | and interface is not Any 620 | and isinstance(to, base_type) 621 | or origin in {dict, list} 622 | and isinstance(to, origin) 623 | ): 624 | return InstanceProvider(to) 625 | elif issubclass(type(base_type), type) or isinstance(base_type, (tuple, list)): 626 | if to is not None: 627 | return InstanceProvider(to) 628 | return ClassProvider(base_type) 629 | 630 | else: 631 | raise UnknownProvider('couldn\'t determine provider for %r to %r' % (interface, to)) 632 | 633 | def _get_binding(self, key: type, *, only_this_binder: bool = False) -> Tuple[Binding, 'Binder']: 634 | binding = self._bindings.get(key) 635 | if binding: 636 | return binding, self 637 | if self.parent and not only_this_binder: 638 | return self.parent._get_binding(key) 639 | 640 | raise KeyError 641 | 642 | def get_binding(self, interface: type) -> Tuple[Binding, 'Binder']: 643 | is_scope = isinstance(interface, type) and issubclass(interface, Scope) 644 | is_assisted_builder = _is_specialization(interface, AssistedBuilder) 645 | try: 646 | return self._get_binding(interface, only_this_binder=is_scope or is_assisted_builder) 647 | except (KeyError, UnsatisfiedRequirement): 648 | if is_scope: 649 | scope = interface 650 | self.bind(scope, to=scope(self.injector)) 651 | return self._get_binding(interface) 652 | # The special interface is added here so that requesting a special 653 | # interface with auto_bind disabled works 654 | if self._auto_bind or self._is_special_interface(interface): 655 | binding = ImplicitBinding(*self.create_binding(interface)) 656 | self._bindings[interface] = binding 657 | return binding, self 658 | 659 | raise UnsatisfiedRequirement(None, interface) 660 | 661 | def has_binding_for(self, interface: type) -> bool: 662 | return interface in self._bindings 663 | 664 | def has_explicit_binding_for(self, interface: type) -> bool: 665 | return self.has_binding_for(interface) and not isinstance(self._bindings[interface], ImplicitBinding) 666 | 667 | def _is_special_interface(self, interface: type) -> bool: 668 | # "Special" interfaces are ones that you cannot bind yourself but 669 | # you can request them (for example you cannot bind ProviderOf(SomeClass) 670 | # to anything but you can inject ProviderOf(SomeClass) just fine 671 | return any(_is_specialization(interface, cls) for cls in [AssistedBuilder, ProviderOf]) 672 | 673 | 674 | def _is_specialization(cls: type, generic_class: Any) -> bool: 675 | # Starting with typing 3.5.3/Python 3.6 it is no longer necessarily true that 676 | # issubclass(SomeGeneric[X], SomeGeneric) so we need some other way to 677 | # determine whether a particular object is a generic class with type parameters 678 | # provided. Fortunately there seems to be __origin__ attribute that's useful here. 679 | 680 | # We need to special-case Annotated as its __origin__ behaves differently than 681 | # other typing generic classes. See https://github.com/python/typing/pull/635 682 | # for some details. 683 | if generic_class is Annotated and isinstance(cls, _AnnotatedAlias): 684 | return True 685 | 686 | if not hasattr(cls, '__origin__'): 687 | return False 688 | origin = cast(Any, cls).__origin__ 689 | if not inspect.isclass(generic_class): 690 | generic_class = type(generic_class) 691 | if not inspect.isclass(origin): 692 | origin = type(origin) 693 | # __origin__ is generic_class is a special case to handle Union as 694 | # Union cannot be used in issubclass() check (it raises an exception 695 | # by design). 696 | return origin is generic_class or issubclass(origin, generic_class) 697 | 698 | 699 | def _punch_through_alias(type_: Any) -> type: 700 | if ( 701 | sys.version_info < (3, 10) 702 | and getattr(type_, '__qualname__', '') == 'NewType..new_type' 703 | or sys.version_info >= (3, 10) 704 | and type(type_).__module__ == 'typing' 705 | and type(type_).__name__ == 'NewType' 706 | ): 707 | return type_.__supertype__ 708 | elif isinstance(type_, _AnnotatedAlias) and getattr(type_, '__metadata__', None) is not None: 709 | return type_.__origin__ 710 | else: 711 | return type_ 712 | 713 | 714 | def _get_origin(type_: type) -> Optional[type]: 715 | origin = getattr(type_, '__origin__', None) 716 | # Older typing behaves differently there and stores Dict and List as origin, we need to be flexible. 717 | if origin is List: 718 | return list 719 | elif origin is Dict: 720 | return dict 721 | return origin 722 | 723 | 724 | class Scope: 725 | """A Scope looks up the Provider for a binding. 726 | 727 | By default (ie. :class:`NoScope` ) this simply returns the default 728 | :class:`Provider` . 729 | """ 730 | 731 | __metaclass__ = ABCMeta 732 | 733 | def __init__(self, injector: 'Injector') -> None: 734 | self.injector = injector 735 | self.configure() 736 | 737 | def configure(self) -> None: 738 | """Configure the scope.""" 739 | 740 | @abstractmethod 741 | def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]: 742 | """Get a :class:`Provider` for a key. 743 | 744 | :param key: The key to return a provider for. 745 | :param provider: The default Provider associated with the key. 746 | :returns: A Provider instance that can provide an instance of key. 747 | """ 748 | raise NotImplementedError # pragma: no cover 749 | 750 | 751 | class ScopeDecorator: 752 | def __init__(self, scope: Type[Scope]) -> None: 753 | self.scope = scope 754 | 755 | def __call__(self, cls: T) -> T: 756 | cast(Any, cls).__scope__ = self.scope 757 | binding = getattr(cls, '__binding__', None) 758 | if binding: 759 | new_binding = Binding(interface=binding.interface, provider=binding.provider, scope=self.scope) 760 | setattr(cls, '__binding__', new_binding) 761 | return cls 762 | 763 | def __repr__(self) -> str: 764 | return 'ScopeDecorator(%s)' % self.scope.__name__ 765 | 766 | 767 | class NoScope(Scope): 768 | """An unscoped provider.""" 769 | 770 | def get(self, unused_key: Type[T], provider: Provider[T]) -> Provider[T]: 771 | return provider 772 | 773 | 774 | noscope = ScopeDecorator(NoScope) 775 | 776 | 777 | class SingletonScope(Scope): 778 | """A :class:`Scope` that returns a per-Injector instance for a key. 779 | 780 | :data:`singleton` can be used as a convenience class decorator. 781 | 782 | >>> class A: pass 783 | >>> injector = Injector() 784 | >>> provider = ClassProvider(A) 785 | >>> singleton = SingletonScope(injector) 786 | >>> a = singleton.get(A, provider) 787 | >>> b = singleton.get(A, provider) 788 | >>> a is b 789 | True 790 | """ 791 | 792 | _context: Dict[type, Provider] 793 | 794 | def configure(self) -> None: 795 | self._context = {} 796 | 797 | @synchronized(lock) 798 | def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]: 799 | try: 800 | return self._context[key] 801 | except KeyError: 802 | instance = self._get_instance(key, provider, self.injector) 803 | provider = InstanceProvider(instance) 804 | self._context[key] = provider 805 | return provider 806 | 807 | def _get_instance(self, key: Type[T], provider: Provider[T], injector: 'Injector') -> T: 808 | if injector.parent and not injector.binder.has_explicit_binding_for(key): 809 | try: 810 | return self._get_instance_from_parent(key, provider, injector.parent) 811 | except (CallError, UnsatisfiedRequirement): 812 | pass 813 | return provider.get(injector) 814 | 815 | def _get_instance_from_parent(self, key: Type[T], provider: Provider[T], parent: 'Injector') -> T: 816 | singleton_scope_binding, _ = parent.binder.get_binding(type(self)) 817 | singleton_scope = singleton_scope_binding.provider.get(parent) 818 | provider = singleton_scope.get(key, provider) 819 | return provider.get(parent) 820 | 821 | 822 | singleton = ScopeDecorator(SingletonScope) 823 | 824 | 825 | class ThreadLocalScope(Scope): 826 | """A :class:`Scope` that returns a per-thread instance for a key.""" 827 | 828 | def configure(self) -> None: 829 | self._locals = threading.local() 830 | 831 | def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]: 832 | try: 833 | return getattr(self._locals, repr(key)) 834 | except AttributeError: 835 | provider = InstanceProvider(provider.get(self.injector)) 836 | setattr(self._locals, repr(key), provider) 837 | return provider 838 | 839 | 840 | threadlocal = ScopeDecorator(ThreadLocalScope) 841 | 842 | 843 | class Module: 844 | """Configures injector and providers.""" 845 | 846 | def __call__(self, binder: Binder) -> None: 847 | """Configure the binder.""" 848 | self.__injector__ = binder.injector 849 | for unused_name, function in inspect.getmembers(self, inspect.ismethod): 850 | binding = None 851 | if hasattr(function, '__binding__'): 852 | binding = function.__binding__ 853 | if binding.interface == '__deferred__': 854 | # We could not evaluate a forward reference at @provider-decoration time, we need to 855 | # try again now. 856 | try: 857 | annotations = get_type_hints(function) 858 | except NameError as e: 859 | raise NameError( 860 | 'Cannot avaluate forward reference annotation(s) in method %r belonging to %r: %s' 861 | % (function.__name__, type(self), e) 862 | ) from e 863 | return_type = annotations['return'] 864 | binding = cast(Any, function.__func__).__binding__ = Binding( 865 | interface=return_type, provider=binding.provider, scope=binding.scope 866 | ) 867 | bind_method = binder.multibind if binding.is_multibinding() else binder.bind 868 | bind_method( # type: ignore 869 | binding.interface, to=types.MethodType(binding.provider, self), scope=binding.scope 870 | ) 871 | self.configure(binder) 872 | 873 | def configure(self, binder: Binder) -> None: 874 | """Override to configure bindings.""" 875 | 876 | 877 | class Injector: 878 | """ 879 | :param modules: Optional - a configuration module or iterable of configuration modules. 880 | Each module will be installed in current :class:`Binder` using :meth:`Binder.install`. 881 | 882 | Consult :meth:`Binder.install` documentation for the details. 883 | 884 | :param auto_bind: Whether to automatically bind missing types. 885 | :param parent: Parent injector. 886 | 887 | .. versionadded:: 0.7.5 888 | ``use_annotations`` parameter 889 | 890 | .. versionchanged:: 0.13.0 891 | ``use_annotations`` parameter is removed 892 | """ 893 | 894 | _stack: Tuple[Tuple[object, Callable, Tuple[Tuple[str, type], ...]], ...] 895 | binder: Binder 896 | 897 | def __init__( 898 | self, 899 | modules: Union[_InstallableModuleType, Iterable[_InstallableModuleType], None] = None, 900 | auto_bind: bool = True, 901 | parent: Optional['Injector'] = None, 902 | ) -> None: 903 | # Stack of keys currently being injected. Used to detect circular 904 | # dependencies. 905 | self._stack = () 906 | 907 | self.parent = parent 908 | 909 | # Binder 910 | self.binder = Binder(self, auto_bind=auto_bind, parent=parent.binder if parent is not None else None) 911 | 912 | if not modules: 913 | modules = [] 914 | elif not hasattr(modules, '__iter__'): 915 | modules = [cast(_InstallableModuleType, modules)] 916 | # This line is needed to pelase mypy. We know we have Iteable of modules here. 917 | modules = cast(Iterable[_InstallableModuleType], modules) 918 | 919 | # Bind some useful types 920 | self.binder.bind(Injector, to=self) 921 | self.binder.bind(Binder, to=self.binder) 922 | 923 | # Initialise modules 924 | for module in modules: 925 | self.binder.install(module) 926 | 927 | @property 928 | def _log_prefix(self) -> str: 929 | return '>' * (len(self._stack) + 1) + ' ' 930 | 931 | @synchronized(lock) 932 | def get(self, interface: Type[T], scope: Union[ScopeDecorator, Type[Scope], None] = None) -> T: 933 | """Get an instance of the given interface. 934 | 935 | .. note:: 936 | 937 | Although this method is part of :class:`Injector`'s public interface 938 | it's meant to be used in limited set of circumstances. 939 | 940 | For example, to create some kind of root object (application object) 941 | of your application (note that only one `get` call is needed, 942 | inside the `Application` class and any of its dependencies 943 | :func:`inject` can and should be used): 944 | 945 | .. code-block:: python 946 | 947 | class Application: 948 | 949 | @inject 950 | def __init__(self, dep1: Dep1, dep2: Dep2): 951 | self.dep1 = dep1 952 | self.dep2 = dep2 953 | 954 | def run(self): 955 | self.dep1.something() 956 | 957 | injector = Injector(configuration) 958 | application = injector.get(Application) 959 | application.run() 960 | 961 | :param interface: Interface whose implementation we want. 962 | :param scope: Class of the Scope in which to resolve. 963 | :returns: An implementation of interface. 964 | """ 965 | binding, binder = self.binder.get_binding(interface) 966 | scope = scope or binding.scope 967 | if isinstance(scope, ScopeDecorator): 968 | scope = scope.scope 969 | # Fetch the corresponding Scope instance from the Binder. 970 | scope_binding, _ = binder.get_binding(scope) 971 | scope_instance = scope_binding.provider.get(self) 972 | 973 | log.debug( 974 | '%sInjector.get(%r, scope=%r) using %r', self._log_prefix, interface, scope, binding.provider 975 | ) 976 | provider_instance = scope_instance.get(interface, binding.provider) 977 | result = provider_instance.get(self) 978 | log.debug('%s -> %r', self._log_prefix, result) 979 | return result 980 | 981 | def create_child_injector(self, *args: Any, **kwargs: Any) -> 'Injector': 982 | kwargs['parent'] = self 983 | return Injector(*args, **kwargs) 984 | 985 | def create_object(self, cls: Type[T], additional_kwargs: Any = None) -> T: 986 | """Create a new instance, satisfying any dependencies on cls.""" 987 | additional_kwargs = additional_kwargs or {} 988 | log.debug('%sCreating %r object with %r', self._log_prefix, cls, additional_kwargs) 989 | 990 | try: 991 | instance = cls.__new__(cls) 992 | except TypeError as e: 993 | reraise( 994 | e, 995 | CallError(cls, getattr(cls.__new__, '__func__', cls.__new__), (), {}, e, self._stack), 996 | maximum_frames=2, 997 | ) 998 | init = cls.__init__ 999 | try: 1000 | self.call_with_injection(init, self_=instance, kwargs=additional_kwargs) 1001 | except TypeError as e: 1002 | # Mypy says "Cannot access "__init__" directly" 1003 | init_function = instance.__init__.__func__ # type: ignore 1004 | reraise(e, CallError(instance, init_function, (), additional_kwargs, e, self._stack)) 1005 | return instance 1006 | 1007 | def call_with_injection( 1008 | self, callable: Callable[..., T], self_: Any = None, args: Any = (), kwargs: Any = {} 1009 | ) -> T: 1010 | """Call a callable and provide its dependencies if needed. 1011 | 1012 | Dependencies are provided when the callable is decorated with :func:`@inject ` 1013 | or some individual parameters are wrapped in :data:`Inject` – otherwise 1014 | ``call_with_injection()`` is equivalent to just calling the callable directly. 1015 | 1016 | If there is an overlap between arguments provided in ``args`` and ``kwargs`` 1017 | and injectable dependencies the provided values take precedence and no dependency 1018 | injection process will take place for the corresponding parameters. 1019 | 1020 | :param self_: Instance of a class callable belongs to if it's a method, 1021 | None otherwise. 1022 | :param args: Arguments to pass to callable. 1023 | :param kwargs: Keyword arguments to pass to callable. 1024 | :type callable: callable 1025 | :type args: tuple of objects 1026 | :type kwargs: dict of string -> object 1027 | :return: Value returned by callable. 1028 | """ 1029 | 1030 | bindings = get_bindings(callable) 1031 | signature = inspect.signature(callable) 1032 | full_args = args 1033 | if self_ is not None: 1034 | full_args = (self_,) + full_args 1035 | bound_arguments = signature.bind_partial(*full_args) 1036 | 1037 | needed = dict( 1038 | (k, v) for (k, v) in bindings.items() if k not in kwargs and k not in bound_arguments.arguments 1039 | ) 1040 | 1041 | dependencies = self.args_to_inject( 1042 | function=callable, 1043 | bindings=needed, 1044 | owner_key=self_.__class__ if self_ is not None else callable.__module__, 1045 | ) 1046 | 1047 | dependencies.update(kwargs) 1048 | 1049 | try: 1050 | return callable(*full_args, **dependencies) 1051 | except TypeError as e: 1052 | reraise(e, CallError(self_, callable, args, dependencies, e, self._stack)) 1053 | # Needed because of a mypy-related issue (https://github.com/python/mypy/issues/8129). 1054 | assert False, "unreachable" # pragma: no cover 1055 | 1056 | @private 1057 | @synchronized(lock) 1058 | def args_to_inject( 1059 | self, function: Callable, bindings: Dict[str, type], owner_key: object 1060 | ) -> Dict[str, Any]: 1061 | """Inject arguments into a function. 1062 | 1063 | :param function: The function. 1064 | :param bindings: Map of argument name to binding key to inject. 1065 | :param owner_key: A key uniquely identifying the *scope* of this function. 1066 | For a method this will be the owning class. 1067 | :returns: Dictionary of resolved arguments. 1068 | """ 1069 | dependencies = {} 1070 | 1071 | key = (owner_key, function, tuple(sorted(bindings.items()))) 1072 | 1073 | def repr_key(k: Tuple[object, Callable, Tuple[Tuple[str, type], ...]]) -> str: 1074 | owner_key, function, bindings = k 1075 | return '%s.%s(injecting %s)' % (tuple(map(_describe, k[:2])) + (dict(k[2]),)) 1076 | 1077 | log.debug('%sProviding %r for %r', self._log_prefix, bindings, function) 1078 | 1079 | if key in self._stack: 1080 | raise CircularDependency( 1081 | 'circular dependency detected: %s -> %s' 1082 | % (' -> '.join(map(repr_key, self._stack)), repr_key(key)) 1083 | ) 1084 | 1085 | self._stack += (key,) 1086 | try: 1087 | for arg, interface in bindings.items(): 1088 | try: 1089 | instance: Any = self.get(interface) 1090 | except UnsatisfiedRequirement as e: 1091 | if not e.owner: 1092 | e = UnsatisfiedRequirement(owner_key, e.interface) 1093 | raise e 1094 | dependencies[arg] = instance 1095 | finally: 1096 | self._stack = tuple(self._stack[:-1]) 1097 | 1098 | return dependencies 1099 | 1100 | 1101 | def get_bindings(callable: Callable) -> Dict[str, type]: 1102 | """Get bindings of injectable parameters from a callable. 1103 | 1104 | If the callable is not decorated with :func:`inject` and does not have any of its 1105 | parameters declared as injectable using :data:`Inject` an empty dictionary will 1106 | be returned. Otherwise the returned dictionary will contain a mapping 1107 | between parameter names and their types with the exception of parameters 1108 | excluded from dependency injection (either with :func:`noninjectable`, :data:`NoInject` 1109 | or only explicit injection with :data:`Inject` being used). For example:: 1110 | 1111 | >>> def function1(a: int) -> None: 1112 | ... pass 1113 | ... 1114 | >>> get_bindings(function1) 1115 | {} 1116 | 1117 | >>> @inject 1118 | ... def function2(a: int) -> None: 1119 | ... pass 1120 | ... 1121 | >>> get_bindings(function2) 1122 | {'a': } 1123 | 1124 | >>> @inject 1125 | ... @noninjectable('b') 1126 | ... def function3(a: int, b: str) -> None: 1127 | ... pass 1128 | ... 1129 | >>> get_bindings(function3) 1130 | {'a': } 1131 | 1132 | >>> # The simple case of no @inject but injection requested with Inject[...] 1133 | >>> def function4(a: Inject[int], b: str) -> None: 1134 | ... pass 1135 | ... 1136 | >>> get_bindings(function4) 1137 | {'a': } 1138 | 1139 | >>> # Using @inject with Inject is redundant but it should not break anything 1140 | >>> @inject 1141 | ... def function5(a: Inject[int], b: str) -> None: 1142 | ... pass 1143 | ... 1144 | >>> get_bindings(function5) 1145 | {'a': , 'b': } 1146 | 1147 | >>> # We need to be able to exclude a parameter from injection with NoInject 1148 | >>> @inject 1149 | ... def function6(a: int, b: NoInject[str]) -> None: 1150 | ... pass 1151 | ... 1152 | >>> get_bindings(function6) 1153 | {'a': } 1154 | 1155 | >>> # The presence of NoInject should not trigger anything on its own 1156 | >>> def function7(a: int, b: NoInject[str]) -> None: 1157 | ... pass 1158 | ... 1159 | >>> get_bindings(function7) 1160 | {} 1161 | 1162 | This function is used internally so by calling it you can learn what exactly 1163 | Injector is going to try to provide to a callable. 1164 | """ 1165 | look_for_explicit_bindings = False 1166 | if not hasattr(callable, '__bindings__'): 1167 | type_hints = get_type_hints(callable, include_extras=True) 1168 | has_injectable_parameters = any( 1169 | _is_specialization(v, Annotated) and _inject_marker in v.__metadata__ for v in type_hints.values() 1170 | ) 1171 | 1172 | if not has_injectable_parameters: 1173 | return {} 1174 | else: 1175 | look_for_explicit_bindings = True 1176 | 1177 | if look_for_explicit_bindings or cast(Any, callable).__bindings__ == 'deferred': 1178 | read_and_store_bindings( 1179 | callable, _infer_injected_bindings(callable, only_explicit_bindings=look_for_explicit_bindings) 1180 | ) 1181 | noninjectables: Set[str] = getattr(callable, '__noninjectables__', set()) 1182 | return {k: v for k, v in cast(Any, callable).__bindings__.items() if k not in noninjectables} 1183 | 1184 | 1185 | class _BindingNotYetAvailable(Exception): 1186 | pass 1187 | 1188 | 1189 | # See a comment in _infer_injected_bindings() for why this is useful. 1190 | class _NoReturnAnnotationProxy: 1191 | def __init__(self, callable: Callable) -> None: 1192 | self.callable = callable 1193 | 1194 | def __getattribute__(self, name: str) -> Any: 1195 | # get_type_hints() uses quite complex logic to determine the namespaces using which 1196 | # any forward references should be resolved. Instead of mirroring this logic here 1197 | # let's just take the easy way out and forward all attribute access to the original 1198 | # callable except for the annotations – we want to filter them. 1199 | callable = object.__getattribute__(self, 'callable') 1200 | if name == '__annotations__': 1201 | annotations = callable.__annotations__ 1202 | return {name: value for (name, value) in annotations.items() if name != 'return'} 1203 | return getattr(callable, name) 1204 | 1205 | 1206 | def _infer_injected_bindings(callable: Callable, only_explicit_bindings: bool) -> Dict[str, type]: 1207 | def _is_new_union_type(instance: Any) -> bool: 1208 | new_union_type = getattr(types, 'UnionType', None) 1209 | return new_union_type is not None and isinstance(instance, new_union_type) 1210 | 1211 | spec = inspect.getfullargspec(callable) 1212 | 1213 | try: 1214 | # Return types don't matter for the purpose of dependency injection so instead of 1215 | # obtaining type hints of the callable directly let's wrap it in _NoReturnAnnotationProxy. 1216 | # The proxy removes the return type annotation (if present) from the annotations so that 1217 | # get_type_hints() works even if the return type is a forward reference that can't be 1218 | # resolved. 1219 | bindings = get_type_hints(cast(Callable, _NoReturnAnnotationProxy(callable)), include_extras=True) 1220 | except NameError as e: 1221 | raise _BindingNotYetAvailable(e) 1222 | 1223 | # We don't care about the return value annotation as it doesn't matter 1224 | # injection-wise. 1225 | bindings.pop('return', None) 1226 | 1227 | # If we're dealing with a bound method get_type_hints will still return `self` annotation even though 1228 | # it's already provided and we're not really interested in its type. So – drop it. 1229 | if isinstance(callable, types.MethodType): 1230 | self_name = spec.args[0] 1231 | bindings.pop(self_name, None) 1232 | 1233 | # variadic arguments aren't supported at the moment (this may change 1234 | # in the future if someone has a good idea how to utilize them) 1235 | if spec.varargs: 1236 | bindings.pop(spec.varargs, None) 1237 | if spec.varkw: 1238 | bindings.pop(spec.varkw, None) 1239 | 1240 | for k, v in list(bindings.items()): 1241 | if _is_specialization(v, Annotated): 1242 | v, metadata = v.__origin__, v.__metadata__ 1243 | bindings[k] = v 1244 | else: 1245 | metadata = tuple() 1246 | 1247 | if only_explicit_bindings and _inject_marker not in metadata or _noinject_marker in metadata: 1248 | del bindings[k] 1249 | elif _is_specialization(v, Union) or _is_new_union_type(v): 1250 | # We don't treat Optional parameters in any special way at the moment. 1251 | union_members = v.__args__ 1252 | new_members = tuple(set(union_members) - {type(None)}) 1253 | # mypy stared complaining about this line for some reason: 1254 | # error: Variable "new_members" is not valid as a type 1255 | new_union = Union[new_members] # type: ignore 1256 | # mypy complains about this construct: 1257 | # error: The type alias is invalid in runtime context 1258 | # See: https://github.com/python/mypy/issues/5354 1259 | union_metadata = { 1260 | metadata 1261 | for member in new_members 1262 | for metadata in getattr(member, '__metadata__', tuple()) 1263 | if _is_specialization(member, Annotated) 1264 | } 1265 | if ( 1266 | only_explicit_bindings 1267 | and _inject_marker not in union_metadata 1268 | or _noinject_marker in union_metadata 1269 | ): 1270 | del bindings[k] 1271 | else: 1272 | bindings[k] = new_union # type: ignore 1273 | 1274 | return bindings 1275 | 1276 | 1277 | def provider(function: CallableT) -> CallableT: 1278 | """Decorator for :class:`Module` methods, registering a provider of a type. 1279 | 1280 | >>> class MyModule(Module): 1281 | ... @provider 1282 | ... def provide_name(self) -> str: 1283 | ... return 'Bob' 1284 | 1285 | @provider-decoration implies @inject so you can omit it and things will 1286 | work just the same: 1287 | 1288 | >>> class MyModule2(Module): 1289 | ... def configure(self, binder): 1290 | ... binder.bind(int, to=654) 1291 | ... 1292 | ... @provider 1293 | ... def provide_str(self, i: int) -> str: 1294 | ... return str(i) 1295 | ... 1296 | >>> injector = Injector(MyModule2) 1297 | >>> injector.get(str) 1298 | '654' 1299 | """ 1300 | _mark_provider_function(function, allow_multi=False) 1301 | return function 1302 | 1303 | 1304 | def multiprovider(function: CallableT) -> CallableT: 1305 | """Like :func:`provider`, but for multibindings. Example usage:: 1306 | 1307 | class MyModule(Module): 1308 | @multiprovider 1309 | def provide_strs(self) -> List[str]: 1310 | return ['str1'] 1311 | 1312 | class OtherModule(Module): 1313 | @multiprovider 1314 | def provide_strs_also(self) -> List[str]: 1315 | return ['str2'] 1316 | 1317 | Injector([MyModule, OtherModule]).get(List[str]) # ['str1', 'str2'] 1318 | 1319 | See also: :meth:`Binder.multibind`.""" 1320 | _mark_provider_function(function, allow_multi=True) 1321 | return function 1322 | 1323 | 1324 | def _mark_provider_function(function: Callable, *, allow_multi: bool) -> None: 1325 | scope_ = getattr(function, '__scope__', None) 1326 | try: 1327 | annotations = get_type_hints(function) 1328 | except NameError: 1329 | return_type = '__deferred__' 1330 | else: 1331 | return_type = annotations['return'] 1332 | _validate_provider_return_type(function, cast(type, return_type), allow_multi) 1333 | function.__binding__ = Binding(return_type, inject(function), scope_) # type: ignore 1334 | 1335 | 1336 | def _validate_provider_return_type(function: Callable, return_type: type, allow_multi: bool) -> None: 1337 | origin = _get_origin(_punch_through_alias(return_type)) 1338 | if origin in {dict, list} and not allow_multi: 1339 | raise Error( 1340 | 'Function %s needs to be decorated with multiprovider instead of provider if it is to ' 1341 | 'provide values to a multibinding of type %s' % (function.__name__, return_type) 1342 | ) 1343 | 1344 | 1345 | ConstructorOrClassT = TypeVar('ConstructorOrClassT', bound=Union[Callable, Type]) 1346 | 1347 | 1348 | @overload 1349 | def inject(constructor_or_class: CallableT) -> CallableT: # pragma: no cover 1350 | pass 1351 | 1352 | 1353 | @overload 1354 | def inject(constructor_or_class: Type[T]) -> Type[T]: # pragma: no cover 1355 | pass 1356 | 1357 | 1358 | def inject(constructor_or_class: ConstructorOrClassT) -> ConstructorOrClassT: 1359 | """Decorator declaring parameters to be injected. 1360 | 1361 | eg. 1362 | 1363 | >>> class A: 1364 | ... @inject 1365 | ... def __init__(self, number: int, name: str): 1366 | ... print([number, name]) 1367 | ... 1368 | >>> def configure(binder): 1369 | ... binder.bind(A) 1370 | ... binder.bind(int, to=123) 1371 | ... binder.bind(str, to='Bob') 1372 | 1373 | Use the Injector to get a new instance of A: 1374 | 1375 | >>> a = Injector(configure).get(A) 1376 | [123, 'Bob'] 1377 | 1378 | As a convenience one can decorate a class itself:: 1379 | 1380 | @inject 1381 | class B: 1382 | def __init__(self, dependency: Dependency): 1383 | self.dependency = dependency 1384 | 1385 | This is equivalent to decorating its constructor. In particular this provides integration with 1386 | `dataclasses `_ (the order of decorator 1387 | application is important here):: 1388 | 1389 | @inject 1390 | @dataclass 1391 | class C: 1392 | dependency: Dependency 1393 | 1394 | .. note:: 1395 | 1396 | This decorator is to be used on class constructors (or, as a convenience, on classes). 1397 | Using it on non-constructor methods worked in the past but it was an implementation 1398 | detail rather than a design decision. 1399 | 1400 | Third party libraries may, however, provide support for injecting dependencies 1401 | into non-constructor methods or free functions in one form or another. 1402 | 1403 | .. seealso:: 1404 | 1405 | Generic type :data:`Inject` 1406 | A more explicit way to declare parameters as injectable. 1407 | 1408 | Function :func:`get_bindings` 1409 | A way to inspect how various injection declarations interact with each other. 1410 | 1411 | .. versionchanged:: 0.16.2 1412 | 1413 | (Re)added support for decorating classes with @inject. 1414 | """ 1415 | if isinstance(constructor_or_class, type) and hasattr(constructor_or_class, '__init__'): 1416 | inject(cast(Any, constructor_or_class).__init__) 1417 | else: 1418 | function = constructor_or_class 1419 | try: 1420 | bindings = _infer_injected_bindings(function, only_explicit_bindings=False) 1421 | read_and_store_bindings(function, bindings) 1422 | except _BindingNotYetAvailable: 1423 | cast(Any, function).__bindings__ = 'deferred' 1424 | return constructor_or_class 1425 | 1426 | 1427 | def noninjectable(*args: str) -> Callable[[CallableT], CallableT]: 1428 | """Mark some parameters as not injectable. 1429 | 1430 | This serves as documentation for people reading the code and will prevent 1431 | Injector from ever attempting to provide the parameters. 1432 | 1433 | For example: 1434 | 1435 | >>> class Service: 1436 | ... pass 1437 | ... 1438 | >>> class SomeClass: 1439 | ... @inject 1440 | ... @noninjectable('user_id') 1441 | ... def __init__(self, service: Service, user_id: int): 1442 | ... # ... 1443 | ... pass 1444 | 1445 | :func:`noninjectable` decorations can be stacked on top of 1446 | each other and the order in which a function is decorated with 1447 | :func:`inject` and :func:`noninjectable` 1448 | doesn't matter. 1449 | 1450 | .. seealso:: 1451 | 1452 | Generic type :data:`NoInject` 1453 | A nicer way to declare parameters as noninjectable. 1454 | 1455 | Function :func:`get_bindings` 1456 | A way to inspect how various injection declarations interact with each other. 1457 | 1458 | """ 1459 | 1460 | def decorator(function: CallableT) -> CallableT: 1461 | argspec = inspect.getfullargspec(inspect.unwrap(function)) 1462 | for arg in args: 1463 | if arg not in argspec.args and arg not in argspec.kwonlyargs: 1464 | raise UnknownArgument('Unable to mark unknown argument %s ' 'as non-injectable.' % arg) 1465 | 1466 | existing: Set[str] = getattr(function, '__noninjectables__', set()) 1467 | merged = existing | set(args) 1468 | cast(Any, function).__noninjectables__ = merged 1469 | return function 1470 | 1471 | return decorator 1472 | 1473 | 1474 | @private 1475 | def read_and_store_bindings(f: Callable, bindings: Dict[str, type]) -> None: 1476 | function_bindings = getattr(f, '__bindings__', None) or {} 1477 | if function_bindings == 'deferred': 1478 | function_bindings = {} 1479 | merged_bindings = dict(function_bindings, **bindings) 1480 | 1481 | if hasattr(f, '__func__'): 1482 | f = cast(Any, f).__func__ 1483 | cast(Any, f).__bindings__ = merged_bindings 1484 | 1485 | 1486 | class BoundKey(tuple): 1487 | """A BoundKey provides a key to a type with pre-injected arguments. 1488 | 1489 | >>> class A: 1490 | ... def __init__(self, a, b): 1491 | ... self.a = a 1492 | ... self.b = b 1493 | >>> InjectedA = BoundKey(A, a=InstanceProvider(1), b=InstanceProvider(2)) 1494 | >>> injector = Injector() 1495 | >>> a = injector.get(InjectedA) 1496 | >>> a.a, a.b 1497 | (1, 2) 1498 | """ 1499 | 1500 | def __new__(cls, interface: Type[T], **kwargs: Any) -> 'BoundKey': 1501 | kwargs_tuple = tuple(sorted(kwargs.items())) 1502 | return super(BoundKey, cls).__new__(cls, (interface, kwargs_tuple)) # type: ignore 1503 | 1504 | @property 1505 | def interface(self) -> Type[T]: 1506 | return self[0] 1507 | 1508 | @property 1509 | def kwargs(self) -> Dict[str, Any]: 1510 | return dict(self[1]) 1511 | 1512 | 1513 | class AssistedBuilder(Generic[T]): 1514 | def __init__(self, injector: Injector, target: Type[T]) -> None: 1515 | self._injector = injector 1516 | self._target = target 1517 | 1518 | def build(self, **kwargs: Any) -> T: 1519 | binder = self._injector.binder 1520 | binding, _ = binder.get_binding(self._target) 1521 | provider = binding.provider 1522 | if not isinstance(provider, ClassProvider): 1523 | raise Error( 1524 | 'Assisted interface building works only with ClassProviders, ' 1525 | 'got %r for %r' % (provider, binding.interface) 1526 | ) 1527 | 1528 | return self._build_class(cast(Type[T], provider._cls), **kwargs) 1529 | 1530 | def _build_class(self, cls: Type[T], **kwargs: Any) -> T: 1531 | return self._injector.create_object(cls, additional_kwargs=kwargs) 1532 | 1533 | 1534 | class ClassAssistedBuilder(AssistedBuilder[T]): 1535 | def build(self, **kwargs: Any) -> T: 1536 | return self._build_class(self._target, **kwargs) 1537 | 1538 | 1539 | def _describe(c: Any) -> str: 1540 | if hasattr(c, '__name__'): 1541 | return cast(str, c.__name__) 1542 | if type(c) in (tuple, list): 1543 | return '[%s]' % c[0].__name__ 1544 | return str(c) 1545 | 1546 | 1547 | class ProviderOf(Generic[T]): 1548 | """Can be used to get a provider of an interface, for example: 1549 | 1550 | >>> def provide_int(): 1551 | ... print('providing') 1552 | ... return 123 1553 | >>> 1554 | >>> def configure(binder): 1555 | ... binder.bind(int, to=provide_int) 1556 | >>> 1557 | >>> injector = Injector(configure) 1558 | >>> provider = injector.get(ProviderOf[int]) 1559 | >>> value = provider.get() 1560 | providing 1561 | >>> value 1562 | 123 1563 | """ 1564 | 1565 | def __init__(self, injector: Injector, interface: Type[T]): 1566 | self._injector = injector 1567 | self._interface = interface 1568 | 1569 | def __repr__(self) -> str: 1570 | return '%s(%r, %r)' % (type(self).__name__, self._injector, self._interface) 1571 | 1572 | def get(self) -> T: 1573 | """Get an implementation for the specified interface.""" 1574 | return self._injector.get(self._interface) 1575 | 1576 | 1577 | def is_decorated_with_inject(function: Callable[..., Any]) -> bool: 1578 | """See if given callable is declared to want some dependencies injected. 1579 | 1580 | Example use: 1581 | 1582 | >>> def fun(i: int) -> str: 1583 | ... return str(i) 1584 | 1585 | >>> is_decorated_with_inject(fun) 1586 | False 1587 | >>> 1588 | >>> @inject 1589 | ... def fun2(i: int) -> str: 1590 | ... return str(i) 1591 | 1592 | >>> is_decorated_with_inject(fun2) 1593 | True 1594 | """ 1595 | return hasattr(function, '__bindings__') 1596 | -------------------------------------------------------------------------------- /injector/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-injector/injector/a7f0bc4deec968f4ba40dad55458f585a99c6adf/injector/py.typed -------------------------------------------------------------------------------- /injector_test.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # 3 | # Copyright (C) 2010 Alec Thomas 4 | # All rights reserved. 5 | # 6 | # This software is licensed as described in the file COPYING, which 7 | # you should have received as part of this distribution. 8 | # 9 | # Author: Alec Thomas 10 | 11 | """Functional tests for the "Injector" dependency injection framework.""" 12 | 13 | from contextlib import contextmanager 14 | from typing import Any, NewType, Optional, Union 15 | import abc 16 | import sys 17 | import threading 18 | import traceback 19 | import warnings 20 | 21 | if sys.version_info >= (3, 9): 22 | from typing import Annotated 23 | else: 24 | from typing_extensions import Annotated 25 | 26 | from typing import Dict, List, NewType 27 | 28 | import pytest 29 | 30 | from injector import ( 31 | Binder, 32 | CallError, 33 | Inject, 34 | Injector, 35 | NoInject, 36 | Scope, 37 | InstanceProvider, 38 | ClassProvider, 39 | get_bindings, 40 | inject, 41 | multiprovider, 42 | noninjectable, 43 | singleton, 44 | threadlocal, 45 | UnsatisfiedRequirement, 46 | CircularDependency, 47 | Module, 48 | SingletonScope, 49 | ScopeDecorator, 50 | AssistedBuilder, 51 | provider, 52 | ProviderOf, 53 | ClassAssistedBuilder, 54 | Error, 55 | UnknownArgument, 56 | ) 57 | 58 | 59 | class EmptyClass: 60 | pass 61 | 62 | 63 | class DependsOnEmptyClass: 64 | @inject 65 | def __init__(self, b: EmptyClass): 66 | """Construct a new DependsOnEmptyClass.""" 67 | self.b = b 68 | 69 | 70 | def prepare_nested_injectors(): 71 | def configure(binder): 72 | binder.bind(str, to='asd') 73 | 74 | parent = Injector(configure) 75 | child = parent.create_child_injector() 76 | return parent, child 77 | 78 | 79 | def check_exception_contains_stuff(exception, stuff): 80 | stringified = str(exception) 81 | 82 | for thing in stuff: 83 | assert thing in stringified, '%r should be present in the exception representation: %s' % ( 84 | thing, 85 | stringified, 86 | ) 87 | 88 | 89 | def test_child_injector_inherits_parent_bindings(): 90 | parent, child = prepare_nested_injectors() 91 | assert child.get(str) == parent.get(str) 92 | 93 | 94 | def test_child_injector_overrides_parent_bindings(): 95 | parent, child = prepare_nested_injectors() 96 | child.binder.bind(str, to='qwe') 97 | 98 | assert (parent.get(str), child.get(str)) == ('asd', 'qwe') 99 | 100 | 101 | def test_child_injector_rebinds_arguments_for_parent_scope(): 102 | class Cls: 103 | val = "" 104 | 105 | class A(Cls): 106 | @inject 107 | def __init__(self, val: str): 108 | self.val = val 109 | 110 | def configure_parent(binder): 111 | binder.bind(Cls, to=A) 112 | binder.bind(str, to="Parent") 113 | 114 | def configure_child(binder): 115 | binder.bind(str, to="Child") 116 | 117 | parent = Injector(configure_parent) 118 | assert parent.get(Cls).val == "Parent" 119 | child = parent.create_child_injector(configure_child) 120 | assert child.get(Cls).val == "Child" 121 | 122 | 123 | def test_scopes_are_only_bound_to_root_injector(): 124 | parent, child = prepare_nested_injectors() 125 | 126 | class A: 127 | pass 128 | 129 | parent.binder.bind(A, to=A, scope=singleton) 130 | assert parent.get(A) is child.get(A) 131 | 132 | 133 | def test_get_default_injected_instances(): 134 | def configure(binder): 135 | binder.bind(DependsOnEmptyClass) 136 | binder.bind(EmptyClass) 137 | 138 | injector = Injector(configure) 139 | assert injector.get(Injector) is injector 140 | assert injector.get(Binder) is injector.binder 141 | 142 | 143 | def test_instantiate_injected_method(): 144 | a = DependsOnEmptyClass('Bob') 145 | assert a.b == 'Bob' 146 | 147 | 148 | def test_method_decorator_is_wrapped(): 149 | assert DependsOnEmptyClass.__init__.__doc__ == 'Construct a new DependsOnEmptyClass.' 150 | assert DependsOnEmptyClass.__init__.__name__ == '__init__' 151 | 152 | 153 | def test_decorator_works_for_function_with_no_args(): 154 | @inject 155 | def wrapped(*args, **kwargs): 156 | pass 157 | 158 | 159 | def test_providers_arent_called_for_dependencies_that_are_already_provided(): 160 | def configure(binder): 161 | binder.bind(int, to=lambda: 1 / 0) 162 | 163 | class A: 164 | @inject 165 | def __init__(self, i: int): 166 | pass 167 | 168 | injector = Injector(configure) 169 | builder = injector.get(AssistedBuilder[A]) 170 | 171 | with pytest.raises(ZeroDivisionError): 172 | builder.build() 173 | 174 | builder.build(i=3) 175 | 176 | 177 | def test_inject_direct(): 178 | def configure(binder): 179 | binder.bind(DependsOnEmptyClass) 180 | binder.bind(EmptyClass) 181 | 182 | injector = Injector(configure) 183 | a = injector.get(DependsOnEmptyClass) 184 | assert isinstance(a, DependsOnEmptyClass) 185 | assert isinstance(a.b, EmptyClass) 186 | 187 | 188 | def test_configure_multiple_modules(): 189 | def configure_a(binder): 190 | binder.bind(DependsOnEmptyClass) 191 | 192 | def configure_b(binder): 193 | binder.bind(EmptyClass) 194 | 195 | injector = Injector([configure_a, configure_b]) 196 | a = injector.get(DependsOnEmptyClass) 197 | assert isinstance(a, DependsOnEmptyClass) 198 | assert isinstance(a.b, EmptyClass) 199 | 200 | 201 | def test_inject_with_missing_dependency(): 202 | def configure(binder): 203 | binder.bind(DependsOnEmptyClass) 204 | 205 | injector = Injector(configure, auto_bind=False) 206 | with pytest.raises(UnsatisfiedRequirement): 207 | injector.get(EmptyClass) 208 | 209 | 210 | def test_inject_named_interface(): 211 | class A: 212 | @inject 213 | def __init__(self, b: EmptyClass): 214 | self.b = b 215 | 216 | def configure(binder): 217 | binder.bind(A) 218 | binder.bind(EmptyClass) 219 | 220 | injector = Injector(configure) 221 | a = injector.get(A) 222 | assert isinstance(a, A) 223 | assert isinstance(a.b, EmptyClass) 224 | 225 | 226 | class TransitiveC: 227 | pass 228 | 229 | 230 | class TransitiveB: 231 | @inject 232 | def __init__(self, c: TransitiveC): 233 | self.c = c 234 | 235 | 236 | class TransitiveA: 237 | @inject 238 | def __init__(self, b: TransitiveB): 239 | self.b = b 240 | 241 | 242 | def test_transitive_injection(): 243 | def configure(binder): 244 | binder.bind(TransitiveA) 245 | binder.bind(TransitiveB) 246 | binder.bind(TransitiveC) 247 | 248 | injector = Injector(configure) 249 | a = injector.get(TransitiveA) 250 | assert isinstance(a, TransitiveA) 251 | assert isinstance(a.b, TransitiveB) 252 | assert isinstance(a.b.c, TransitiveC) 253 | 254 | 255 | def test_transitive_injection_with_missing_dependency(): 256 | def configure(binder): 257 | binder.bind(TransitiveA) 258 | binder.bind(TransitiveB) 259 | 260 | injector = Injector(configure, auto_bind=False) 261 | with pytest.raises(UnsatisfiedRequirement): 262 | injector.get(TransitiveA) 263 | with pytest.raises(UnsatisfiedRequirement): 264 | injector.get(TransitiveB) 265 | 266 | 267 | def test_inject_singleton(): 268 | class A: 269 | @inject 270 | def __init__(self, b: EmptyClass): 271 | self.b = b 272 | 273 | def configure(binder): 274 | binder.bind(A) 275 | binder.bind(EmptyClass, scope=SingletonScope) 276 | 277 | injector1 = Injector(configure) 278 | a1 = injector1.get(A) 279 | a2 = injector1.get(A) 280 | assert a1.b is a2.b 281 | 282 | 283 | @singleton 284 | class SingletonB: 285 | pass 286 | 287 | 288 | def test_inject_decorated_singleton_class(): 289 | class A: 290 | @inject 291 | def __init__(self, b: SingletonB): 292 | self.b = b 293 | 294 | def configure(binder): 295 | binder.bind(A) 296 | binder.bind(SingletonB) 297 | 298 | injector1 = Injector(configure) 299 | a1 = injector1.get(A) 300 | a2 = injector1.get(A) 301 | assert a1.b is a2.b 302 | assert a1 is not a2 303 | 304 | 305 | def test_injecting_an_auto_bound_decorated_singleton_class(): 306 | class A: 307 | @inject 308 | def __init__(self, b: SingletonB): 309 | self.b = b 310 | 311 | injector1 = Injector() 312 | a1 = injector1.get(A) 313 | a2 = injector1.get(A) 314 | assert a1.b is a2.b 315 | assert a1 is not a2 316 | 317 | 318 | def test_a_decorated_singleton_is_shared_between_parent_and_child_injectors_when_parent_creates_it_first(): 319 | parent_injector = Injector() 320 | 321 | child_injector = parent_injector.create_child_injector() 322 | 323 | assert parent_injector.get(SingletonB) is child_injector.get(SingletonB) 324 | 325 | 326 | def test_a_decorated_singleton_is_shared_between_parent_and_child_injectors_when_child_creates_it_first(): 327 | parent_injector = Injector() 328 | 329 | child_injector = parent_injector.create_child_injector() 330 | 331 | assert child_injector.get(SingletonB) is parent_injector.get(SingletonB) 332 | 333 | 334 | # Test for https://github.com/python-injector/injector/issues/207 335 | def test_a_decorated_singleton_is_shared_among_child_injectors(): 336 | parent_injector = Injector() 337 | 338 | child_injector_1 = parent_injector.create_child_injector() 339 | child_injector_2 = parent_injector.create_child_injector() 340 | 341 | assert child_injector_1.get(SingletonB) is child_injector_2.get(SingletonB) 342 | 343 | 344 | def test_a_decorated_singleton_should_not_override_explicit_binds(): 345 | parent_injector = Injector() 346 | 347 | child_injector = parent_injector.create_child_injector() 348 | grand_child_injector = child_injector.create_child_injector() 349 | 350 | bound_singleton = SingletonB() 351 | child_injector.binder.bind(SingletonB, to=bound_singleton) 352 | 353 | assert parent_injector.get(SingletonB) is not bound_singleton 354 | assert child_injector.get(SingletonB) is bound_singleton 355 | assert grand_child_injector.get(SingletonB) is bound_singleton 356 | 357 | 358 | def test_binding_a_singleton_to_a_child_injector_does_not_affect_the_parent_injector(): 359 | parent_injector = Injector() 360 | 361 | child_injector = parent_injector.create_child_injector() 362 | child_injector.binder.bind(EmptyClass, scope=singleton) 363 | 364 | assert child_injector.get(EmptyClass) is child_injector.get(EmptyClass) 365 | assert child_injector.get(EmptyClass) is not parent_injector.get(EmptyClass) 366 | assert parent_injector.get(EmptyClass) is not parent_injector.get(EmptyClass) 367 | 368 | 369 | def test_a_decorated_singleton_should_not_override_a_child_provider(): 370 | parent_injector = Injector() 371 | 372 | provided_instance = SingletonB() 373 | 374 | class MyModule(Module): 375 | @provider 376 | def provide_name(self) -> SingletonB: 377 | return provided_instance 378 | 379 | child_injector = parent_injector.create_child_injector([MyModule]) 380 | 381 | assert child_injector.get(SingletonB) is provided_instance 382 | assert parent_injector.get(SingletonB) is not provided_instance 383 | assert parent_injector.get(SingletonB) is parent_injector.get(SingletonB) 384 | 385 | 386 | # Test for https://github.com/python-injector/injector/issues/207 387 | def test_a_decorated_singleton_is_created_as_close_to_the_root_where_dependencies_fulfilled(): 388 | class NonInjectableD: 389 | @inject 390 | def __init__(self, required) -> None: 391 | self.required = required 392 | 393 | @singleton 394 | class SingletonC: 395 | @inject 396 | def __init__(self, d: NonInjectableD): 397 | self.d = d 398 | 399 | parent_injector = Injector() 400 | 401 | child_injector_1 = parent_injector.create_child_injector() 402 | 403 | child_injector_2 = parent_injector.create_child_injector() 404 | child_injector_2_1 = child_injector_2.create_child_injector() 405 | 406 | provided_d = NonInjectableD(required=True) 407 | child_injector_2.binder.bind(NonInjectableD, to=provided_d) 408 | 409 | assert child_injector_2_1.get(SingletonC) is child_injector_2.get(SingletonC) 410 | assert child_injector_2.get(SingletonC).d is provided_d 411 | 412 | with pytest.raises(CallError): 413 | parent_injector.get(SingletonC) 414 | 415 | with pytest.raises(CallError): 416 | child_injector_1.get(SingletonC) 417 | 418 | 419 | def test_a_bound_decorated_singleton_is_created_as_close_to_the_root_where_it_exists_when_auto_bind_is_disabled(): 420 | parent_injector = Injector(auto_bind=False) 421 | 422 | child_injector_1 = parent_injector.create_child_injector(auto_bind=False) 423 | 424 | child_injector_2 = parent_injector.create_child_injector(auto_bind=False) 425 | child_injector_2_1 = child_injector_2.create_child_injector(auto_bind=False) 426 | 427 | child_injector_2.binder.bind(SingletonB) 428 | 429 | assert child_injector_2_1.get(SingletonB) is child_injector_2_1.get(SingletonB) 430 | assert child_injector_2_1.get(SingletonB) is child_injector_2.get(SingletonB) 431 | 432 | with pytest.raises(UnsatisfiedRequirement): 433 | parent_injector.get(SingletonB) 434 | 435 | with pytest.raises(UnsatisfiedRequirement): 436 | child_injector_1.get(SingletonB) 437 | 438 | 439 | def test_threadlocal(): 440 | @threadlocal 441 | class A: 442 | def __init__(self): 443 | pass 444 | 445 | def configure(binder): 446 | binder.bind(A) 447 | 448 | injector = Injector(configure) 449 | a1 = injector.get(A) 450 | a2 = injector.get(A) 451 | 452 | assert a1 is a2 453 | 454 | a3 = [None] 455 | ready = threading.Event() 456 | 457 | def inject_a3(): 458 | a3[0] = injector.get(A) 459 | ready.set() 460 | 461 | threading.Thread(target=inject_a3).start() 462 | ready.wait(1.0) 463 | 464 | assert a2 is not a3[0] and a3[0] is not None 465 | 466 | 467 | class Interface2: 468 | pass 469 | 470 | 471 | def test_injecting_interface_implementation(): 472 | class Implementation: 473 | pass 474 | 475 | class A: 476 | @inject 477 | def __init__(self, i: Interface2): 478 | self.i = i 479 | 480 | def configure(binder): 481 | binder.bind(A) 482 | binder.bind(Interface2, to=Implementation) 483 | 484 | injector = Injector(configure) 485 | a = injector.get(A) 486 | assert isinstance(a.i, Implementation) 487 | 488 | 489 | class CyclicInterface: 490 | pass 491 | 492 | 493 | class CyclicA: 494 | @inject 495 | def __init__(self, i: CyclicInterface): 496 | self.i = i 497 | 498 | 499 | class CyclicB: 500 | @inject 501 | def __init__(self, a: CyclicA): 502 | self.a = a 503 | 504 | 505 | def test_cyclic_dependencies(): 506 | def configure(binder): 507 | binder.bind(CyclicInterface, to=CyclicB) 508 | binder.bind(CyclicA) 509 | 510 | injector = Injector(configure) 511 | with pytest.raises(CircularDependency): 512 | injector.get(CyclicA) 513 | 514 | 515 | class CyclicInterface2: 516 | pass 517 | 518 | 519 | class CyclicA2: 520 | @inject 521 | def __init__(self, i: CyclicInterface2): 522 | self.i = i 523 | 524 | 525 | class CyclicB2: 526 | @inject 527 | def __init__(self, a_builder: AssistedBuilder[CyclicA2]): 528 | self.a = a_builder.build(i=self) 529 | 530 | 531 | def test_dependency_cycle_can_be_worked_broken_by_assisted_building(): 532 | def configure(binder): 533 | binder.bind(CyclicInterface2, to=CyclicB2) 534 | binder.bind(CyclicA2) 535 | 536 | injector = Injector(configure) 537 | 538 | # Previously it'd detect a circular dependency here: 539 | # 1. Constructing CyclicA2 requires CyclicInterface2 (bound to CyclicB2) 540 | # 2. Constructing CyclicB2 requires assisted build of CyclicA2 541 | # 3. Constructing CyclicA2 triggers circular dependency check 542 | assert isinstance(injector.get(CyclicA2), CyclicA2) 543 | 544 | 545 | class Interface5: 546 | constructed = False 547 | 548 | def __init__(self): 549 | Interface5.constructed = True 550 | 551 | 552 | def test_that_injection_is_lazy(): 553 | class A: 554 | @inject 555 | def __init__(self, i: Interface5): 556 | self.i = i 557 | 558 | def configure(binder): 559 | binder.bind(Interface5) 560 | binder.bind(A) 561 | 562 | injector = Injector(configure) 563 | assert not (Interface5.constructed) 564 | injector.get(A) 565 | assert Interface5.constructed 566 | 567 | 568 | def test_module_provider(): 569 | class MyModule(Module): 570 | @provider 571 | def provide_name(self) -> str: 572 | return 'Bob' 573 | 574 | module = MyModule() 575 | injector = Injector(module) 576 | assert injector.get(str) == 'Bob' 577 | 578 | 579 | def test_module_class_gets_instantiated(): 580 | name = 'Meg' 581 | 582 | class MyModule(Module): 583 | def configure(self, binder): 584 | binder.bind(str, to=name) 585 | 586 | injector = Injector(MyModule) 587 | assert injector.get(str) == name 588 | 589 | 590 | def test_inject_and_provide_coexist_happily(): 591 | class MyModule(Module): 592 | @provider 593 | def provide_weight(self) -> float: 594 | return 50.0 595 | 596 | @provider 597 | def provide_age(self) -> int: 598 | return 25 599 | 600 | # TODO(alec) Make provider/inject order independent. 601 | @provider 602 | @inject 603 | def provide_description(self, age: int, weight: float) -> str: 604 | return 'Bob is %d and weighs %0.1fkg' % (age, weight) 605 | 606 | assert Injector(MyModule()).get(str) == 'Bob is 25 and weighs 50.0kg' 607 | 608 | 609 | Names = NewType('Names', List[str]) 610 | Passwords = NewType('Ages', Dict[str, str]) 611 | 612 | 613 | def test_multibind(): 614 | # First let's have some explicit multibindings 615 | def configure(binder): 616 | binder.multibind(List[str], to=['not a name']) 617 | binder.multibind(Dict[str, str], to={'asd': 'qwe'}) 618 | # To make sure Lists and Dicts of different subtypes are treated distinctly 619 | binder.multibind(List[int], to=[1, 2, 3]) 620 | binder.multibind(Dict[str, int], to={'weight': 12}) 621 | # To see that NewTypes are treated distinctly 622 | binder.multibind(Names, to=['Bob']) 623 | binder.multibind(Passwords, to={'Bob': 'password1'}) 624 | 625 | # Then @multiprovider-decorated Module methods 626 | class CustomModule(Module): 627 | @multiprovider 628 | def provide_some_ints(self) -> List[int]: 629 | return [4, 5, 6] 630 | 631 | @multiprovider 632 | def provide_some_strs(self) -> List[str]: 633 | return ['not a name either'] 634 | 635 | @multiprovider 636 | def provide_str_to_str_mapping(self) -> Dict[str, str]: 637 | return {'xxx': 'yyy'} 638 | 639 | @multiprovider 640 | def provide_str_to_int_mapping(self) -> Dict[str, int]: 641 | return {'height': 33} 642 | 643 | @multiprovider 644 | def provide_names(self) -> Names: 645 | return ['Alice', 'Clarice'] 646 | 647 | @multiprovider 648 | def provide_passwords(self) -> Passwords: 649 | return {'Alice': 'aojrioeg3', 'Clarice': 'clarice30'} 650 | 651 | injector = Injector([configure, CustomModule]) 652 | assert injector.get(List[str]) == ['not a name', 'not a name either'] 653 | assert injector.get(List[int]) == [1, 2, 3, 4, 5, 6] 654 | assert injector.get(Dict[str, str]) == {'asd': 'qwe', 'xxx': 'yyy'} 655 | assert injector.get(Dict[str, int]) == {'weight': 12, 'height': 33} 656 | assert injector.get(Names) == ['Bob', 'Alice', 'Clarice'] 657 | assert injector.get(Passwords) == {'Bob': 'password1', 'Alice': 'aojrioeg3', 'Clarice': 'clarice30'} 658 | 659 | 660 | def test_regular_bind_and_provider_dont_work_with_multibind(): 661 | # We only want multibind and multiprovider to work to avoid confusion 662 | 663 | Names = NewType('Names', List[str]) 664 | Passwords = NewType('Passwords', Dict[str, str]) 665 | 666 | class MyModule(Module): 667 | with pytest.raises(Error): 668 | 669 | @provider 670 | def provide_strs(self) -> List[str]: 671 | return [] 672 | 673 | with pytest.raises(Error): 674 | 675 | @provider 676 | def provide_names(self) -> Names: 677 | return [] 678 | 679 | with pytest.raises(Error): 680 | 681 | @provider 682 | def provide_strs_in_dict(self) -> Dict[str, str]: 683 | return {} 684 | 685 | with pytest.raises(Error): 686 | 687 | @provider 688 | def provide_passwords(self) -> Passwords: 689 | return {} 690 | 691 | injector = Injector() 692 | binder = injector.binder 693 | 694 | with pytest.raises(Error): 695 | binder.bind(List[str], to=[]) 696 | 697 | with pytest.raises(Error): 698 | binder.bind(Names, to=[]) 699 | 700 | with pytest.raises(Error): 701 | binder.bind(Dict[str, str], to={}) 702 | 703 | with pytest.raises(Error): 704 | binder.bind(Passwords, to={}) 705 | 706 | 707 | def test_auto_bind(): 708 | class A: 709 | pass 710 | 711 | injector = Injector() 712 | assert isinstance(injector.get(A), A) 713 | 714 | 715 | def test_auto_bind_with_newtype(): 716 | # Reported in https://github.com/alecthomas/injector/issues/117 717 | class A: 718 | pass 719 | 720 | AliasOfA = NewType('AliasOfA', A) 721 | injector = Injector() 722 | assert isinstance(injector.get(AliasOfA), A) 723 | 724 | 725 | class Request: 726 | pass 727 | 728 | 729 | class RequestScope(Scope): 730 | def configure(self): 731 | self.context = None 732 | 733 | @contextmanager 734 | def __call__(self, request): 735 | assert self.context is None 736 | self.context = {} 737 | binder = self.injector.get(Binder) 738 | binder.bind(Request, to=request, scope=RequestScope) 739 | yield 740 | self.context = None 741 | 742 | def get(self, key, provider): 743 | if self.context is None: 744 | raise UnsatisfiedRequirement(None, key) 745 | try: 746 | return self.context[key] 747 | except KeyError: 748 | provider = InstanceProvider(provider.get(self.injector)) 749 | self.context[key] = provider 750 | return provider 751 | 752 | 753 | request = ScopeDecorator(RequestScope) 754 | 755 | 756 | @request 757 | class Handler: 758 | def __init__(self, request): 759 | self.request = request 760 | 761 | 762 | class RequestModule(Module): 763 | @provider 764 | @inject 765 | def handler(self, request: Request) -> Handler: 766 | return Handler(request) 767 | 768 | 769 | def test_custom_scope(): 770 | injector = Injector([RequestModule()], auto_bind=False) 771 | 772 | with pytest.raises(UnsatisfiedRequirement): 773 | injector.get(Handler) 774 | 775 | scope = injector.get(RequestScope) 776 | request = Request() 777 | 778 | with scope(request): 779 | handler = injector.get(Handler) 780 | assert handler.request is request 781 | 782 | with pytest.raises(UnsatisfiedRequirement): 783 | injector.get(Handler) 784 | 785 | 786 | def test_binder_install(): 787 | class ModuleA(Module): 788 | def configure(self, binder): 789 | binder.bind(str, to='hello world') 790 | 791 | class ModuleB(Module): 792 | def configure(self, binder): 793 | binder.install(ModuleA()) 794 | 795 | injector = Injector([ModuleB()]) 796 | assert injector.get(str) == 'hello world' 797 | 798 | 799 | def test_binder_provider_for_method_with_explicit_provider(): 800 | injector = Injector() 801 | binder = injector.binder 802 | provider = binder.provider_for(int, to=InstanceProvider(1)) 803 | assert type(provider) is InstanceProvider 804 | assert provider.get(injector) == 1 805 | 806 | 807 | def test_binder_provider_for_method_with_instance(): 808 | injector = Injector() 809 | binder = injector.binder 810 | provider = binder.provider_for(int, to=1) 811 | assert type(provider) is InstanceProvider 812 | assert provider.get(injector) == 1 813 | 814 | 815 | def test_binder_provider_for_method_with_class(): 816 | injector = Injector() 817 | binder = injector.binder 818 | provider = binder.provider_for(int) 819 | assert type(provider) is ClassProvider 820 | assert provider.get(injector) == 0 821 | 822 | 823 | def test_binder_provider_for_method_with_class_to_specific_subclass(): 824 | class A: 825 | pass 826 | 827 | class B(A): 828 | pass 829 | 830 | injector = Injector() 831 | binder = injector.binder 832 | provider = binder.provider_for(A, B) 833 | assert type(provider) is ClassProvider 834 | assert isinstance(provider.get(injector), B) 835 | 836 | 837 | def test_binder_provider_for_type_with_metaclass(): 838 | # use a metaclass cross python2/3 way 839 | # otherwise should be: 840 | # class A(object, metaclass=abc.ABCMeta): 841 | # passa 842 | A = abc.ABCMeta('A', (object,), {}) 843 | 844 | injector = Injector() 845 | binder = injector.binder 846 | assert isinstance(binder.provider_for(A, None).get(injector), A) 847 | 848 | 849 | class ClassA: 850 | def __init__(self, parameter): 851 | pass 852 | 853 | 854 | class ClassB: 855 | @inject 856 | def __init__(self, a: ClassA): 857 | pass 858 | 859 | 860 | def test_injecting_undecorated_class_with_missing_dependencies_raises_the_right_error(): 861 | injector = Injector() 862 | try: 863 | injector.get(ClassB) 864 | except CallError as ce: 865 | check_exception_contains_stuff(ce, ('ClassA.__init__', 'ClassB')) 866 | 867 | 868 | def test_call_to_method_with_legitimate_call_error_raises_type_error(): 869 | class A: 870 | def __init__(self): 871 | max() 872 | 873 | injector = Injector() 874 | with pytest.raises(TypeError): 875 | injector.get(A) 876 | 877 | 878 | def test_call_error_str_representation_handles_single_arg(): 879 | ce = CallError('zxc') 880 | assert str(ce) == 'zxc' 881 | 882 | 883 | class NeedsAssistance: 884 | @inject 885 | def __init__(self, a: str, b): 886 | self.a = a 887 | self.b = b 888 | 889 | 890 | def test_assisted_builder_works_when_got_directly_from_injector(): 891 | injector = Injector() 892 | builder = injector.get(AssistedBuilder[NeedsAssistance]) 893 | obj = builder.build(b=123) 894 | assert (obj.a, obj.b) == (str(), 123) 895 | 896 | 897 | def test_assisted_builder_works_when_injected(): 898 | class X: 899 | @inject 900 | def __init__(self, builder: AssistedBuilder[NeedsAssistance]): 901 | self.obj = builder.build(b=234) 902 | 903 | injector = Injector() 904 | x = injector.get(X) 905 | assert (x.obj.a, x.obj.b) == (str(), 234) 906 | 907 | 908 | class Interface: 909 | b = 0 910 | 911 | 912 | def test_assisted_builder_uses_bindings(): 913 | def configure(binder): 914 | binder.bind(Interface, to=NeedsAssistance) 915 | 916 | injector = Injector(configure) 917 | builder = injector.get(AssistedBuilder[Interface]) 918 | x = builder.build(b=333) 919 | assert (type(x), x.b) == (NeedsAssistance, 333) 920 | 921 | 922 | def test_assisted_builder_uses_concrete_class_when_specified(): 923 | class X: 924 | pass 925 | 926 | def configure(binder): 927 | # meant only to show that provider isn't called 928 | binder.bind(X, to=lambda: 1 / 0) 929 | 930 | injector = Injector(configure) 931 | builder = injector.get(ClassAssistedBuilder[X]) 932 | builder.build() 933 | 934 | 935 | def test_assisted_builder_injection_is_safe_to_use_with_multiple_injectors(): 936 | class X: 937 | @inject 938 | def __init__(self, builder: AssistedBuilder[NeedsAssistance]): 939 | self.builder = builder 940 | 941 | i1, i2 = Injector(), Injector() 942 | b1 = i1.get(X).builder 943 | b2 = i2.get(X).builder 944 | assert (b1._injector, b2._injector) == (i1, i2) 945 | 946 | 947 | def test_assisted_builder_injection_is_safe_to_use_with_child_injectors(): 948 | class X: 949 | @inject 950 | def __init__(self, builder: AssistedBuilder[NeedsAssistance]): 951 | self.builder = builder 952 | 953 | i1 = Injector() 954 | i2 = i1.create_child_injector() 955 | b1 = i1.get(X).builder 956 | b2 = i2.get(X).builder 957 | assert (b1._injector, b2._injector) == (i1, i2) 958 | 959 | 960 | class TestThreadSafety: 961 | def setup_method(self): 962 | self.event = threading.Event() 963 | 964 | def configure(binder): 965 | binder.bind(str, to=lambda: self.event.wait() and 'this is str') 966 | 967 | class XXX: 968 | @inject 969 | def __init__(self, s: str): 970 | pass 971 | 972 | self.injector = Injector(configure) 973 | self.cls = XXX 974 | 975 | def gather_results(self, count): 976 | objects = [] 977 | lock = threading.Lock() 978 | 979 | def target(): 980 | o = self.injector.get(self.cls) 981 | with lock: 982 | objects.append(o) 983 | 984 | threads = [threading.Thread(target=target) for i in range(count)] 985 | 986 | for t in threads: 987 | t.start() 988 | 989 | self.event.set() 990 | 991 | for t in threads: 992 | t.join() 993 | 994 | return objects 995 | 996 | def test_injection_is_thread_safe(self): 997 | objects = self.gather_results(2) 998 | assert len(objects) == 2 999 | 1000 | def test_singleton_scope_is_thread_safe(self): 1001 | self.injector.binder.bind(self.cls, scope=singleton) 1002 | a, b = self.gather_results(2) 1003 | assert a is b 1004 | 1005 | 1006 | def test_provider_and_scope_decorator_collaboration(): 1007 | @provider 1008 | @singleton 1009 | def provider_singleton() -> int: 1010 | return 10 1011 | 1012 | @singleton 1013 | @provider 1014 | def singleton_provider() -> int: 1015 | return 10 1016 | 1017 | assert provider_singleton.__binding__.scope == SingletonScope 1018 | assert singleton_provider.__binding__.scope == SingletonScope 1019 | 1020 | 1021 | def test_injecting_into_method_of_object_that_is_falseish_works(): 1022 | # regression test 1023 | 1024 | class X(dict): 1025 | @inject 1026 | def __init__(self, s: str): 1027 | pass 1028 | 1029 | injector = Injector() 1030 | injector.get(X) 1031 | 1032 | 1033 | Name = NewType("Name", str) 1034 | Message = NewType("Message", str) 1035 | 1036 | 1037 | def test_callable_provider_injection(): 1038 | @inject 1039 | def create_message(name: Name): 1040 | return "Hello, " + name 1041 | 1042 | def configure(binder): 1043 | binder.bind(Name, to="John") 1044 | binder.bind(Message, to=create_message) 1045 | 1046 | injector = Injector([configure]) 1047 | msg = injector.get(Message) 1048 | assert msg == "Hello, John" 1049 | 1050 | 1051 | def test_providerof(): 1052 | counter = [0] 1053 | 1054 | def provide_str(): 1055 | counter[0] += 1 1056 | return 'content' 1057 | 1058 | def configure(binder): 1059 | binder.bind(str, to=provide_str) 1060 | 1061 | injector = Injector(configure) 1062 | 1063 | assert counter[0] == 0 1064 | 1065 | provider = injector.get(ProviderOf[str]) 1066 | assert counter[0] == 0 1067 | 1068 | assert provider.get() == 'content' 1069 | assert counter[0] == 1 1070 | 1071 | assert provider.get() == injector.get(str) 1072 | assert counter[0] == 3 1073 | 1074 | 1075 | def test_providerof_cannot_be_bound(): 1076 | def configure(binder): 1077 | binder.bind(ProviderOf[int], to=InstanceProvider(None)) 1078 | 1079 | with pytest.raises(Exception): 1080 | Injector(configure) 1081 | 1082 | 1083 | def test_providerof_is_safe_to_use_with_multiple_injectors(): 1084 | def configure1(binder): 1085 | binder.bind(int, to=1) 1086 | 1087 | def configure2(binder): 1088 | binder.bind(int, to=2) 1089 | 1090 | injector1 = Injector(configure1) 1091 | injector2 = Injector(configure2) 1092 | 1093 | provider_of = ProviderOf[int] 1094 | 1095 | provider1 = injector1.get(provider_of) 1096 | provider2 = injector2.get(provider_of) 1097 | 1098 | assert provider1.get() == 1 1099 | assert provider2.get() == 2 1100 | 1101 | 1102 | def test_special_interfaces_work_with_auto_bind_disabled(): 1103 | class InjectMe: 1104 | pass 1105 | 1106 | def configure(binder): 1107 | binder.bind(InjectMe, to=InstanceProvider(InjectMe())) 1108 | 1109 | injector = Injector(configure, auto_bind=False) 1110 | 1111 | # This line used to fail with: 1112 | # Traceback (most recent call last): 1113 | # File "/projects/injector/injector_test.py", line 1171, 1114 | # in test_auto_bind_disabled_regressions 1115 | # injector.get(ProviderOf(InjectMe)) 1116 | # File "/projects/injector/injector.py", line 687, in get 1117 | # binding = self.binder.get_binding(None, key) 1118 | # File "/projects/injector/injector.py", line 459, in get_binding 1119 | # raise UnsatisfiedRequirement(cls, key) 1120 | # UnsatisfiedRequirement: unsatisfied requirement on 1121 | # 1122 | injector.get(ProviderOf[InjectMe]) 1123 | 1124 | # This used to fail with an error similar to the ProviderOf one 1125 | injector.get(ClassAssistedBuilder[InjectMe]) 1126 | 1127 | 1128 | def test_binding_an_instance_regression(): 1129 | text = b'hello'.decode() 1130 | 1131 | def configure(binder): 1132 | # Yes, this binding doesn't make sense strictly speaking but 1133 | # it's just a sample case. 1134 | binder.bind(bytes, to=text) 1135 | 1136 | injector = Injector(configure) 1137 | # This used to return empty bytes instead of the expected string 1138 | assert injector.get(bytes) == text 1139 | 1140 | 1141 | class PartialB: 1142 | @inject 1143 | def __init__(self, a: EmptyClass, b: str): 1144 | self.a = a 1145 | self.b = b 1146 | 1147 | 1148 | def test_class_assisted_builder_of_partially_injected_class_old(): 1149 | class C: 1150 | @inject 1151 | def __init__(self, a: EmptyClass, builder: ClassAssistedBuilder[PartialB]): 1152 | self.a = a 1153 | self.b = builder.build(b='C') 1154 | 1155 | c = Injector().get(C) 1156 | assert isinstance(c, C) 1157 | assert isinstance(c.b, PartialB) 1158 | assert isinstance(c.b.a, EmptyClass) 1159 | 1160 | 1161 | class ImplicitA: 1162 | pass 1163 | 1164 | 1165 | class ImplicitB: 1166 | @inject 1167 | def __init__(self, a: ImplicitA): 1168 | self.a = a 1169 | 1170 | 1171 | class ImplicitC: 1172 | @inject 1173 | def __init__(self, b: ImplicitB): 1174 | self.b = b 1175 | 1176 | 1177 | def test_implicit_injection_for_python3(): 1178 | injector = Injector() 1179 | c = injector.get(ImplicitC) 1180 | assert isinstance(c, ImplicitC) 1181 | assert isinstance(c.b, ImplicitB) 1182 | assert isinstance(c.b.a, ImplicitA) 1183 | 1184 | 1185 | def test_annotation_based_injection_works_in_provider_methods(): 1186 | class MyModule(Module): 1187 | def configure(self, binder): 1188 | binder.bind(int, to=42) 1189 | 1190 | @provider 1191 | def provide_str(self, i: int) -> str: 1192 | return str(i) 1193 | 1194 | @singleton 1195 | @provider 1196 | def provide_object(self) -> object: 1197 | return object() 1198 | 1199 | injector = Injector(MyModule) 1200 | assert injector.get(str) == '42' 1201 | assert injector.get(object) is injector.get(object) 1202 | 1203 | 1204 | class Fetcher: 1205 | def fetch(self, user_id): 1206 | assert user_id == 333 1207 | return {'name': 'John'} 1208 | 1209 | 1210 | class Processor: 1211 | @noninjectable('provider_id') 1212 | @inject 1213 | @noninjectable('user_id') 1214 | def __init__(self, fetcher: Fetcher, user_id: int, provider_id: str): 1215 | assert provider_id == 'not injected' 1216 | data = fetcher.fetch(user_id) 1217 | self.name = data['name'] 1218 | 1219 | 1220 | def test_assisted_building_is_supported(): 1221 | def configure(binder): 1222 | binder.bind(int, to=897) 1223 | binder.bind(str, to='injected') 1224 | 1225 | injector = Injector(configure) 1226 | processor_builder = injector.get(AssistedBuilder[Processor]) 1227 | 1228 | with pytest.raises(CallError): 1229 | processor_builder.build() 1230 | 1231 | processor = processor_builder.build(user_id=333, provider_id='not injected') 1232 | assert processor.name == 'John' 1233 | 1234 | 1235 | def test_raises_when_noninjectable_arguments_defined_with_invalid_arguments(): 1236 | with pytest.raises(UnknownArgument): 1237 | 1238 | class A: 1239 | @inject 1240 | @noninjectable('c') 1241 | def __init__(self, b: str): 1242 | self.b = b 1243 | 1244 | 1245 | def test_can_create_instance_with_untyped_noninjectable_argument(): 1246 | class Parent: 1247 | @inject 1248 | @noninjectable('child1', 'child2') 1249 | def __init__(self, child1, *, child2): 1250 | self.child1 = child1 1251 | self.child2 = child2 1252 | 1253 | injector = Injector() 1254 | parent_builder = injector.get(AssistedBuilder[Parent]) 1255 | parent = parent_builder.build(child1='injected1', child2='injected2') 1256 | 1257 | assert parent.child1 == 'injected1' 1258 | assert parent.child2 == 'injected2' 1259 | 1260 | 1261 | def test_implicit_injection_fails_when_annotations_are_missing(): 1262 | class A: 1263 | def __init__(self, n): 1264 | self.n = n 1265 | 1266 | injector = Injector() 1267 | with pytest.raises(CallError): 1268 | injector.get(A) 1269 | 1270 | 1271 | def test_injection_works_in_presence_of_return_value_annotation(): 1272 | # Code with PEP 484-compatible type hints will have __init__ methods 1273 | # annotated as returning None[1] and this didn't work well with Injector. 1274 | # 1275 | # [1] https://www.python.org/dev/peps/pep-0484/#the-meaning-of-annotations 1276 | 1277 | class A: 1278 | @inject 1279 | def __init__(self, s: str) -> None: 1280 | self.s = s 1281 | 1282 | def configure(binder): 1283 | binder.bind(str, to='this is string') 1284 | 1285 | injector = Injector([configure]) 1286 | 1287 | # Used to fail with: 1288 | # injector.UnknownProvider: couldn't determine provider for None to None 1289 | a = injector.get(A) 1290 | 1291 | # Just a sanity check, if the code above worked we're almost certain 1292 | # we're good but just in case the return value annotation handling changed 1293 | # something: 1294 | assert a.s == 'this is string' 1295 | 1296 | 1297 | def test_things_dont_break_in_presence_of_args_or_kwargs(): 1298 | class A: 1299 | @inject 1300 | def __init__(self, s: str, *args: int, **kwargs: str): 1301 | assert not args 1302 | assert not kwargs 1303 | 1304 | injector = Injector() 1305 | 1306 | # The following line used to fail with something like this: 1307 | # Traceback (most recent call last): 1308 | # File "/ve/injector/injector_test_py3.py", line 192, 1309 | # in test_things_dont_break_in_presence_of_args_or_kwargs 1310 | # injector.get(A) 1311 | # File "/ve/injector/injector.py", line 707, in get 1312 | # result = scope_instance.get(key, binding.provider).get(self) 1313 | # File "/ve/injector/injector.py", line 142, in get 1314 | # return injector.create_object(self._cls) 1315 | # File "/ve/injector/injector.py", line 744, in create_object 1316 | # init(instance, **additional_kwargs) 1317 | # File "/ve/injector/injector.py", line 1082, in inject 1318 | # kwargs=kwargs 1319 | # File "/ve/injector/injector.py", line 851, in call_with_injection 1320 | # **dependencies) 1321 | # File "/ve/injector/injector_test_py3.py", line 189, in __init__ 1322 | # assert not kwargs 1323 | # AssertionError: assert not {'args': 0, 'kwargs': ''} 1324 | injector.get(A) 1325 | 1326 | 1327 | def test_forward_references_in_annotations_are_handled(): 1328 | # See https://www.python.org/dev/peps/pep-0484/#forward-references for details 1329 | 1330 | class CustomModule(Module): 1331 | @provider 1332 | def provide_x(self) -> 'X': 1333 | return X('hello') 1334 | 1335 | @inject 1336 | def fun(s: 'X') -> 'X': 1337 | return s 1338 | 1339 | # The class needs to be module-global in order for the string -> object 1340 | # resolution mechanism to work. I could make it work with locals but it 1341 | # doesn't seem worth it. 1342 | global X 1343 | 1344 | class X: 1345 | def __init__(self, message: str) -> None: 1346 | self.message = message 1347 | 1348 | try: 1349 | injector = Injector(CustomModule) 1350 | assert injector.call_with_injection(fun).message == 'hello' 1351 | finally: 1352 | del X 1353 | 1354 | 1355 | def test_more_useful_exception_is_raised_when_parameters_type_is_any(): 1356 | @inject 1357 | def fun(a: Any) -> None: 1358 | pass 1359 | 1360 | injector = Injector() 1361 | 1362 | # This was the exception before: 1363 | # 1364 | # TypeError: Cannot instantiate 1365 | # 1366 | # Now: 1367 | # 1368 | # injector.CallError: Call to AnyMeta.__new__() failed: Cannot instantiate 1369 | # (injection stack: ['injector_test_py3']) 1370 | # 1371 | # In this case the injection stack doesn't provide too much information but 1372 | # it quickly gets helpful when the stack gets deeper. 1373 | with pytest.raises((CallError, TypeError)): 1374 | injector.call_with_injection(fun) 1375 | 1376 | 1377 | def test_optionals_are_ignored_for_now(): 1378 | @inject 1379 | def fun(s: str = None): 1380 | return s 1381 | 1382 | assert Injector().call_with_injection(fun) == '' 1383 | 1384 | 1385 | def test_explicitly_passed_parameters_override_injectable_values(): 1386 | # The class needs to be defined globally for the 'X' forward reference to be able to be resolved. 1387 | global X 1388 | 1389 | # We test a method on top of regular function to exercise the code path that's 1390 | # responsible for handling methods. 1391 | class X: 1392 | @inject 1393 | def method(self, s: str) -> str: 1394 | return s 1395 | 1396 | @inject 1397 | def method_typed_self(self: 'X', s: str) -> str: 1398 | return s 1399 | 1400 | @inject 1401 | def function(s: str) -> str: 1402 | return s 1403 | 1404 | injection_counter = 0 1405 | 1406 | def provide_str() -> str: 1407 | nonlocal injection_counter 1408 | injection_counter += 1 1409 | return 'injected string' 1410 | 1411 | def configure(binder: Binder) -> None: 1412 | binder.bind(str, to=provide_str) 1413 | 1414 | injector = Injector([configure]) 1415 | x = X() 1416 | 1417 | try: 1418 | assert injection_counter == 0 1419 | 1420 | assert injector.call_with_injection(x.method) == 'injected string' 1421 | assert injection_counter == 1 1422 | assert injector.call_with_injection(x.method_typed_self) == 'injected string' 1423 | assert injection_counter == 2 1424 | assert injector.call_with_injection(function) == 'injected string' 1425 | assert injection_counter == 3 1426 | 1427 | assert injector.call_with_injection(x.method, args=('passed string',)) == 'passed string' 1428 | assert injection_counter == 3 1429 | assert injector.call_with_injection(x.method_typed_self, args=('passed string',)) == 'passed string' 1430 | assert injection_counter == 3 1431 | assert injector.call_with_injection(function, args=('passed string',)) == 'passed string' 1432 | assert injection_counter == 3 1433 | 1434 | assert injector.call_with_injection(x.method, kwargs={'s': 'passed string'}) == 'passed string' 1435 | assert injection_counter == 3 1436 | assert ( 1437 | injector.call_with_injection(x.method_typed_self, kwargs={'s': 'passed string'}) 1438 | == 'passed string' 1439 | ) 1440 | assert injection_counter == 3 1441 | assert injector.call_with_injection(function, kwargs={'s': 'passed string'}) == 'passed string' 1442 | assert injection_counter == 3 1443 | finally: 1444 | del X 1445 | 1446 | 1447 | class AssistedB: 1448 | @inject 1449 | def __init__(self, a: EmptyClass, b: str): 1450 | self.a = a 1451 | self.b = b 1452 | 1453 | 1454 | def test_class_assisted_builder_of_partially_injected_class(): 1455 | class C: 1456 | @inject 1457 | def __init__(self, a: EmptyClass, builder: ClassAssistedBuilder[AssistedB]): 1458 | self.a = a 1459 | self.b = builder.build(b='C') 1460 | 1461 | c = Injector().get(C) 1462 | assert isinstance(c, C) 1463 | assert isinstance(c.b, AssistedB) 1464 | assert isinstance(c.b.a, EmptyClass) 1465 | 1466 | 1467 | # The test taken from Alec Thomas' pull request: https://github.com/alecthomas/injector/pull/73 1468 | def test_child_scope(): 1469 | TestKey = NewType('TestKey', str) 1470 | TestKey2 = NewType('TestKey2', str) 1471 | 1472 | def parent_module(binder): 1473 | binder.bind(TestKey, to='in parent', scope=singleton) 1474 | 1475 | def first_child_module(binder): 1476 | binder.bind(TestKey2, to='in first child', scope=singleton) 1477 | 1478 | def second_child_module(binder): 1479 | binder.bind(TestKey2, to='in second child', scope=singleton) 1480 | 1481 | injector = Injector(modules=[parent_module]) 1482 | first_child_injector = injector.create_child_injector(modules=[first_child_module]) 1483 | second_child_injector = injector.create_child_injector(modules=[second_child_module]) 1484 | 1485 | assert first_child_injector.get(TestKey) is first_child_injector.get(TestKey) 1486 | assert first_child_injector.get(TestKey) is second_child_injector.get(TestKey) 1487 | assert first_child_injector.get(TestKey2) is not second_child_injector.get(TestKey2) 1488 | 1489 | 1490 | def test_custom_scopes_work_as_expected_with_child_injectors(): 1491 | class CustomSingletonScope(SingletonScope): 1492 | pass 1493 | 1494 | custom_singleton = ScopeDecorator(CustomSingletonScope) 1495 | 1496 | def parent_module(binder): 1497 | binder.bind(str, to='parent value', scope=custom_singleton) 1498 | 1499 | def child_module(binder): 1500 | binder.bind(str, to='child value', scope=custom_singleton) 1501 | 1502 | parent = Injector(modules=[parent_module]) 1503 | child = parent.create_child_injector(modules=[child_module]) 1504 | print('parent, child: %s, %s' % (parent, child)) 1505 | assert parent.get(str) == 'parent value' 1506 | assert child.get(str) == 'child value' 1507 | 1508 | 1509 | # Test for https://github.com/alecthomas/injector/issues/75 1510 | def test_inject_decorator_does_not_break_manual_construction_of_pyqt_objects(): 1511 | class PyQtFake: 1512 | @inject 1513 | def __init__(self): 1514 | pass 1515 | 1516 | def __getattribute__(self, item): 1517 | if item == '__injector__': 1518 | raise RuntimeError( 1519 | 'A PyQt class would raise this exception if getting ' 1520 | 'self.__injector__ before __init__ is called and ' 1521 | 'self.__injector__ has not been set by Injector.' 1522 | ) 1523 | return object.__getattribute__(self, item) 1524 | 1525 | instance = PyQtFake() # This used to raise the exception 1526 | 1527 | assert isinstance(instance, PyQtFake) 1528 | 1529 | 1530 | def test_using_an_assisted_builder_with_a_provider_raises_an_injector_error(): 1531 | class MyModule(Module): 1532 | @provider 1533 | def provide_a(self, builder: AssistedBuilder[EmptyClass]) -> EmptyClass: 1534 | return builder.build() 1535 | 1536 | injector = Injector(MyModule) 1537 | 1538 | with pytest.raises(Error): 1539 | injector.get(EmptyClass) 1540 | 1541 | 1542 | def test_newtype_integration_works(): 1543 | UserID = NewType('UserID', int) 1544 | 1545 | def configure(binder): 1546 | binder.bind(UserID, to=123) 1547 | 1548 | injector = Injector([configure]) 1549 | assert injector.get(UserID) == 123 1550 | 1551 | 1552 | def test_dataclass_integration_works(): 1553 | import dataclasses 1554 | 1555 | @inject 1556 | @dataclasses.dataclass 1557 | class Data: 1558 | name: str 1559 | 1560 | def configure(binder): 1561 | binder.bind(str, to='data') 1562 | 1563 | injector = Injector([configure]) 1564 | assert injector.get(Data).name == 'data' 1565 | 1566 | 1567 | def test_binder_does_not_have_a_binding_for_an_unbound_type(): 1568 | injector = Injector() 1569 | assert not injector.binder.has_binding_for(int) 1570 | assert not injector.binder.has_explicit_binding_for(int) 1571 | 1572 | 1573 | def test_binder_has_binding_for_explicitly_bound_type(): 1574 | def configure(binder): 1575 | binder.bind(int, to=123) 1576 | 1577 | injector = Injector([configure]) 1578 | assert injector.binder.has_binding_for(int) 1579 | assert injector.binder.has_explicit_binding_for(int) 1580 | 1581 | 1582 | def test_binder_has_implicit_binding_for_implicitly_bound_type(): 1583 | injector = Injector() 1584 | 1585 | injector.get(int) 1586 | 1587 | assert injector.binder.has_binding_for(int) 1588 | assert not injector.binder.has_explicit_binding_for(int) 1589 | 1590 | 1591 | def test_get_bindings(): 1592 | def function1(a: int) -> None: 1593 | pass 1594 | 1595 | assert get_bindings(function1) == {} 1596 | 1597 | @inject 1598 | def function2(a: int) -> None: 1599 | pass 1600 | 1601 | assert get_bindings(function2) == {'a': int} 1602 | 1603 | @inject 1604 | @noninjectable('b') 1605 | def function3(a: int, b: str) -> None: 1606 | pass 1607 | 1608 | assert get_bindings(function3) == {'a': int} 1609 | 1610 | # Let's verify that the inject/noninjectable ordering doesn't matter 1611 | @noninjectable('b') 1612 | @inject 1613 | def function3b(a: int, b: str) -> None: 1614 | pass 1615 | 1616 | assert get_bindings(function3b) == {'a': int} 1617 | 1618 | # The simple case of no @inject but injection requested with Inject[...] 1619 | def function4(a: Inject[int], b: str) -> None: 1620 | pass 1621 | 1622 | assert get_bindings(function4) == {'a': int} 1623 | 1624 | # Using @inject with Inject is redundant but it should not break anything 1625 | @inject 1626 | def function5(a: Inject[int], b: str) -> None: 1627 | pass 1628 | 1629 | assert get_bindings(function5) == {'a': int, 'b': str} 1630 | 1631 | # We need to be able to exclude a parameter from injection with NoInject 1632 | @inject 1633 | def function6(a: int, b: NoInject[str]) -> None: 1634 | pass 1635 | 1636 | assert get_bindings(function6) == {'a': int} 1637 | 1638 | # The presence of NoInject should not trigger anything on its own 1639 | def function7(a: int, b: NoInject[str]) -> None: 1640 | pass 1641 | 1642 | assert get_bindings(function7) == {} 1643 | 1644 | # There was a bug where in case of multiple NoInject-decorated parameters only the first one was 1645 | # actually made noninjectable and we tried to inject something we couldn't possibly provide 1646 | # into the second one. 1647 | @inject 1648 | def function8(a: NoInject[int], b: NoInject[int]) -> None: 1649 | pass 1650 | 1651 | assert get_bindings(function8) == {} 1652 | 1653 | # Default arguments to NoInject annotations should behave the same as noninjectable decorator w.r.t 'None' 1654 | @inject 1655 | @noninjectable('b') 1656 | def function9(self, a: int, b: Optional[str] = None): 1657 | pass 1658 | 1659 | @inject 1660 | def function10(self, a: int, b: NoInject[Optional[str]] = None): 1661 | # b:s type is Union[NoInject[Union[str, None]], None] 1662 | pass 1663 | 1664 | assert get_bindings(function9) == {'a': int} == get_bindings(function10) 1665 | 1666 | # If there's a return type annottion that contains an a forward reference that can't be 1667 | # resolved (for whatever reason) we don't want that to break things for us – return types 1668 | # don't matter for the purpose of dependency injection. 1669 | @inject 1670 | def function11(a: int) -> 'InvalidForwardReference': 1671 | pass 1672 | 1673 | assert get_bindings(function11) == {'a': int} 1674 | 1675 | 1676 | # Tests https://github.com/alecthomas/injector/issues/202 1677 | @pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+") 1678 | def test_get_bindings_for_pep_604(): 1679 | @inject 1680 | def function1(a: int | None) -> None: 1681 | pass 1682 | 1683 | assert get_bindings(function1) == {'a': int} 1684 | 1685 | @inject 1686 | def function1(a: int | str) -> None: 1687 | pass 1688 | 1689 | assert get_bindings(function1) == {'a': Union[int, str]} 1690 | 1691 | 1692 | # test for https://github.com/python-injector/injector/issues/217 1693 | def test_annotated_instance_integration_works(): 1694 | UserID = Annotated[int, "user_id"] 1695 | 1696 | def configure(binder): 1697 | binder.bind(UserID, to=123) 1698 | 1699 | injector = Injector([configure]) 1700 | assert injector.get(UserID) == 123 1701 | 1702 | 1703 | def test_annotated_class_integration_works(): 1704 | class Shape(abc.ABC): 1705 | pass 1706 | 1707 | class Circle(Shape): 1708 | pass 1709 | 1710 | first = Annotated[Shape, "first"] 1711 | 1712 | def configure(binder): 1713 | binder.bind(first, to=Circle) 1714 | 1715 | injector = Injector([configure]) 1716 | assert isinstance(injector.get(first), Circle) 1717 | 1718 | 1719 | def test_annotated_meta_separate_bindings(): 1720 | first = Annotated[int, "first"] 1721 | second = Annotated[int, "second"] 1722 | 1723 | def configure(binder): 1724 | binder.bind(first, to=123) 1725 | binder.bind(second, to=456) 1726 | 1727 | injector = Injector([configure]) 1728 | assert injector.get(first) == 123 1729 | assert injector.get(second) == 456 1730 | assert injector.get(first) != injector.get(second) 1731 | 1732 | 1733 | def test_annotated_origin_separate_bindings(): 1734 | UserID = Annotated[int, "user_id"] 1735 | 1736 | def configure(binder): 1737 | binder.bind(UserID, to=123) 1738 | binder.bind(int, to=456) 1739 | 1740 | injector = Injector([configure]) 1741 | assert injector.get(UserID) == 123 1742 | assert injector.get(int) == 456 1743 | assert injector.get(UserID) != injector.get(int) 1744 | 1745 | 1746 | def test_annotated_non_comparable_types(): 1747 | foo = Annotated[int, float("nan")] 1748 | bar = Annotated[int, object()] 1749 | 1750 | def configure(binder): 1751 | binder.bind(foo, to=123) 1752 | binder.bind(bar, to=456) 1753 | 1754 | injector = Injector([configure]) 1755 | assert injector.get(foo) == 123 1756 | assert injector.get(bar) == 456 1757 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = true 3 | follow_imports = error 4 | warn_no_return = true 5 | warn_redundant_casts = true 6 | disallow_untyped_defs = true 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 110 3 | target_version = ['py36', 'py37'] 4 | skip_string_normalization = true 5 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -v --tb=native --doctest-glob=*.md --doctest-modules --cov-report term --cov-report html --cov-report xml --cov=injector --cov-branch 3 | norecursedirs = __pycache__ *venv* .git build 4 | -------------------------------------------------------------------------------- /requirements-dev.in: -------------------------------------------------------------------------------- 1 | # Our direct dependencies used in development/CI. 2 | # 3 | # We generate requirements-dev.txt from this file by running 4 | # 5 | # pip install -r requirements-dev.in && pip freeze > requirements-dev.txt 6 | # 7 | # and then modifying the file manually to restrict black and mypy to CPython 8 | 9 | pytest 10 | pytest-cov>=2.5.1 11 | mypy;implementation_name=="cpython" 12 | black;implementation_name=="cpython" 13 | check-manifest 14 | typing_extensions>=3.7.4;python_version<"3.9" 15 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | black==24.3.0 ; implementation_name == "cpython" 2 | build==1.0.3 3 | check-manifest==0.49 4 | click==8.1.7 5 | coverage[toml]==7.3.2 6 | exceptiongroup==1.2.0 7 | importlib-metadata==7.0.0 8 | iniconfig==2.0.0 9 | mypy==1.7.1 ; implementation_name == "cpython" 10 | mypy-extensions==1.0.0 11 | packaging==23.2 12 | pathspec==0.12.1 13 | platformdirs==4.1.0 14 | pluggy==1.3.0 15 | pyproject-hooks==1.0.0 16 | pytest==7.4.3 17 | pytest-cov==4.1.0 18 | tomli==2.0.1 19 | typing-extensions==4.9.0 ; python_version < "3.9" 20 | zipp==3.19.1 21 | -------------------------------------------------------------------------------- /requirements-docs.in: -------------------------------------------------------------------------------- 1 | # The documentation-specific development dependencies. 2 | # 3 | # We generate requirements-dev.txt from this file by running 4 | # 5 | # pip install -r requirements-docs.in && pip freeze > requirements-docs.txt 6 | # 7 | # and then modifying the file manually to restrict black and mypy to CPython 8 | furo 9 | sphinx 10 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.13 2 | babel==2.14.0 3 | beautifulsoup4==4.12.3 4 | certifi==2024.7.4 5 | charset-normalizer==3.3.2 6 | docutils==0.20.1 7 | furo==2024.5.6 8 | idna==3.7 9 | imagesize==1.4.1 10 | jinja2==3.1.6 11 | markupsafe==2.1.3 12 | packaging==23.2 13 | pygments==2.17.2 14 | requests==2.32.2 15 | snowballstemmer==2.2.0 16 | soupsieve==2.5 17 | sphinx==7.1.2 18 | sphinx-basic-ng==1.0.0b2 19 | sphinxcontrib-applehelp==1.0.4 20 | sphinxcontrib-devhelp==1.0.2 21 | sphinxcontrib-htmlhelp==2.0.1 22 | sphinxcontrib-jsmath==1.0.1 23 | sphinxcontrib-qthelp==1.0.3 24 | sphinxcontrib-serializinghtml==1.1.5 25 | urllib3==2.2.2 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | typing_extensions>=3.7.4;python_version<"3.9" 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = True 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Command 2 | import sys 3 | import warnings 4 | 5 | 6 | warnings.filterwarnings("always", module=__name__) 7 | 8 | 9 | def obtain_requirements(file_name): 10 | with open(file_name) as fd_in: 11 | for line in fd_in: 12 | if '#' not in line: 13 | yield line.strip() 14 | 15 | 16 | class PyTest(Command): 17 | user_options = [] 18 | 19 | def initialize_options(self): 20 | pass 21 | 22 | def finalize_options(self): 23 | pass 24 | 25 | def run(self): 26 | import subprocess 27 | 28 | errno = subprocess.call([sys.executable, '-m', 'pytest']) 29 | raise SystemExit(errno) 30 | 31 | 32 | def read_injector_variable(name): 33 | prefix = '%s = ' % (name,) 34 | with open('injector/__init__.py') as f: 35 | for line in f: 36 | if line.startswith(prefix): 37 | return line.replace(prefix, '').strip().strip("'") 38 | raise AssertionError('variable %s not found' % (name,)) 39 | 40 | 41 | version = read_injector_variable('__version__') 42 | version_tag = read_injector_variable('__version_tag__') 43 | 44 | 45 | requirements = list(obtain_requirements('requirements.txt')) 46 | requirements_dev = list(obtain_requirements('requirements-dev.txt')) 47 | 48 | 49 | try: 50 | import pypandoc 51 | 52 | long_description = pypandoc.convert_file('README.md', 'rst') 53 | except ImportError: 54 | warnings.warn('Could not locate pandoc, using Markdown long_description.', ImportWarning) 55 | with open('README.md') as f: 56 | long_description = f.read() 57 | 58 | description = long_description.splitlines()[0].strip() 59 | 60 | 61 | setup( 62 | name='injector', 63 | url='https://github.com/alecthomas/injector', 64 | download_url='https://pypi.org/project/injector/', 65 | version=version, 66 | options=dict(egg_info=dict(tag_build=version_tag)), 67 | description=description, 68 | long_description=long_description, 69 | license='BSD', 70 | platforms=['any'], 71 | packages=['injector'], 72 | package_data={'injector': ['py.typed']}, 73 | author='Alec Thomas', 74 | author_email='alec@swapoff.org', 75 | cmdclass={'test': PyTest}, 76 | extras_require={'dev': requirements_dev}, 77 | keywords=[ 78 | 'Dependency Injection', 79 | 'DI', 80 | 'Dependency Injection framework', 81 | 'Inversion of Control', 82 | 'IoC', 83 | 'Inversion of Control container', 84 | ], 85 | install_requires=requirements, 86 | ) 87 | --------------------------------------------------------------------------------