Compare commits

..

22 Commits

Author SHA1 Message Date
Roman Mogylatov
b4890dcf80 Bump version to 4.0.0a2 2020-09-28 16:33:39 -04:00
Roman Mogylatov
d9334ef1fe Add protection for wiring only declarative container instances 2020-09-28 15:52:21 -04:00
Roman Mogylatov
cc1b6ba3e6 Add __all__ for wiring module 2020-09-28 15:39:41 -04:00
Roman Mogylatov
42b0bde8d2 Deprecate provider.delegate() method 2020-09-28 15:38:34 -04:00
Roman Mogylatov
5d4eeb648a Deprecate ext package modules and remove types module 2020-09-28 14:16:05 -04:00
Roman Mogylatov
26e490bf0a Add container.unwire() typing stub 2020-09-28 13:41:54 -04:00
Roman Mogylatov
6182b8448a
Wiring refactoring (#296)
* Refactor wiring

* Add todos to wiring

* Implement wiring of config invariant

* Implement sub containers wiring + add tests

* Add test for wiring config invariant
2020-09-27 23:10:11 -04:00
Roman Mogylatov
7f854548d6 Make flake8 happy 2020-09-26 01:17:42 -04:00
Roman Mogylatov
95db0eddc9 Implement Provide[foo.provided.bar.baz.call()] 2020-09-26 01:07:32 -04:00
Roman Mogylatov
6d92df32aa Implement wiring for Provide[foo.provider] 2020-09-26 00:31:29 -04:00
Roman Mogylatov
c20c57ae7c Update demo 2020-09-26 00:23:11 -04:00
Roman Mogylatov
7b2baeeb6f Remove not needed images 2020-09-24 20:51:45 -04:00
Roman Mogylatov
9653dfc263 Add sanic example 2020-09-23 18:29:13 -04:00
Roman Mogylatov
e37b5181e4 Rename views module to handlers in aiohttp example 2020-09-23 15:58:36 -04:00
Roman Mogylatov
a9970b63b9 Rename aiohttp example directory 2020-09-22 22:03:49 -04:00
Roman Mogylatov
170819c6ed Update flask example 2020-09-22 22:03:02 -04:00
Roman Mogylatov
4fab71c35b Update aiohttp example 2020-09-22 21:48:41 -04:00
Roman Mogylatov
170263de4d Add flake8 ignore for demo 2020-09-21 16:55:42 -04:00
Roman Mogylatov
b7efb1e3e2 Add pydocstyle ignore for demo 2020-09-21 16:51:57 -04:00
Roman Mogylatov
dd8778bf20 Updaet demo example 2020-09-21 16:46:02 -04:00
Roman Mogylatov
edd8979bf6 Bump version to 4.0 2020-09-20 21:51:48 -04:00
Roman Mogylatov
af7364e062
Add wiring (#294)
* Add wiring module

* Fix code style

* Fix package test

* Add version fix

* Try spike for 3.6

* Try another fix with metaclass

* Downsample required version to 3.6

* Introduce concept with annotations

* Fix bugs

* Add debug message

* Add extra tests

* Add extra debugging

* Update config resolving

* Remove 3.6 generic meta fix

* Fix Flake8

* Add spike for 3.6

* Add Python 3.6 spike

* Add unwire functionality

* Add support of corouting functions
2020-09-20 21:50:25 -04:00
568 changed files with 120683 additions and 31499 deletions

7
.coveragerc Normal file
View File

@ -0,0 +1,7 @@
[run]
source = src/dependency_injector
omit = tests/unit
plugins = Cython.Coverage
[html]
directory=reports/unittests/

View File

@ -1,29 +0,0 @@
---
description: Code in Python and Cython
globs:
alwaysApply: false
---
- Follow PEP 8 rules
- When you write imports, split system, 3rd-party, and local imports with a new line
- Have two empty lines between the import block and the rest of the code
- Have an empty line (\n) at the end of every file
- If a file is supposed to be run, always add ``if __name__ == 'main'``
- Always follow a consistent pattern of using double or single quotes
- When there is a class without a docblock, leave one blank line before its members, e.g.:
```python
class Container(containers.DeclarativeContainer):
service = providers.Factory(Service)
```
- Avoid shortcuts in names unless absolutely necessary, exceptions:
```
arg
args
kwarg
kwargs
obj
cls
```
- Avoid inline comments unless absolutely necessary

View File

@ -1,7 +0,0 @@
---
description: Build and run tests
globs:
alwaysApply: false
---
- Use Makefile commands to build, test, lint and other similar operations when they are available.
- Activate virtualenv before running any commands by ``. venv/bin/actvate``

View File

@ -1,8 +0,0 @@
---
description: Run examples
globs:
alwaysApply: false
---
- When you run an example from the ``examples/`` folder, switch to the example folder and run it from there.
- If there are instructions on running the examples or its tests in readme, follow them
- Activate virtualenv before running any commands by ``. venv/bin/actvate``

View File

@ -1,12 +0,0 @@
version = 1
test_patterns = ["tests/**/test_*.py"]
exclude_patterns = ["docs/**"]
[[analyzers]]
name = "python"
enabled = true
[analyzers.meta]
runtime_version = "3.x.x"

View File

@ -1,9 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{py,pyi,pxd,pyx}]
ij_visual_guides = 80,88

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
github: rmk135

View File

@ -1,131 +0,0 @@
name: Publishing
on:
workflow_dispatch:
push:
tags:
- '*'
jobs:
tests:
name: Run tests
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.13
- run: pip install tox
- run: tox
env:
TOXENV: 3.13
linters:
name: Run linters
runs-on: ubuntu-24.04
strategy:
matrix:
toxenv: [flake8, pydocstyle, mypy, pylint]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.13
- run: pip install tox
- run: tox
env:
TOXENV: ${{ matrix.toxenv }}
build-sdist:
name: Build source tarball
needs: [tests, linters]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.13
- run: |
python -m pip install --upgrade build
python -m build --sdist
- uses: actions/upload-artifact@v4
with:
name: cibw-sdist
path: ./dist/*
build-wheels:
name: Build wheels
needs: [tests, linters]
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2022, macos-14]
env:
CIBW_ENABLE: pypy
CIBW_ENVIRONMENT: >-
PIP_CONFIG_SETTINGS="build_ext=-j4"
DEPENDENCY_INJECTOR_LIMITED_API="1"
CFLAGS="-g0"
steps:
- uses: actions/checkout@v3
- name: Build wheels
uses: pypa/cibuildwheel@v3.0.0
- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl
test-publish:
name: Upload release to TestPyPI
needs: [build-sdist, build-wheels]
runs-on: ubuntu-latest
environment: test-pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: cibw-*
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
publish:
name: Upload release to PyPI
needs: [build-sdist, build-wheels, test-publish]
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: cibw-*
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
publish-docs:
name: Publish docs
needs: [publish]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.13
- run: pip install awscli
- run: pip install -r requirements-doc.txt
- run: pip install -e .
- run: (cd docs && make clean html)
- run: |
aws s3 sync docs/_build/html s3://python-dependency-injector-docs --delete
aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} --path "/*" > /dev/null
echo "Cache invalidation triggered"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}

View File

@ -1,67 +0,0 @@
name: Tests and linters
on: [push, pull_request, workflow_dispatch]
jobs:
test-on-different-versions:
name: Run tests
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- run: pip install tox
- run: tox
env:
DEPENDENCY_INJECTOR_LIMITED_API: 1
TOXENV: ${{ matrix.python-version }}
test-different-pydantic-versions:
name: Run tests with different pydantic versions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.12"
- run: pip install tox
- run: tox -e pydantic-v1,pydantic-v2
test-coverage:
name: Run tests with coverage
runs-on: ubuntu-latest
env:
DEPENDENCY_INJECTOR_DEBUG_MODE: 1
PIP_VERBOSE: 1
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.12
- run: pip install tox
- run: tox -vv
env:
TOXENV: coveralls
linters:
name: Run linters
runs-on: ubuntu-latest
strategy:
matrix:
toxenv: [flake8, pydocstyle, mypy, pylint]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.13
- run: pip install tox
- run: tox
env:
TOXENV: ${{ matrix.toxenv }}

21
.gitignore vendored
View File

@ -15,7 +15,6 @@ lib64/
parts/
sdist/
var/
wheelhouse/
*.egg-info/
.installed.cfg
*.egg
@ -37,7 +36,6 @@ reports/
.cache
nosetests.xml
coverage.xml
.hypothesis/
# Translations
*.mo
@ -56,7 +54,7 @@ target/
.idea/
# Virtualenv
venv*/
venv/
# SQLite
*.db
@ -64,13 +62,10 @@ venv*/
# Vim Rope
.ropeproject/
# Cython artifacts
src/**/*.c
src/**/*.h
src/**/*.so
src/**/*.html
# Workspace for samples
.workspace/
.vscode/
# C extensions
src/dependency_injector/*.h
src/dependency_injector/*.so
src/dependency_injector/containers/*.h
src/dependency_injector/containers/*.so
src/dependency_injector/providers/*.h
src/dependency_injector/providers/*.so

49
.pylintrc Normal file
View File

@ -0,0 +1,49 @@
[MASTER]
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
ignore=utils,tests
[MESSAGES CONTROL]
# Disable the message(s) with the given id(s).
# disable-msg=
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=5
[TYPECHECK]
ignore-mixin-members=yes
# ignored-classes=
zope=no
# generated-members=providedBy,implementedBy,rawDataReceived
[DESIGN]
# Maximum number of arguments for function / method
max-args=10
# Maximum number of locals for function / method body
max-locals=20
# Maximum number of return / yield for function / method body
max-returns=10
# Maximum number of branch for function / method body
max-branchs=10
# Maximum number of statements in function / method body
max-statements=60
# Maximum number of parents for a class (see R0901).
max-parents=10
# Maximum number of attributes for a class (see R0902).
max-attributes=30
# Minimum number of public methods for a class (see R0903).
min-public-methods=0
# Maximum number of public methods for a class (see R0904).
max-public-methods=30

101
.travis.yml Normal file
View File

@ -0,0 +1,101 @@
os: linux
dist: xenial
language: python
jobs:
include:
- python: 3.8
env: TOXENV=coveralls DEPENDENCY_INJECTOR_DEBUG_MODE=1
install:
- pip install tox
- pip install cython
- make cythonize
script: tox
- python: 3.6
env: TOXENV=pylint
install: pip install tox
script: tox
- python: 3.6
env: TOXENV=flake8
install: pip install tox
script: tox
- python: 3.6
env: TOXENV=pydocstyle
install: pip install tox
script: tox
- python: 3.6
env: TOXENV=mypy
install: pip install tox
script: tox
- python: 2.7
env: TOXENV=py27
install: pip install tox
script: tox
- python: 3.4
env: TOXENV=py34
install: pip install tox
script: tox
- python: 3.5
env: TOXENV=py35
install: pip install tox
script: tox
- python: 3.6
env: TOXENV=py36
install: pip install tox
script: tox
- python: 3.7
env: TOXENV=py37
install: pip install tox
script: tox
- python: 3.8
env: TOXENV=py38
install: pip install tox
script: tox
- python: pypy
env: TOXENV=pypy
install: pip install tox
script: tox
- python: pypy3
env: TOXENV=pypy3
install: pip install tox
script: tox
- python: 3.8
if: tag IS present
env: TWINE_USERNAME=__token__
install: pip install pip --upgrade
script: python setup.py sdist
after_success:
- python3 -m pip install twine
- python3 -m twine upload dist/*
- services: docker
if: tag IS present
env: TWINE_USERNAME=__token__
install: python3 -m pip install cibuildwheel==1.5.1
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success:
- python3 -m pip install twine
- python3 -m twine upload wheelhouse/*.whl
- os: osx
if: tag IS present
language: shell
env: TWINE_USERNAME=__token__
install: python3 -m pip install cibuildwheel==1.5.1
script: python3 -m cibuildwheel --output-dir wheelhouse
after_success:
- python3 -m pip install twine
- python3 -m twine upload wheelhouse/*.whl
- os: windows
if: tag IS present
language: shell
env: TWINE_USERNAME=__token__
before_install:
- choco install python --version 3.8.0
- export PATH="/c/Python38:/c/Python38/Scripts:$PATH"
install: python -m pip install cibuildwheel==1.5.1
script: python -m cibuildwheel --output-dir wheelhouse
after_success:
- python -m pip install twine
- python -m twine upload wheelhouse/*.whl
notifications:
slack:
rooms:
secure: CdWDgKnfYW7vvvoH3nS3yg3TcNZiYLRUyEp6ukQ4rQiiuR4+ltuvyGyFJWgP8r7VVJ9yHkB0jebCKWLUMsAEt1my33B6eMDEVefovpkdh2eJjGswmm80brt0EJULpgwPOtB1U47Mwca8L5jDW4KSv9RypUFRgn8eHDoWw6LKf5g=

View File

@ -13,12 +13,3 @@ Dependency Injector Contributors
+ Bruno P. Kinoshita (kinow)
+ RobinsonMa (RobinsonMa)
+ Rüdiger Busche (JarnoRFB)
+ Dmitry Rassoshenko (rda-dev)
+ Fotis Koutoupas (kootoopas)
+ Shubhendra Singh Chauhan (withshubh)
+ sonthonaxrk (sonthonaxrk)
+ Ngo Thanh Loi (Leonn) (loingo95)
+ Thiago Hiromi (thiromi)
+ Felipe Rubio (krouw)
+ Anton Petrov (anton-petrov)
+ ZipFile (ZipFile)

View File

@ -1,4 +1,4 @@
Copyright (c) 2024, Roman Mogylatov
Copyright (c) 2017, ETS Labs
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -1,7 +1,9 @@
recursive-include src/dependency_injector *.py* *.c py.typed
recursive-include src/dependency_injector *.py* *.c
recursive-include tests *.py
include README.rst
include CONTRIBUTORS.rst
include LICENSE.rst
include requirements.txt
include setup.py
include tox.ini
include py.typed

View File

@ -1,6 +1,14 @@
VERSION := $(shell python setup.py --version)
export COVERAGE_RCFILE := pyproject.toml
CYTHON_SRC := $(shell find src/dependency_injector -name '*.pyx')
CYTHON_DIRECTIVES = -Xlanguage_level=2
ifdef DEPENDENCY_INJECTOR_DEBUG_MODE
CYTHON_DIRECTIVES += -Xprofile=True
CYTHON_DIRECTIVES += -Xlinetrace=True
endif
clean:
# Clean sources
@ -17,28 +25,39 @@ clean:
find examples -name '*.py[co]' -delete
find examples -name '__pycache__' -delete
build: clean
# Compile C extensions
python setup.py build_ext --inplace
cythonize:
# Compile Cython to C
cython -a $(CYTHON_DIRECTIVES) $(CYTHON_SRC)
# Move all Cython html reports
mkdir -p reports/cython/
find src -name '*.html' -exec mv {} reports/cython/ \;
build: clean cythonize
# Compile C extensions
python setup.py build_ext --inplace
docs-live:
sphinx-autobuild docs docs/_build/html
install: uninstall clean build
install: uninstall clean cythonize
pip install -ve .
uninstall:
- pip uninstall -y -q dependency-injector 2> /dev/null
test:
test-py2: build
# Unit tests with coverage report
coverage erase
coverage run -m pytest
coverage report
coverage html
coverage run --rcfile=./.coveragerc -m unittest2 discover -s tests/unit/ -p test_*_py2_py3.py
coverage report --rcfile=./.coveragerc
coverage html --rcfile=./.coveragerc
test-py3: build
# Unit tests with coverage report
coverage erase
coverage run --rcfile=./.coveragerc -m unittest2 discover -s tests/unit/ -p test_*py3*.py
coverage report --rcfile=./.coveragerc
coverage html --rcfile=./.coveragerc
check:
flake8 src/dependency_injector/
@ -49,9 +68,9 @@ check:
mypy tests/typing
test-publish: build
test-publish: cythonize
# Create distributions
python -m build --sdist
python setup.py sdist
# Upload distributions to PyPI
twine upload --repository testpypi dist/dependency-injector-$(VERSION)*

View File

@ -35,10 +35,14 @@
:target: https://pypi.org/project/dependency-injector/
:alt: Wheel
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master
:target: https://github.com/ets-labs/python-dependency-injector/actions
.. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
:target: https://travis-ci.org/ets-labs/python-dependency-injector
:alt: Build Status
.. image:: http://readthedocs.org/projects/python-dependency-injector/badge/?version=latest
:target: http://python-dependency-injector.ets-labs.org/
:alt: Docs Status
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
:alt: Coverage Status
@ -48,91 +52,74 @@ What is ``Dependency Injector``?
``Dependency Injector`` is a dependency injection framework for Python.
It helps implement the dependency injection principle.
It helps implementing the dependency injection principle.
Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
that help assemble your objects.
See `Providers <https://python-dependency-injector.ets-labs.org/providers/index.html>`_.
``List``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help assembling your
objects. See `Providers <http://python-dependency-injector.ets-labs.org/providers/index.html>`_.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
and configuring dev/stage environment to replace API clients with stubs etc. See
`Provider overriding <https://python-dependency-injector.ets-labs.org/providers/overriding.html>`_.
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings,
environment variables, and dictionaries.
See `Configuration provider <https://python-dependency-injector.ets-labs.org/providers/configuration.html>`_.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
See `Resource provider <https://python-dependency-injector.ets-labs.org/providers/resource.html>`_.
and configuring dev / stage environment to replace API clients with stubs etc. See
`Provider overriding <http://python-dependency-injector.ets-labs.org/providers/overriding.html>`_.
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables
and dictionaries.
See `Configuration provider <http://python-dependency-injector.ets-labs.org/providers/configuration.html>`_.
- **Containers**. Provides declarative and dynamic containers.
See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_.
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc.
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
- **Asynchronous**. Supports asynchronous injections.
See `Asynchronous injections <https://python-dependency-injector.ets-labs.org/providers/async.html>`_.
- **Typing**. Provides typing stubs, ``mypy``-friendly.
See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
See `Containers <http://python-dependency-injector.ets-labs.org/containers/index.html>`_.
- **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
- **Typing**. Provides typing stubs, ``mypy``-friendly.
See `Typing and mypy <http://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
.. code-block:: python
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide
class Container(containers.DeclarativeContainer):
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
config = providers.Configuration()
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout,
)
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout.as_int(),
)
service = providers.Factory(
Service,
api_client=api_client,
)
service = providers.Factory(
Service,
api_client=api_client,
)
@inject
def main(service: Service = Provide[Container.service]) -> None:
...
def main(service: Service = Provide[Container.service]):
...
if __name__ == "__main__":
container = Container()
container.config.api_key.from_env("API_KEY", required=True)
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
container.wire(modules=[__name__])
if __name__ == '__main__':
container = Container()
main() # <-- dependency is injected automatically
container.config.api_key.from_env('API_KEY')
container.config.timeout.from_env('TIMEOUT')
with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically
container.wire(modules=[sys.modules[__name__]])
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically.
main()
When you do testing, you call the ``container.api_client.override()`` method to replace the real API
client with a mock. When you call ``main()``, the mock is injected.
With the ``Dependency Injector`` you keep **application structure in one place**.
This place is called **the container**. You use the container to manage all the components of the
application. All the component dependencies are defined explicitly. This provides the control on
the application structure. It is **easy to understand and change** it.
You can override any provider with another provider.
It also helps you in a re-configuring project for different environments: replace an API client
with a stub on the dev or stage.
With the ``Dependency Injector``, object assembling is consolidated in a container. Dependency injections are defined explicitly.
This makes it easier to understand and change how an application works.
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-map.svg
:target: https://github.com/ets-labs/python-dependency-injector
*The container is like a map of your application. You always know what depends on what.*
Visit the docs to know more about the
`Dependency injection and inversion of control in Python <https://python-dependency-injector.ets-labs.org/introduction/di_in_python.html>`_.
`Dependency injection and inversion of control in Python <http://python-dependency-injector.ets-labs.org/introduction/di_in_python.html>`_.
Installation
------------
@ -144,67 +131,83 @@ The package is available on the `PyPi`_::
Documentation
-------------
The documentation is available `here <https://python-dependency-injector.ets-labs.org/>`_.
The documentation is available on the `Read The Docs <http://python-dependency-injector.ets-labs.org/>`_
Examples
--------
Choose one of the following:
- `Application example (single container) <https://python-dependency-injector.ets-labs.org/examples/application-single-container.html>`_
- `Application example (multiple containers) <https://python-dependency-injector.ets-labs.org/examples/application-multiple-containers.html>`_
- `Decoupled packages example (multiple containers) <https://python-dependency-injector.ets-labs.org/examples/decoupled-packages.html>`_
- `Boto3 example <https://python-dependency-injector.ets-labs.org/examples/boto3.html>`_
- `Django example <https://python-dependency-injector.ets-labs.org/examples/django.html>`_
- `Flask example <https://python-dependency-injector.ets-labs.org/examples/flask.html>`_
- `Aiohttp example <https://python-dependency-injector.ets-labs.org/examples/aiohttp.html>`_
- `Sanic example <https://python-dependency-injector.ets-labs.org/examples/sanic.html>`_
- `FastAPI example <https://python-dependency-injector.ets-labs.org/examples/fastapi.html>`_
- `FastAPI + Redis example <https://python-dependency-injector.ets-labs.org/examples/fastapi-redis.html>`_
- `FastAPI + SQLAlchemy example <https://python-dependency-injector.ets-labs.org/examples/fastapi-sqlalchemy.html>`_
- `Application example (single container) <http://python-dependency-injector.ets-labs.org/examples/application-single-container.html>`_
- `Application example (multiple containers) <http://python-dependency-injector.ets-labs.org/examples/application-multiple-containers.html>`_
- `Decoupled packages example (multiple containers) <http://python-dependency-injector.ets-labs.org/examples/decoupled-packages.html>`_
Tutorials
---------
Choose one of the following:
- `Flask web application tutorial <https://python-dependency-injector.ets-labs.org/tutorials/flask.html>`_
- `Aiohttp REST API tutorial <https://python-dependency-injector.ets-labs.org/tutorials/aiohttp.html>`_
- `Asyncio monitoring daemon tutorial <https://python-dependency-injector.ets-labs.org/tutorials/asyncio-daemon.html>`_
- `CLI application tutorial <https://python-dependency-injector.ets-labs.org/tutorials/cli.html>`_
- `Flask web application tutorial <http://python-dependency-injector.ets-labs.org/tutorials/flask.html>`_
- `Aiohttp REST API tutorial <http://python-dependency-injector.ets-labs.org/tutorials/aiohttp.html>`_
- `Asyncio monitoring daemon tutorial <http://python-dependency-injector.ets-labs.org/tutorials/asyncio-daemon.html>`_
- `CLI application tutorial <http://python-dependency-injector.ets-labs.org/tutorials/cli.html>`_
Concept
-------
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
``Dependency Injector`` stands on two principles:
.. code-block:: bash
- Explicit is better than implicit (PEP20).
- Do no magic to your code.
Explicit is better than implicit
How is it different from the other frameworks?
You need to specify how to assemble and where to inject the dependencies explicitly.
- **No autowiring.** The framework does NOT do any autowiring / autoresolving of the dependencies. You need to specify everything explicitly. Because *"Explicit is better than implicit" (PEP20)*.
- **Does not pollute your code.** Your application does NOT know and does NOT depend on the framework. No ``@inject`` decorators, annotations, patching or any other magic tricks.
The power of the framework is in its simplicity.
``Dependency Injector`` is a simple tool for the powerful concept.
``Dependency Injector`` makes a simple contract with you:
- You tell the framework how to assemble your objects
- The framework does it for you
The power of the ``Dependency Injector`` is in its simplicity and straightforwardness. It is a simple tool for the powerful concept.
Frequently asked questions
--------------------------
What is dependency injection?
What is the dependency injection?
- dependency injection is a principle that decreases coupling and increases cohesion
Why should I do the dependency injection?
- your code becomes more flexible, testable, and clear 😎
- your code becomes more flexible, testable and clear
- you have no problems when you need to understand how it works or change it 😎
How do I start applying the dependency injection?
How do I start doing the dependency injection?
- you start writing the code following the dependency injection principle
- you register all of your application components and their dependencies in the container
- when you need a component, you specify where to inject it or get it from the container
- when you need a component, you get it from the container
Why do I need a framework for this?
- you need the framework for this to not create it by your own
- this framework gives you the container and the providers
- the container is like a dictionary with the batteries 🔋
- the providers manage the lifetime of your components, you will need factories, singletons, smart config object etc
What price do I pay and what do I get?
- you need to explicitly specify the dependencies
- you need to explicitly specify the dependencies in the container
- it will be extra work in the beginning
- it will payoff as project grows
- it will payoff when project grows or in two weeks 😊 (when you forget what project was about)
What features does the framework have?
- building objects graph
- smart configuration object
- providers: factory, singleton, thread locals registers, etc
- positional and keyword context injections
- overriding of the objects in any part of the graph
What features the framework does NOT have?
- autowiring / autoresolving of the dependencies
- the annotations and ``@inject``-like decorators
Have a question?
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_

View File

@ -1,9 +0,0 @@
.no-border {
border: 0 !important;
box-shadow: none !important;
-webkit-box-shadow: none !important;
}
.no-border td {
border: 0px !important;
padding: 0px 10px 0px 0px !important;
}

11
docs/_static/disqus.js vendored Normal file
View File

@ -0,0 +1,11 @@
var disqus_shortname;
var disqus_identifier;
$(function() {
var disqus_thread = $("#disqus_thread");
disqus_shortname = disqus_thread.data('disqus-shortname');
disqus_identifier = disqus_thread.data('disqus-identifier');
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
});

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,3 @@
.rst-content .highlight>pre, .rst-content .linenodiv>pre {
line-height: normal;
}

View File

@ -1 +0,0 @@
<iframe src="https://github.com/sponsors/rmk135/button" title="Sponsor Dependency Injector" height="32" width="114" style="border: 0; border-radius: 6px;"></iframe>

9
docs/api/aiohttpext.rst Normal file
View File

@ -0,0 +1,9 @@
dependency_injector.ext.aiohttp
===============================
.. automodule:: dependency_injector.ext.aiohttp
:members:
:show-inheritance:
.. disqus::

View File

@ -1,9 +0,0 @@
dependency_injector.ext.starlette
=================================
.. automodule:: dependency_injector.ext.starlette
:members:
:inherited-members:
:show-inheritance:
.. disqus::

9
docs/api/flaskext.rst Normal file
View File

@ -0,0 +1,9 @@
dependency_injector.ext.flask
=============================
.. automodule:: dependency_injector.ext.flask
:members:
:show-inheritance:
.. disqus::

View File

@ -2,11 +2,11 @@ API Documentation
=================
.. toctree::
:maxdepth: 2
:maxdepth: 2
top-level
providers
containers
wiring
errors
asgi-lifespan
top_level
providers
containers
errors
aiohttpext
flaskext

View File

@ -1,7 +0,0 @@
dependency_injector.wiring
=============================
.. automodule:: dependency_injector.wiring
:members:
.. disqus::

View File

@ -20,49 +20,49 @@ import alabaster
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath(".."))
sys.path.insert(0, os.path.abspath('..'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = "1.0"
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named "sphinx.ext.*") or your custom
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"alabaster",
"sphinx.ext.autodoc",
"sphinx_disqus.disqus",
'alabaster',
'sphinx.ext.autodoc',
'sphinxcontrib.disqus',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = [".rst", ".md"]
source_suffix = ".rst"
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = "utf-8-sig"
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
master_doc = 'index'
# General information about the project.
project = "Dependency Injector"
copyright = "2024, Roman Mogylatov"
author = "Roman Mogylatov"
project = u'Dependency Injector'
copyright = u'2020, ETS Labs'
author = u'ETS Labs'
# The version info for the project you"re documenting, acts as replacement for
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# Getting version:
with open("../src/dependency_injector/__init__.py") as init_file:
version = re.search("__version__ = \"(.*?)\"", init_file.read()).group(1)
with open('../src/dependency_injector/__init__.py') as init_file:
version = re.search('__version__ = \'(.*?)\'', init_file.read()).group(1)
# The full version, including alpha/beta/rc tags.
release = version
@ -72,23 +72,23 @@ release = version
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ""
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = "%B %d, %Y"
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["_build"]
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, "()" will be appended to :func: etc. cross-reference text.
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
@ -100,7 +100,7 @@ exclude_patterns = ["_build"]
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
@ -116,8 +116,8 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# html_theme = "sphinx_rtd_theme"
html_theme = "alabaster"
# html_theme = 'sphinx_rtd_theme'
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@ -141,24 +141,21 @@ html_theme_path = [alabaster.get_path()]
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
html_favicon = "favicon.ico"
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_css_files = [
"custom.css",
]
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not "", a "Last updated on:" timestamp is inserted at every page bottom,
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = "%b %d, %Y"
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
@ -192,50 +189,50 @@ html_css_files = [
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ""
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# "da", "de", "en", "es", "fi", "fr", "hu", "it", "ja"
# "nl", "no", "pt", "ro", "ru", "sv", "tr"
#html_search_language = "en"
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only "ja" uses this config value
#html_search_options = {"type": "default"}
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = "scorer.js"
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = "dependency_injectordoc"
htmlhelp_basename = 'dependency_injectordoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ("letterpaper" or "a4paper").
#"papersize": "letterpaper",
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ("10pt", "11pt" or "12pt").
#"pointsize": "10pt",
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#"preamble": "",
#'preamble': '',
# Latex figure (float) alignment
#"figure_align": "htbp",
#'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, "dependency_injector.tex", u"Dependency Injector Documentation",
u"Roman Mogylatov", "manual"),
(master_doc, 'dependency_injector.tex', u'Dependency Injector Documentation',
u'ETS Labs', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@ -264,7 +261,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, "Dependency Injector", u"Dependency Injector Documentation",
(master_doc, 'Dependency Injector', u'Dependency Injector Documentation',
[author], 1)
]
@ -278,9 +275,9 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, "Dependency Injector", u"Dependency Injector Documentation",
author, "Dependency Injector", "Dependency injection microframework for Python",
"Miscellaneous"),
(master_doc, 'Dependency Injector', u'Dependency Injector Documentation',
author, 'Dependency Injector', 'Dependency injection microframework for Python',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
@ -289,25 +286,23 @@ texinfo_documents = [
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: "footnote", "no", or "inline".
#texinfo_show_urls = "footnote"
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node"s menu.
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
autodoc_member_order = "bysource"
autodoc_member_order = 'bysource'
disqus_shortname = "python-dependency-injector"
disqus_shortname = 'python-dependency-injector'
html_theme_options = {
"github_user": "ets-labs",
"github_repo": "python-dependency-injector",
"github_type": "star",
"github_button": True,
"github_banner": True,
"logo": "logo.svg",
"description": "Dependency injection framework for Python by Roman Mogylatov",
"code_font_size": "10pt",
"analytics_id": "UA-67012059-1",
"donate_url": "https://github.com/sponsors/rmk135",
'github_user': 'ets-labs',
'github_repo': 'python-dependency-injector',
'github_type': 'star',
'github_button': True,
'github_banner': True,
'logo': 'logo.svg',
'description': 'Dependency injection framework for Python',
'code_font_size': '10pt',
}

View File

@ -1,18 +0,0 @@
.. _check-container-dependencies:
Check container dependencies
----------------------------
To check container dependencies use method ``.check_dependencies()``.
.. literalinclude:: ../../examples/containers/check_dependencies.py
:language: python
:lines: 3-
:emphasize-lines: 12
Method ``.check_dependencies()`` raises an error if container has any undefined dependencies.
If all dependencies are provided or have defaults, no error is raised.
See also: :ref:`dependency-provider`.
.. disqus::

View File

@ -1,23 +0,0 @@
Container copying
-----------------
You can create declarative container copies using ``@containers.copy()`` decorator.
.. literalinclude:: ../../examples/containers/declarative_copy_decorator1.py
:language: python
:lines: 3-
:emphasize-lines: 18-22
Decorator ``@containers.copy()`` copies providers from source container to destination container.
Destination container provider will replace source provider, if names match.
Decorator ``@containers.copy()`` helps you when you create derived declarative containers
from the base one. Base container often keeps default dependencies while derived containers define
overriding providers. Without ``@containers.copy()`` decorator, overridden providers are available
in the derived container, but base class dependencies continue to be bound to the base class providers.
.. literalinclude:: ../../examples/containers/declarative_copy_decorator2.py
:language: python
:lines: 11-
.. disqus::

View File

@ -34,39 +34,10 @@ Injections in the declarative container are done the usual way:
:language: python
:lines: 3-
You can override container providers while creating a container instance:
You can override the container providers when you create the container instance:
.. literalinclude:: ../../examples/containers/declarative_override_providers.py
:language: python
:lines: 3-
:emphasize-lines: 13
Alternatively, you can call ``container.override_providers()`` method when the container instance
already exists:
.. code-block:: python
:emphasize-lines: 3
container = Container()
container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar))
assert isinstance(container.foo(), mock.Mock)
assert isinstance(container.bar(), mock.Mock)
You can also use ``container.override_providers()`` with a context manager to reset
provided overriding after the context is closed:
.. code-block:: python
:emphasize-lines: 3
container = Container()
with container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar)):
assert isinstance(container.foo(), mock.Mock)
assert isinstance(container.bar(), mock.Mock)
assert isinstance(container.foo(), Foo)
assert isinstance(container.bar(), Bar)
.. disqus::

View File

@ -23,7 +23,3 @@ Containers module API docs - :py:mod:`dependency_injector.containers`.
dynamic
specialization
overriding
copying
reset_singletons
check_dependencies
traversal

View File

@ -21,20 +21,4 @@ The container also has:
:py:class:`DynamicContainer` has the same functionality.
Another possible way to override container providers on declarative level is
``@containers.override()`` decorator:
.. literalinclude:: ../../examples/containers/declarative_override_decorator.py
:language: python
:lines: 3-
:emphasize-lines: 12-16
Decorator ``@containers.override()`` takes a container for overriding as an argument.
This container providers will be overridden by the providers with the same names from
the decorated container.
It helps to change the behaviour of application by importing extension modules but not a code change.
Imported module can override providers in main container. While the code uses main container as
before, the overridden providers provide components defined in the extension module.
.. disqus::

View File

@ -1,31 +0,0 @@
.. _reset-container-singletons:
Reset container singletons
--------------------------
To reset all container singletons use method ``.reset_singletons()``.
.. literalinclude:: ../../examples/containers/reset_singletons.py
:language: python
:lines: 3-
:emphasize-lines: 16
Method ``.reset_singletons()`` also resets singletons in sub-containers: ``providers.Container`` and
``providers.DependenciesContainer.``
.. literalinclude:: ../../examples/containers/reset_singletons_subcontainers.py
:language: python
:lines: 3-
:emphasize-lines: 21
You can use ``.reset_singletons()`` method with a context manager. Singletons will be reset on
both entering and exiting a context.
.. literalinclude:: ../../examples/containers/reset_singletons_with.py
:language: python
:lines: 3-
:emphasize-lines: 14-15
See also: :ref:`singleton-provider`.
.. disqus::

View File

@ -1,33 +0,0 @@
Container providers traversal
-----------------------------
To traverse container providers use method ``.traverse()``.
.. literalinclude:: ../../examples/containers/traverse.py
:language: python
:lines: 3-
:emphasize-lines: 38
Method ``.traverse()`` returns a generator. Traversal generator visits all container providers.
This includes nested providers even if they are not present on the root level of the container.
Traversal generator guarantees that each container provider will be visited only once.
It can traverse cyclic provider graphs.
Traversal generator does not guarantee traversal order.
You can use ``types=[...]`` argument to filter providers. Traversal generator will only return
providers matching specified types.
.. code-block:: python
:emphasize-lines: 3
container = Container()
for provider in container.traverse(types=[providers.Resource]):
print(provider)
# <dependency_injector.providers.Resource(<function init_database at 0x10bd2cb80>) at 0x10d346b40>
# <dependency_injector.providers.Resource(<function init_cache at 0x10be373a0>) at 0x10d346bc0>
.. disqus::

View File

@ -19,7 +19,7 @@ additional arguments.
)
if __name__ == "__main__":
if __name__ == '__main__':
instance = concrete_factory()
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
@ -43,21 +43,21 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
providers.Factory(dict, arg1=1),
arg2=2,
)
print(chained_dict_factory()) # prints: {"arg1": 1, "arg2": 2}
print(chained_dict_factory()) # prints: {'arg1': 1, 'arg2': 2}
# 2. Keyword arguments of upper level factory have priority
chained_dict_factory = providers.Factory(
providers.Factory(dict, arg1=1),
arg1=2,
)
print(chained_dict_factory()) # prints: {"arg1": 2}
print(chained_dict_factory()) # prints: {'arg1': 2}
# 3. Keyword arguments provided from context have the most priority
chained_dict_factory = providers.Factory(
providers.Factory(dict, arg1=1),
arg1=2,
)
print(chained_dict_factory(arg1=3)) # prints: {"arg1": 3}
print(chained_dict_factory(arg1=3)) # prints: {'arg1': 3}
Credits

View File

@ -20,7 +20,7 @@ additional arguments.
)
if __name__ == "__main__":
if __name__ == '__main__':
instance = concrete_factory()
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
@ -46,7 +46,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
arg1=1,
)
dict_factory = factory_of_dict_factories(arg2=2)
print(dict_factory()) # prints: {"arg1": 1, "arg2": 2}
print(dict_factory()) # prints: {'arg1': 1, 'arg2': 2}
# 2. Keyword arguments of upper level factory have priority
factory_of_dict_factories = providers.Factory(
@ -55,7 +55,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
arg1=1,
)
dict_factory = factory_of_dict_factories(arg1=2)
print(dict_factory()) # prints: {"arg1": 2}
print(dict_factory()) # prints: {'arg1': 2}
# 3. Keyword arguments provided from context have the most priority
factory_of_dict_factories = providers.Factory(
@ -64,7 +64,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
arg1=1,
)
dict_factory = factory_of_dict_factories(arg1=2)
print(dict_factory(arg1=3)) # prints: {"arg1": 3}
print(dict_factory(arg1=3)) # prints: {'arg1': 3}
Credits
-------

View File

@ -1,83 +0,0 @@
.. _aiohttp-example:
Aiohttp example
===============
.. meta::
:keywords: Python,Dependency Injection,Aiohttp,Example
:description: This example demonstrates a usage of the Aiohttp and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Aiohttp <https://docs.aiohttp.org/>`_.
The example application is a REST API that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
:ref:`aiohttp-tutorial` demonstrates how to build this application step-by-step.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── giphynavigator/
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── giphy.py
│ ├── handlers.py
│ ├── services.py
│ └── tests.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``giphynavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/containers.py
:language: python
Handlers
--------
Handler has dependencies on search service and some config options. The dependencies are injected
using :ref:`wiring` feature.
Listing of ``giphynavigator/handlers.py``:
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/handlers.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the ``handlers`` module, creates
``Aiohttp`` app and setup routes.
Listing of ``giphynavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace giphy client with a mock ``giphynavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/aiohttp/giphynavigator/tests.py
:language: python
:emphasize-lines: 32,59,73
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -84,6 +84,4 @@ Run the application
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-multiple-containers>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -90,6 +90,4 @@ Run the application
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-single-container>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,22 +0,0 @@
.. _boto3-example:
Boto3 example
=============
.. meta::
:keywords: Python,Dependency Injection,Boto3,AWS,Amazon Web Services,S3,SQS,Rout53,EC2,Lambda,Example
:description: This example demonstrates a usage of Boto3 AWS client and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Boto3 <https://github.com/boto/boto3>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/boto3-session>`_.
Listing of ``boto3_session_example.py``:
.. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py
:language: python
.. include:: ../sponsor.rst
.. disqus::

View File

@ -129,6 +129,4 @@ Run the application
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,99 +0,0 @@
.. _django-example:
Django example
==============
.. meta::
:keywords: Python,Dependency Injection,Django,Example
:description: This example demonstrates a usage of the Django and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Django <https://www.djangoproject.com/>`_.
The example application helps to search for repositories on the Github.
.. image:: images/django.png
:width: 100%
:align: center
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
Application structure
---------------------
Application has standard Django project structure. It consists of ``githubnavigator`` project package and
``web`` application package:
.. code-block:: bash
./
├── githubnavigator/
│ ├── __init__.py
│ ├── asgi.py
│ ├── containers.py
│ ├── services.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── web/
│ ├── templates/
│ │ ├── base.html
│ │ └── index.html
│ ├── __init__.py
│ ├── apps.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── manage.py
└── requirements.txt
Container
---------
Declarative container is defined in ``githubnavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/django/githubnavigator/containers.py
:language: python
Container instance is created in ``githubnavigator/__init__.py``:
.. literalinclude:: ../../examples/miniapps/django/githubnavigator/__init__.py
:language: python
Views
-----
View has dependencies on search service and some config options. The dependencies are injected
using :ref:`wiring` feature.
Listing of ``web/views.py``:
.. literalinclude:: ../../examples/miniapps/django/web/views.py
:language: python
App config
----------
Container is wired to the ``views`` module in the app config ``web/apps.py``:
.. literalinclude:: ../../examples/miniapps/django/web/apps.py
:language: python
:emphasize-lines: 12
Tests
-----
Tests use :ref:`provider-overriding` feature to replace github client with a mock ``web/tests.py``:
.. literalinclude:: ../../examples/miniapps/django/web/tests.py
:language: python
:emphasize-lines: 39,60
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,100 +0,0 @@
.. _fastapi-redis-example:
FastAPI + Redis example
=======================
.. meta::
:keywords: Python,Dependency Injection,FastAPI,Redis,Example
:description: This example demonstrates a usage of the FastAPI, Redis, and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_ and
`Redis <https://redis.io/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-redis>`_.
See also:
- Provider :ref:`async-injections`
- Resource provider :ref:`resource-async-initializers`
- Wiring :ref:`async-injections-wiring`
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── fastapiredis/
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── redis.py
│ ├── services.py
│ └── tests.py
├── docker-compose.yml
├── Dockerfile
└── requirements.txt
Redis
-----
Module ``redis`` defines Redis connection pool initialization and shutdown. See ``fastapiredis/redis.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/redis.py
:language: python
Service
-------
Module ``services`` contains example service. Service has a dependency on Redis connection pool.
It uses it for getting and setting a key asynchronously. Real life service will do something more meaningful.
See ``fastapiredis/services.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/services.py
:language: python
Container
---------
Declarative container wires example service with Redis connection pool. See ``fastapiredis/containers.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/containers.py
:language: python
Application
-----------
Module ``application`` creates ``FastAPI`` app, setup endpoint, and init container.
Endpoint ``index`` has a dependency on example service. The dependency is injected using :ref:`wiring` feature.
Listing of ``fastapiredis/application.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace example service with a mock. See ``fastapiredis/tests.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/tests.py
:language: python
:emphasize-lines: 24
Sources
-------
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-redis>`_.
See also:
- Provider :ref:`async-injections`
- Resource provider :ref:`resource-async-initializers`
- Wiring :ref:`async-injections-wiring`
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,121 +0,0 @@
.. _fastapi-sqlalchemy-example:
FastAPI + SQLAlchemy example
============================
.. meta::
:keywords: Python,Dependency Injection,FastAPI,SQLAlchemy,Example
:description: This example demonstrates a usage of the FastAPI, SQLAlchemy, and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_ and
`SQLAlchemy <https://www.sqlalchemy.org/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_.
Thanks to `@ShvetsovYura <https://github.com/ShvetsovYura>`_ for providing initial example:
`FastAPI_DI_SqlAlchemy <https://github.com/ShvetsovYura/FastAPI_DI_SqlAlchemy>`_.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── webapp/
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── database.py
│ ├── endpoints.py
│ ├── models.py
│ ├── repositories.py
│ ├── services.py
│ └── tests.py
├── config.yml
├── docker-compose.yml
├── Dockerfile
└── requirements.txt
Application factory
-------------------
Application factory creates container, wires it with the ``endpoints`` module, creates
``FastAPI`` app, and setup routes.
Application factory also creates database if it does not exist.
Listing of ``webapp/application.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/application.py
:language: python
Endpoints
---------
Module ``endpoints`` contains example endpoints. Endpoints have a dependency on user service.
User service is injected using :ref:`wiring` feature. See ``webapp/endpoints.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/endpoints.py
:language: python
Container
---------
Declarative container wires example user service, user repository, and utility database class.
See ``webapp/containers.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/containers.py
:language: python
Services
--------
Module ``services`` contains example user service. See ``webapp/services.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/services.py
:language: python
Repositories
------------
Module ``repositories`` contains example user repository. See ``webapp/repositories.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/repositories.py
:language: python
Models
------
Module ``models`` contains example SQLAlchemy user model. See ``webapp/models.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/models.py
:language: python
Database
--------
Module ``database`` defines declarative base and utility class with engine and session factory.
See ``webapp/database.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/database.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace repository with a mock. See ``webapp/tests.py``:
.. literalinclude:: ../../examples/miniapps/fastapi-sqlalchemy/webapp/tests.py
:language: python
:emphasize-lines: 25, 45, 58, 74, 86, 97
Sources
-------
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,81 +0,0 @@
.. _fastapi-example:
FastAPI example
===============
.. meta::
:keywords: Python,Dependency Injection,FastAPI,Example
:description: This example demonstrates a usage of the FastAPI and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_.
The example application is a REST API that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── giphynavigator/
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── endpoints.py
│ ├── giphy.py
│ ├── services.py
│ └── tests.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``giphynavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/containers.py
:language: python
Endpoints
---------
Endpoint has a dependency on search service. There are also some config options that are used as default values.
The dependencies are injected using :ref:`wiring` feature.
Listing of ``giphynavigator/endpoints.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/endpoints.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the ``endpoints`` module, creates
``FastAPI`` app, and setup routes.
Listing of ``giphynavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace giphy client with a mock ``giphynavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/fastapi/giphynavigator/tests.py
:language: python
:emphasize-lines: 29,57,72
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,48 +0,0 @@
.. _fastdepends-example:
FastDepends example
===================
.. meta::
:keywords: Python,Dependency Injection,FastDepends,Example
:description: This example demonstrates a usage of the FastDepends and Dependency Injector.
This example demonstrates how to use ``Dependency Injector`` with `FastDepends <https://github.com/Lancetnik/FastDepends>`_, a lightweight dependency injection framework inspired by FastAPI's dependency system, but without the web framework components.
Basic Usage
-----------
The integration between FastDepends and Dependency Injector is straightforward. Simply use Dependency Injector's ``Provide`` marker within FastDepends' ``Depends`` function:
.. code-block:: python
import sys
from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
from fast_depends import Depends
class CoefficientService:
@staticmethod
def get_coefficient() -> float:
return 1.2
class Container(containers.DeclarativeContainer):
service = providers.Factory(CoefficientService)
@inject
def apply_coefficient(
a: int,
coefficient_provider: CoefficientService = Depends(Provide[Container.service]),
) -> float:
return a * coefficient_provider.get_coefficient()
container = Container()
container.wire(modules=[sys.modules[__name__]])
apply_coefficient(100) == 120.0

View File

@ -1,91 +0,0 @@
.. _flask-blueprints-example:
Flask blueprints example
========================
.. meta::
:keywords: Python,Dependency Injection,Flask,Blueprints,Example
:description: This example demonstrates a usage of the Flask Blueprints and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Flask <https://flask.palletsprojects.com/en/1.1.x/>`_
blueprints.
The example application helps to search for repositories on the Github.
.. image:: images/flask.png
:width: 100%
:align: center
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── githubnavigator/
│ ├── blueprints
│ │ ├── __init__.py
│ │ └── example.py
│ ├── templates
│ │ ├── base.html
│ │ └── index.py
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── services.py
│ └── tests.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``githubnavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/containers.py
:language: python
Blueprints
----------
Blueprint's view has dependencies on search service and some config options. The dependencies are injected
using :ref:`wiring` feature.
Listing of ``githubnavigator/blueprints/example.py``:
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/blueprints/example.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the blueprints, creates
``Flask`` app, and setup routes.
Listing of ``githubnavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace github client with a mock ``githubnavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/flask-blueprints/githubnavigator/tests.py
:language: python
:emphasize-lines: 44,67
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -1,89 +0,0 @@
.. _flask-example:
Flask example
=============
.. meta::
:keywords: Python,Dependency Injection,Flask,Example
:description: This example demonstrates a usage of the Flask and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Flask <https://flask.palletsprojects.com/en/1.1.x/>`_.
The example application helps to search for repositories on the Github.
.. image:: images/flask.png
:width: 100%
:align: center
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
:ref:`flask-tutorial` demonstrates how to build this application step-by-step.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── githubnavigator/
│ ├── templates
│ │ ├── base.html
│ │ └── index.py
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ ├── services.py
│ ├── tests.py
│ └── views.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``githubnavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/containers.py
:language: python
Views
-----
View has dependencies on search service and some config options. The dependencies are injected
using :ref:`wiring` feature.
Listing of ``githubnavigator/views.py``:
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/views.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the ``views`` module, creates
``Flask`` app and setup routes.
Listing of ``githubnavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace github client with a mock ``githubnavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/flask/githubnavigator/tests.py
:language: python
:emphasize-lines: 44,67
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
.. include:: ../sponsor.rst
.. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

View File

@ -13,15 +13,5 @@ Explore the examples to see the ``Dependency Injector`` in action.
application-single-container
application-multiple-containers
decoupled-packages
boto3
django
flask
flask-blueprints
aiohttp
sanic
fastapi
fastapi-redis
fastapi-sqlalchemy
fastdepends
.. disqus::

View File

@ -1,82 +0,0 @@
.. _sanic-example:
Sanic example
==============
.. meta::
:keywords: Python,Dependency Injection,Sanic,Example
:description: This example demonstrates a usage of the Sanic and Dependency Injector.
This example shows how to use ``Dependency Injector`` with `Sanic <https://sanic.readthedocs.io/en/latest/>`_.
The example application is a REST API that searches for funny GIFs on the `Giphy <https://giphy.com/>`_.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
Application structure
---------------------
Application has next structure:
.. code-block:: bash
./
├── giphynavigator/
│ ├── __init__.py
│ ├── __main__.py
│ ├── application.py
│ ├── containers.py
│ ├── giphy.py
│ ├── handlers.py
│ ├── services.py
│ └── tests.py
├── config.yml
└── requirements.txt
Container
---------
Declarative container is defined in ``giphynavigator/containers.py``:
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/containers.py
:language: python
Handlers
--------
Handler has dependencies on search service and some config options. The dependencies are injected
using :ref:`wiring` feature.
Listing of ``giphynavigator/handlers.py``:
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/handlers.py
:language: python
Application factory
-------------------
Application factory creates container, wires it with the ``handlers`` module, creates
``Sanic`` app and setup routes.
Listing of ``giphynavigator/application.py``:
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/application.py
:language: python
Tests
-----
Tests use :ref:`provider-overriding` feature to replace giphy client with a mock ``giphynavigator/tests.py``:
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/tests.py
:language: python
:emphasize-lines: 34,61,75
Sources
-------
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
.. include:: ../sponsor.rst
.. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -34,15 +34,15 @@ Dependency Injector --- Dependency injection framework for Python
:target: https://pypi.org/project/dependency-injector/
:alt: Supported Python implementations
.. image:: https://static.pepy.tech/badge/dependency-injector
.. image:: https://pepy.tech/badge/dependency-injector
:target: https://pepy.tech/project/dependency-injector
:alt: Downloads
.. image:: https://static.pepy.tech/badge/dependency-injector/month
.. image:: https://pepy.tech/badge/dependency-injector/month
:target: https://pepy.tech/project/dependency-injector
:alt: Downloads
.. image:: https://static.pepy.tech/badge/dependency-injector/week
.. image:: https://pepy.tech/badge/dependency-injector/week
:target: https://pepy.tech/project/dependency-injector
:alt: Downloads
@ -50,10 +50,14 @@ Dependency Injector --- Dependency injection framework for Python
:target: https://pypi.org/project/dependency-injector/
:alt: Wheel
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master
:target: https://github.com/ets-labs/python-dependency-injector/actions
.. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
:target: https://travis-ci.org/ets-labs/python-dependency-injector
:alt: Build Status
.. image:: http://readthedocs.org/projects/python-dependency-injector/badge/?version=latest
:target: http://python-dependency-injector.ets-labs.org/
:alt: Docs Status
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
:alt: Coverage Status
@ -65,69 +69,64 @@ It helps implementing the dependency injection principle.
Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
that help assemble your objects. See :ref:`providers`.
``List``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help assembling your
objects. See :ref:`providers`.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
and configuring dev/stage environment to replace API clients with stubs etc. See
and configuring dev / stage environment to replace API clients with stubs etc. See
:ref:`provider-overriding`.
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings,
environment variables, and dictionaries. See :ref:`configuration-provider`.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
See :ref:`resource-provider`.
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables
and dictionaries. See :ref:`configuration-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
.. code-block:: python
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide
class Container(containers.DeclarativeContainer):
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
config = providers.Configuration()
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout,
)
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout.as_int(),
)
service = providers.Factory(
Service,
api_client=api_client,
)
service = providers.Factory(
Service,
api_client=api_client,
)
@inject
def main(service: Service = Provide[Container.service]) -> None:
...
def main(service: Service = Provide[Container.service]):
...
if __name__ == "__main__":
container = Container()
container.config.api_key.from_env("API_KEY", required=True)
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
container.wire(modules=[__name__])
if __name__ == '__main__':
container = Container()
main() # <-- dependency is injected automatically
container.config.api_key.from_env('API_KEY')
container.config.timeout.from_env('TIMEOUT')
with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically
container.wire(modules=[sys.modules[__name__]])
With the ``Dependency Injector``, object assembling is consolidated in the container.
Dependency injections are defined explicitly.
This makes it easier to understand and change how the application works.
main()
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
With the ``Dependency Injector`` you keep **application structure in one place**.
This place is called **the container**. You use the container to manage all the components of the
application. All the component dependencies are defined explicitly. This provides the control on
the application structure. It is **easy to understand and change** it.
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-map.svg
:target: https://github.com/ets-labs/python-dependency-injector
*The container is like a map of your application. You always know what depends on what.*
Explore the documentation to know more about the ``Dependency Injector``.
.. _contents:
@ -135,16 +134,15 @@ Explore the documentation to know more about the ``Dependency Injector``.
Contents
--------
.. toctree::
:maxdepth: 2
.. toctree::
:maxdepth: 2
introduction/index
examples/index
tutorials/index
providers/index
containers/index
wiring
examples-other/index
api/index
main/feedback
main/changelog
introduction/index
examples/index
tutorials/index
providers/index
containers/index
examples-other/index
api/index
main/feedback
main/changelog

View File

@ -9,27 +9,27 @@ Dependency injection and inversion of control in Python
Dependency Injector, its container, Factory, Singleton and Configuration
providers. The example show how to use Dependency Injector providers overriding
feature for testing or configuring project in different environments and explains
why it's better than monkey-patching.
why it's better then monkey-patching.
Originally dependency injection pattern got popular in languages with static typing like Java.
Dependency injection is a principle that helps to achieve an inversion of control. A
dependency injection framework can significantly improve the flexibility of a language
with static typing. Implementation of a dependency injection framework for a language
with static typing is not something that one can do quickly. It will be a quite complex thing
Originally dependency injection pattern got popular in the languages with a static typing,
like Java. Dependency injection is a principle that helps to achieve an inversion of control.
Dependency injection framework can significantly improve a flexibility of the language
with a static typing. Implementation of a dependency injection framework for a language
with a static typing is not something that one can do quickly. It will be a quite complex thing
to be done well. And will take time.
Python is an interpreted language with dynamic typing. There is an opinion that dependency
Python is an interpreted language with a dynamic typing. There is an opinion that dependency
injection doesn't work for it as well as it does for Java. A lot of the flexibility is already
built-in. Also, there is an opinion that a dependency injection framework is something that
built in. Also there is an opinion that a dependency injection framework is something that
Python developer rarely needs. Python developers say that dependency injection can be implemented
easily using language fundamentals.
This page describes the advantages of applying dependency injection in Python. It
contains Python examples that show how to implement dependency injection. It demonstrates the usage
of the ``Dependency Injector`` framework, its container, ``Factory``, ``Singleton``,
and ``Configuration`` providers. The example shows how to use providers' overriding feature
of ``Dependency Injector`` for testing or re-configuring a project in different environments and
explains why it's better than monkey-patching.
This page describes the advantages of the dependency injection usage in Python. It
contains Python examples that show how to implement dependency injection. It demonstrates a usage
of the dependency injection framework ``Dependency Injector``, its container, ``Factory``,
``Singleton`` and ``Configuration`` providers. The example shows how to use ``Dependency Injector``
providers overriding feature for testing or configuring project in different environments and
explains why it's better then monkey-patching.
What is dependency injection?
-----------------------------
@ -44,14 +44,14 @@ What is coupling and cohesion?
Coupling and cohesion are about how tough the components are tied.
- **High coupling**. If the coupling is high it's like using superglue or welding. No easy way
- **High coupling**. If the coupling is high it's like using a superglue or welding. No easy way
to disassemble.
- **High cohesion**. High cohesion is like using screws. Quite easy to disassemble and
re-assemble in a different way. It is an opposite to high coupling.
- **High cohesion**. High cohesion is like using the screws. Very easy to disassemble and
assemble back or assemble a different way. It is an opposite to high coupling.
Cohesion often correlates with coupling. Higher cohesion usually leads to lower coupling and vice versa.
When the cohesion is high the coupling is low.
Low coupling brings flexibility. Your code becomes easier to change and test.
Low coupling brings a flexibility. Your code becomes easier to change and test.
How to implement the dependency injection?
@ -66,25 +66,21 @@ Before:
class ApiClient:
def __init__(self) -> None:
self.api_key = os.getenv("API_KEY") # <-- dependency
self.timeout = int(os.getenv("TIMEOUT")) # <-- dependency
def __init__(self):
self.api_key = os.getenv('API_KEY') # <-- the dependency
self.timeout = os.getenv('TIMEOUT') # <-- the dependency
class Service:
def __init__(self) -> None:
self.api_client = ApiClient() # <-- dependency
def __init__(self):
self.api_client = ApiClient() # <-- the dependency
def main() -> None:
service = Service() # <-- dependency
...
if __name__ == '__main__':
service = Service()
if __name__ == "__main__":
main()
After:
.. code-block:: python
@ -94,30 +90,19 @@ After:
class ApiClient:
def __init__(self, api_key: str, timeout: int) -> None:
self.api_key = api_key # <-- dependency is injected
self.timeout = timeout # <-- dependency is injected
def __init__(self, api_key: str, timeout: int):
self.api_key = api_key # <-- the dependency is injected
self.timeout = timeout # <-- the dependency is injected
class Service:
def __init__(self, api_client: ApiClient) -> None:
self.api_client = api_client # <-- dependency is injected
def __init__(self, api_client: ApiClient):
self.api_client = api_client # <-- the dependency is injected
def main(service: Service) -> None: # <-- dependency is injected
...
if __name__ == "__main__":
main(
service=Service(
api_client=ApiClient(
api_key=os.getenv("API_KEY"),
timeout=int(os.getenv("TIMEOUT")),
),
),
)
if __name__ == '__main__':
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
``ApiClient`` is decoupled from knowing where the options come from. You can read a key and a
timeout from a configuration file or even get them from a database.
@ -125,22 +110,11 @@ timeout from a configuration file or even get them from a database.
``Service`` is decoupled from the ``ApiClient``. It does not create it anymore. You can provide a
stub or other compatible object.
Function ``main()`` is decoupled from ``Service``. It receives it as an argument.
Flexibility comes with a price.
Now you need to assemble and inject the objects like this:
Now you need to assemble the objects like this::
.. code-block:: python
main(
service=Service(
api_client=ApiClient(
api_key=os.getenv("API_KEY"),
timeout=int(os.getenv("TIMEOUT")),
),
),
)
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
The assembly code might get duplicated and it'll become harder to change the application structure.
@ -149,20 +123,18 @@ Here comes the ``Dependency Injector``.
What does the Dependency Injector do?
-------------------------------------
With the dependency injection pattern, objects lose the responsibility of assembling
the dependencies. The ``Dependency Injector`` absorbs that responsibility.
With the dependency injection pattern objects loose the responsibility of assembling the
dependencies. The ``Dependency Injector`` absorbs that responsibility.
``Dependency Injector`` helps to assemble and inject the dependencies.
``Dependency Injector`` helps to assemble the objects.
It provides a container and providers that help you with the objects assembly.
When you need an object you place a ``Provide`` marker as a default value of a
function argument. When you call this function, framework assembles and injects
the dependency.
It provides a container and providers that help you with the objects assembly. When you
need an object you get it from the container. The rest of the assembly work is done by the
framework:
.. code-block:: python
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer):
@ -172,7 +144,7 @@ the dependency.
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout,
timeout=config.timeout.as_int(),
)
service = providers.Factory(
@ -181,95 +153,96 @@ the dependency.
)
@inject
def main(service: Service = Provide[Container.service]) -> None:
...
if __name__ == "__main__":
if __name__ == '__main__':
container = Container()
container.config.api_key.from_env("API_KEY", required=True)
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
container.wire(modules=[__name__])
container.config.api_key.from_env('API_KEY')
container.config.timeout.from_env('TIMEOUT')
main() # <-- dependency is injected automatically
service = container.service()
with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically
Retrieving of the ``Service`` instance now is done like this::
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically.
service = container.service()
When you do testing, you call the ``container.api_client.override()`` method to replace the real API
client with a mock. When you call ``main()``, the mock is injected.
Objects assembling is consolidated in the container. When you need to make a change you do it in
one place.
When doing a testing you call the ``container.api_client.override()`` to replace the real API
client with a mock:
.. code-block:: python
from unittest import mock
with container.api_client.override(mock.Mock()):
service = container.service()
You can override any provider with another provider.
It also helps you in a re-configuring project for different environments: replace an API client
It also helps you in configuring project for the different environments: replace an API client
with a stub on the dev or stage.
Objects assembling is consolidated in a container. Dependency injections are defined explicitly.
This makes it easier to understand and change how an application works.
Testing, Monkey-patching and dependency injection
-------------------------------------------------
The testability benefit is opposed to monkey-patching.
The testability benefit is opposed to a monkey-patching.
In Python, you can monkey-patch anything, anytime. The problem with monkey-patching is
that it's too fragile. The cause of it is that when you monkey-patch you do something that
wasn't intended to be done. You monkey-patch the implementation details. When implementation
changes the monkey-patching is broken.
In Python you can monkey-patch
anything, anytime. The problem with a monkey-patching is that it's too fragile. The reason is that
when you monkey-patch you do something that wasn't intended to be done. You monkey-patch the
implementation details. When implementation changes the monkey-patching is broken.
With dependency injection, you patch the interface, not an implementation. This is a way more
With a dependency injection you patch the interface, not an implementation. This is a way more
stable approach.
Also, monkey-patching is way too dirty to be used outside of the testing code for
re-configuring the project for the different environments.
Also monkey-patching is a way too dirty to be used outside of the testing code for
reconfiguring the project for the different environments.
Conclusion
----------
Dependency injection provides you with three advantages:
Dependency injection brings you 3 advantages:
- **Flexibility**. The components are loosely coupled. You can easily extend or change the
functionality of a system by combining the components in a different way. You even can do it on
- **Flexibility**. The components are loosely coupled. You can easily extend or change a
functionality of the system by combining the components different way. You even can do it on
the fly.
- **Testability**. Testing is easier because you can easily inject mocks instead of real objects
- **Testability**. Testing is easy because you can easily inject mocks instead of real objects
that use API or database, etc.
- **Clearness and maintainability**. Dependency injection helps you reveal the dependencies.
Implicit becomes explicit. And "Explicit is better than implicit" (PEP 20 - The Zen of Python).
You have all the components and dependencies defined explicitly in a container. This
provides an overview and control of the application structure. It is easier to understand and
You have all the components and dependencies defined explicitly in the container. This
provides an overview and control on the application structure. It is easy to understand and
change it.
Is it worth applying dependency injection in Python?
Is it worth to use a dependency injection in Python?
It depends on what you build. The advantages above are not too important if you use Python as a
scripting language. The picture is different when you use Python to create an application. The
larger the application the more significant the benefits.
larger the application the more significant is the benefit.
Is it worth using a framework for applying dependency injection?
Is it worth to use a framework for the dependency injection?
The complexity of the dependency injection pattern implementation in Python is
lower than in other languages but it's still in place. It doesn't mean you have to use a
lower than in the other languages but it's still in place. It doesn't mean you have to use a
framework but using a framework is beneficial because the framework is:
- Already implemented
- Tested on all platforms and versions of Python
- Documented
- Supported
- Other engineers are familiar with it
- Known to the other engineers
An advice at last:
Few advices at last:
- **Give it a try**. Dependency injection is counter-intuitive. Our nature is that
when we need something the first thought that comes to our mind is to go and get it. Dependency
injection is just like "Wait, I need to state a need instead of getting something right away".
injection is just like "Wait, I need to state a need instead of getting something right now".
It's like a little investment that will pay-off later. The advice is to just give it a try for
two weeks. This time will be enough for getting your own impression. If you don't like it you
won't lose too much.
- **Common sense first**. Use common sense when applying dependency injection. It is a good
principle, but not a silver bullet. If you do it too much you will reveal too many of the
- **Common sense first**. Use a common sense when apply dependency injection. It is a good
principle, but not a silver bullet. If you do it too much you will reveal too much of the
implementation details. Experience comes with practice and time.
What's next?
@ -281,15 +254,6 @@ Choose one of the following as a next step:
- :ref:`application-single-container`
- :ref:`application-multiple-containers`
- :ref:`decoupled-packages`
- :ref:`boto3-example`
- :ref:`django-example`
- :ref:`flask-example`
- :ref:`flask-blueprints-example`
- :ref:`aiohttp-example`
- :ref:`sanic-example`
- :ref:`fastapi-example`
- :ref:`fastapi-redis-example`
- :ref:`fastapi-sqlalchemy-example`
- Pass the tutorials:
- :ref:`flask-tutorial`
- :ref:`aiohttp-tutorial`
@ -297,19 +261,17 @@ Choose one of the following as a next step:
- :ref:`cli-tutorial`
- Know more about the ``Dependency Injector`` :ref:`key-features`
- Know more about the :ref:`providers`
- Know more about the :ref:`wiring`
- Go to the :ref:`contents`
Useful links
------------
A few useful links related to a dependency injection design pattern for further reading:
There are some useful links related to dependency injection design pattern
that could be used for further reading:
+ https://en.wikipedia.org/wiki/Dependency_injection
+ https://martinfowler.com/articles/injection.html
+ https://github.com/ets-labs/python-dependency-injector
+ https://pypi.org/project/dependency-injector/
.. include:: ../sponsor.rst
.. disqus::

View File

@ -7,8 +7,8 @@ Introduction
overview of the dependency injection, inversion of
control and Dependency Injector framework.
The current section of the documentation provides an overview of the
dependency injection, inversion of control, and the ``Dependency Injector`` framework.
Current section of the documentation provides an overview of the
dependency injection, inversion of control and the ``Dependency Injector`` framework.
.. toctree::
:maxdepth: 2

View File

@ -1,42 +1,41 @@
Installation
============
``Dependency Injector`` is available on `PyPI <https://pypi.org/project/dependency-injector/>`_.
To install the latest version you can use ``pip``:
*Dependency Injector* framework is distributed by PyPi_.
Latest stable version (and all previous versions) of *Dependency Injector*
framework can be installed from PyPi_:
.. code-block:: bash
pip install dependency-injector
Some modules of the ``Dependency Injector`` are implemented as C extensions.
``Dependency Injector`` is distributed as a pre-compiled wheels. Wheels are
available for all supported Python versions on Linux, Windows, and MacOS.
Linux distribution uses `manylinux <https://github.com/pypa/manylinux>`_.
.. note::
Some components of *Dependency Injector* are implemented as C extension types.
*Dependency Injector* is distributed as an archive with a source code, so
C compiler and Python header files are required for the installation.
If there is no appropriate wheel for your environment (Python version and OS)
installer will compile the package from sources on your machine. You'll need
a C compiler and Python header files.
Sources can be cloned from GitHub_:
To verify the installed version:
.. code-block:: bash
git clone https://github.com/ets-labs/python-dependency-injector.git
Also all *Dependency Injector* releases can be downloaded from
`GitHub releases page`_.
Verification of currently installed version could be done using
:py:obj:`dependency_injector.VERSION` constant:
.. code-block:: bash
>>> import dependency_injector
>>> dependency_injector.__version__
'4.39.0'
'3.43.0'
.. note::
When adding ``Dependency Injector`` to ``pyproject.toml`` or ``requirements.txt``
don't forget to pin the version to the current major:
.. _PyPi: https://pypi.org/project/dependency-injector/
.. _GitHub: https://github.com/ets-labs/python-dependency-injector
.. _GitHub releases page: https://github.com/ets-labs/python-dependency-injector/releases
.. code-block:: bash
dependency-injector>=4.0,<5.0
*The next major version can be incompatible.*
All releases are available on the `PyPI release history page <https://pypi.org/project/dependency-injector/#history>`_.
Each release has an appropriate tag. The tags are available on the
`GitHub releases page <https://github.com/ets-labs/python-dependency-injector/releases>`_.
.. disqus::

View File

@ -11,33 +11,28 @@ Key features
Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
that help assemble your objects. See :ref:`providers`.
``List``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help assembling your
objects. See :ref:`providers`.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
and configuring dev/stage environment to replace API clients with stubs etc. See
and configuring dev / stage environment to replace API clients with stubs etc. See
:ref:`provider-overriding`.
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings,
environment variables, and dictionaries. See :ref:`configuration-provider`.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
See :ref:`resource-provider`.
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables
and dictionaries. See :ref:`configuration-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
The framework stands on two principles:
.. code-block:: text
- **Explicit is better than implicit (PEP20)**.
- **Do not do any magic to your code**.
Explicit is better than implicit
How is that different from the other frameworks?
You need to specify how to assemble and where to inject the dependencies explicitly.
- **No autowiring.** The framework does NOT do any autowiring / autoresolving of the dependencies. You need to specify everything explicitly. Because *"Explicit is better than implicit" (PEP20)*.
- **Does not pollute your code.** Your application does NOT know and does NOT depend on the framework. No ``@inject`` decorators, annotations, patching or any other magic tricks.
The power of the framework is in its simplicity.
``Dependency Injector`` is a simple tool for the powerful concept.
The power of the framework is in a simplicity. ``Dependency Injector`` is a simple tool for the powerful concept.
.. disqus::

View File

@ -7,908 +7,16 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
4.48.1
------
* Improve performance of ``dependency_injector._cwiring.DependencyResolver``
* Add ``typing-extensions`` as a dependency for older Python versions (<3.11)
* Produce warning on ``@inject``s without ``Provide[...]`` marks
* Add support for `resource_type` in ``Lifespan``s
4.48.0
------
- Improve performance of wiring (`#897 <https://github.com/ets-labs/python-dependency-injector/pull/897>`_)
- Add Context Manager support to Resource provider (`#899 <https://github.com/ets-labs/python-dependency-injector/pull/899>`_)
- Add support for async generator injections (`#900 <https://github.com/ets-labs/python-dependency-injector/pull/900>`_)
- Fix unintended dependency on ``typing_extensions`` (`#902 <https://github.com/ets-labs/python-dependency-injector/pull/902>`_)
- Add support for Fast Depends (`#898 <https://github.com/ets-labs/python-dependency-injector/pull/898>`_)
- Add ``resource_type`` parameter to init and shutdown resources using specialized providers (`#858 <https://github.com/ets-labs/python-dependency-injector/pull/858>`_)
4.47.1
------
- Fix typing for wiring marker (`#892 <https://github.com/ets-labs/python-dependency-injector/pull/896>`_)
- Strip debug symbols in wheels
4.47.0
------
- Add support for ``Annotated`` type for module and class attribute injection in wiring,
with updated documentation and examples.
See discussion:
https://github.com/ets-labs/python-dependency-injector/pull/721#issuecomment-2025263718
- Fix ``root`` property shadowing in ``ConfigurationOption`` (`#875 <https://github.com/ets-labs/python-dependency-injector/pull/875>`_)
- Fix incorrect monkeypatching during ``wire()`` that could violate MRO in some classes (`#886 <https://github.com/ets-labs/python-dependency-injector/pull/886>`_)
- ABI3 wheels are now published for CPython.
- Drop support of Python 3.7.
4.46.0
------
- Add option to disable env var interpolation in configs (`#861 <https://github.com/ets-labs/python-dependency-injector/pull/861>`_)
- Fix ``Closing`` dependency resolution (`#852 <https://github.com/ets-labs/python-dependency-injector/pull/852>`_)
- Add support for ``inspect.iscoroutinefunction()`` in ``Coroutine`` provider (`#830 <https://github.com/ets-labs/python-dependency-injector/pull/830>`_)
- Fix broken wiring of sync inject-decorated methods (`#673 <https://github.com/ets-labs/python-dependency-injector/pull/673>`_)
- Add support for ``typing.Annotated`` (`#721 <https://github.com/ets-labs/python-dependency-injector/pull/721>`_, `#853 <https://github.com/ets-labs/python-dependency-injector/pull/853>`_)
- Documentation updates for movie-lister example (`#747 <https://github.com/ets-labs/python-dependency-injector/pull/747>`_)
- Fix type propagation in ``Provider.provider`` (`#744 <https://github.com/ets-labs/python-dependency-injector/pull/744>`_)
Many thanks for the contributions to:
- `ZipFile <https://github.com/ZipFile>`_
- `Yegor Statkevich <https://github.com/jazzthief>`_
- `Federico Tomasi <https://github.com/federinik>`_
- `Martin Lafrance <https://github.com/martlaf>`_
- `Philip Bjorge <https://github.com/philipbjorge>`_
- `Ilya Kazakov <https://github.com/mrKazzila>`_
4.45.0
--------
- Add Starlette lifespan handler implementation (`#683 <https://github.com/ets-labs/python-dependency-injector/pull/683>`_).
- Raise exception in ``ThreadLocalSingleton`` instead of hiding it in finally (`#845 <https://github.com/ets-labs/python-dependency-injector/pull/845>`_).
- Improve debuggability of ``deepcopy`` errors (`#839 <https://github.com/ets-labs/python-dependency-injector/pull/839>`_).
- Update examples (`#838 <https://github.com/ets-labs/python-dependency-injector/pull/838>`_).
- Upgrade testing dependencies (`#837 <https://github.com/ets-labs/python-dependency-injector/pull/837>`_).
- Add minor fixes to the documentation (`#709 <https://github.com/ets-labs/python-dependency-injector/pull/709>`_).
- Remove ``six`` from the dependencies (`3ba4704 <https://github.com/ets-labs/python-dependency-injector/commit/3ba4704bc1cb00310749fd2eda0c8221167c313c>`_).
Many thanks for the contributions to:
- `ZipFile <https://github.com/ZipFile>`_
- `František Trebuňa <https://github.com/gortibaldik>`_
- `JC (Jonathan Chen) <https://github.com/dijonkitchen>`_
4.44.0
--------
- Implement support for Pydantic 2. PR: `#832 <https://github.com/ets-labs/python-dependency-injector/pull/832>`_.
- Implement `PEP-517 <https://peps.python.org/pep-0517/>`_, `PEP-518 <https://peps.python.org/pep-0518/>`_, and
`PEP-621 <https://peps.python.org/pep-0621/>`_. PR: `#829 <https://github.com/ets-labs/python-dependency-injector/pull/829>`_.
Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
4.43.0
--------
- Add support for Python 3.13.
- Migrate to Cython 3 (version 3.0.11). Many thanks to `ZipFile <https://github.com/ZipFile>`_ for
this contribution `#813 <https://github.com/ets-labs/python-dependency-injector/pull/813>`_.
4.42.0
--------
- Promote release ``4.42.0b1`` to a production release.
- Fix the Disqus comment widget.
4.42.0b1
--------
- Add support of Python 3.12.
- Drop support of Python 2.7, 3.5, and 3.6.
- Regenerate C sources using Cython 0.29.37.
- Update ``cibuildwheel`` to version ``2.20.0``.
4.41.0
------
- Add support of Python 3.11.
- Allow Closing to detect dependent resources `#633 <https://github.com/ets-labs/python-dependency-injector/issues/633>`_,
`#636 <https://github.com/ets-labs/python-dependency-injector/pull/636>`_. Thanks `Jamie Stumme @StummeJ <https://github.com/StummeJ>`_
for the contribution.
- Update CI/CD to use Ubuntu 22.04.
- Update CI/CD to ``actions/checkout@v3``, ``actions/setup-python@v4``, ``actions/upload-artifact@v3``, ``pypa/cibuildwheel@v2.11.3``,
and ``actions/download-artifact@v3``.
- Fix install crash on non-utf8 systems `#644 <https://github.com/ets-labs/python-dependency-injector/pull/644>`_.
- Fix a bug in Windows build with default charset `#635 <https://github.com/ets-labs/python-dependency-injector/pull/635>`_.
- Update FastAPI Redis example to use ``aioredis`` version 2 `#613 <https://github.com/ets-labs/python-dependency-injector/pull/613>`_.
- Update documentation on creating custom providers `#598 <https://github.com/ets-labs/python-dependency-injector/pull/598>`_.
- Regenerate C sources using Cython 0.29.32.
- Fix builds badge.
4.40.0
------
- Add ``Configuration.from_json()`` method to load configuration from a json file.
- Fix bug with wiring not working properly with functions double wrapped by ``@functools.wraps`` decorator.
See issue: `#454 <https://github.com/ets-labs/python-dependency-injector/issues/454>`_.
Many thanks to: `@platipo <https://github.com/platipo>`_, `@MatthieuMoreau0 <https://github.com/MatthieuMoreau0>`_,
`@fabiocerqueira <https://github.com/fabiocerqueira>`_, `@Jitesh-Khuttan <https://github.com/Jitesh-Khuttan>`_.
- Refactor wiring module to store all patched callable data in the ``PatchedRegistry``.
- Improve wording on the "Dependency injection and inversion of control in Python" docs page.
- Add documentation on the ``@inject`` decorator.
- Update typing in the main example and cohesion/coupling correlation definition in
"Dependency injection and inversion of control in Python".
Thanks to `@illia-v (Illia Volochii) <https://github.com/illia-v>`_ for the
PR (`#580 <https://github.com/ets-labs/python-dependency-injector/pull/580>`_).
- Update copyright year.
- Enable skipped test ``test_schema_with_boto3_session()``.
- Update pytest configuration.
- Regenerate C sources using Cython 0.29.30.
4.39.1
------
- Fix bug `#574 <https://github.com/ets-labs/python-dependency-injector/issues/574>`_:
"``@inject`` breaks ``inspect.iscoroutinefunction``". Thanks to
`@burritoatspoton (Rafał Burczyński) <https://github.com/burritoatspoton>`_ for reporting the issue.
4.39.0
------
- Optimize injections and wiring from x1.5 to x7 times depending on the use case.
- Fix bug `#569 <https://github.com/ets-labs/python-dependency-injector/issues/569>`_:
"numpy.typing.NDArray breaks wiring". Thanks to
`@VKFisher (Vlad Fisher) <https://github.com/VKFisher>`_ for reporting the issue and providing a fix.
4.38.0
------
- Add new provider ``Aggregate``. It is a generalized version of ``FactoryAggregate`` that
can contain providers of any type, not only ``Factory``. See issue
`#530 <https://github.com/ets-labs/python-dependency-injector/issues/530>`_. Thanks to
`@zerlok (Danil Troshnev) <https://github.com/zerlok>`_ for suggesting the feature.
- Add argument ``as_`` to the ``config.from_env()`` method for the explicit type casting
of an environment variable value, e.g.: ``config.timeout.from_env("TIMEOUT", as_=int)``.
See issue `#533 <https://github.com/ets-labs/python-dependency-injector/issues/533>`_. Thanks to
`@gtors (Andrey Torsunov) <https://github.com/gtors>`_ for suggesting the feature.
- Add ``.providers`` attribute to the ``FactoryAggregate`` provider. It is an alias for
``FactoryAggregate.factories`` attribute.
- Add ``.set_providers()`` method to the ``FactoryAggregate`` provider. It is an alias for
``FactoryAggregate.set_factories()`` method.
- Add string imports for ``Factory``, ``Singleton``, ``Callable``, ``Resource``, and ``Coroutine``
providers, e.g. ``Factory("module.Class")``.
See issue `#531 <https://github.com/ets-labs/python-dependency-injector/issues/531>`_.
Thanks to `@al-stefanitsky-mozdor <https://github.com/al-stefanitsky-mozdor>`_ for suggesting the feature.
- Fix ``Dependency`` provider to don't raise "Dependency is not defined" error when the ``default``
is a falsy value of proper type.
See issue `#550 <https://github.com/ets-labs/python-dependency-injector/issues/550>`_. Thanks to
`@approxit <https://github.com/approxit>`_ for reporting the issue.
- Refactor ``FactoryAggregate`` provider internals.
- Update logo on Github and in docs to support dark themes and remove some imperfections.
4.37.0
------
- Add support of Python 3.10.
- Improve wiring with adding importing modules and packages from a string
``container.wire(modules=["yourapp.module1"])``.
- Add container wiring configuration ``wiring_config = containers.WiringConfiguration()``.
- Add support of ``with`` statement for ``container.override_providers()`` method.
- Add ``Configuration(yaml_files=[...])`` argument.
- Add ``Configuration(ini_files=[...])`` argument.
- Add ``Configuration(pydantic_settings=[...])`` argument.
- Drop support of Python 3.4. There are no immediate breaking changes, but Dependency Injector
will no longer be tested on Python 3.4 and any bugs will not be fixed.
- Announce the date of dropping Python 3.5 support (Jan 1st 2022).
- Fix ``Dependency.is_defined`` attribute to always return boolean value.
- Fix ``envs_required=False`` behavior in ``Configuration.from_*()`` methods
to give a priority to the explicitly provided value.
- Update documentation and fix typos.
- Regenerate C sources using Cython 0.29.24.
- Migrate tests to ``pytest``.
4.36.2
------
- Update docs.
4.36.1
------
- Fix a wiring bug with improper resolving of ``Provide[some_provider.provider]``.
- Fix a typo in ``Factory`` provider docs ``service.add_attributes(clent=client)``
`#499 <https://github.com/ets-labs/python-dependency-injector/issues/499>`_.
Thanks to `@rajanjha786 <https://github.com/rajanjha786>`_ for the contribution.
- Fix a typo in ``boto3`` example
`#511 <https://github.com/ets-labs/python-dependency-injector/issues/511>`_.
Thanks to `@whysage <https://github.com/whysage>`_ for the contribution.
4.36.0
------
- Add support of non-string keys for ``FactoryAggregate`` provider.
- Improve ``FactoryAggregate`` typing stub.
- Improve resource subclasses typing and make shutdown definition optional
`PR #492 <https://github.com/ets-labs/python-dependency-injector/pull/492>`_.
Thanks to `@EdwardBlair <https://github.com/EdwardBlair>`_ for suggesting the improvement.
- Fix type annotations for ``.provides``.
Thanks to `Thiago Hiromi @thiromi <https://github.com/thiromi>`_ for the fix
`PR #491 <https://github.com/ets-labs/python-dependency-injector/pull/491>`_.
- Fix environment variables interpolation examples in configuration provider docs ``{$ENV} -> ${ENV}``.
Thanks to `Felipe Rubio @krouw <https://github.com/krouw>`_ for reporting the issue and
fixing yaml example `PR #494 <https://github.com/ets-labs/python-dependency-injector/pull/494>`_.
- Fix ``@containers.copy()`` decorator to respect dependencies on parent providers.
See issue `#477 <https://github.com/ets-labs/python-dependency-injector/issues/477>`_.
Thanks to `Andrey Torsunov @gtors <https://github.com/gtors>`_ for reporting the issue.
- Fix typing stub for ``container.override_providers()`` to accept other types besides ``Provider``.
- Fix runtime issue with generic typing in resource initializer classes ``resources.Resource``
and ``resources.AsyncResource``.
See issue `#488 <https://github.com/ets-labs/python-dependency-injector/issues/488>`_.
Thanks to `@EdwardBlair <https://github.com/EdwardBlair>`_ for reporting the issue.
4.35.3
------
- *This release was removed from PyPI. It was inconsistently published because project has
reached a PyPI size limit. Changes from this release are published on PyPI in next version.*
4.35.2
------
- Update wiring to support modules provided as packages.
See issue `#481 <https://github.com/ets-labs/python-dependency-injector/issues/481>`_.
Thanks to `@Sadbot <https://github.com/Sadbot>`_ for demonstrating the issue.
4.35.1
------
- Fix a container issue with supporting custom string types.
See issue `#479 <https://github.com/ets-labs/python-dependency-injector/issues/479>`_.
Thanks to `@ilsurih <https://github.com/ilsurih>`_ for reporting the issue.
4.35.0
------
- Add support of six 1.16.0.
4.34.2
------
- Fix a bug with reverse shutdown order in ``container.shutdown_resources()``.
See issue `#432 <https://github.com/ets-labs/python-dependency-injector/issues/432>`_.
Thanks to `Saulius Beinorius <https://github.com/saulbein>`_ for bringing up the issue.
4.34.1
------
- Update ``container.shutdown_resources()`` to respect dependencies order while shutdown.
See issue `#432 <https://github.com/ets-labs/python-dependency-injector/issues/432>`_.
Thanks to `Saulius Beinorius <https://github.com/saulbein>`_ for bringing up the issue.
4.34.0
------
- Add option ``envs_required`` for configuration provider ``.from_yaml()`` and ``.from_ini()``
methods. With ``envs_required=True`` methods ``.from_yaml()`` and ``.from_ini()`` raise
an exception when encounter an undefined environment variable in the configuration file.
By default this option is set to false for preserving previous behavior ``envs_required=False``.
- Add raising of an exception in configuration provider strict mode when provider encounters
an undefined environment variable in the configuration file.
- Update configuration provider environment variables interpolation to replace
undefined environment variables with an empty value.
- Update configuration provider to perform environment variables interpolation before passing
configuration file content to the parser.
4.33.0
------
- Add support of default value for environment variable in INI and YAML
configuration files with ``${ENV_NAME:default}`` format.
See issue `#459 <https://github.com/ets-labs/python-dependency-injector/issues/459>`_.
Thanks to `Maksym Shemet @hbmshemet <https://github.com/hbmshemet>`_ for suggesting the feature.
- Add method ``Configuration.from_value()``.
See issue `#462 <https://github.com/ets-labs/python-dependency-injector/issues/462>`_.
Thanks to Mr. `Slack Clone <https://disqus.com/by/slackclone/>`_ for bringing it up
in the comments for configuration provider docs.
4.32.3
------
- This fix a typo in ``di_in_python.rst`` doc.
Thanks to `@loingo95 <https://github.com/loingo95>`_ for the fix.
4.32.2
------
- Improve wiring fault tolerance.
See issue `#441 <https://github.com/ets-labs/python-dependency-injector/issues/441>`_.
Thanks to `@ssheng <https://github.com/ssheng>`_ for reporting the issue.
4.32.1
------
- Fix a bug with ``List`` provider not working in async mode.
See issue: `#450 <https://github.com/ets-labs/python-dependency-injector/issues/450>`_.
Thanks to `@mxab <https://github.com/mxab>`_ for reporting the issue.
- Add async mode tests for ``List`` and ``Dict`` provider.
4.32.0
------
- Add ``ContextLocalSingleton`` provider.
See PR: `#443 <https://github.com/ets-labs/python-dependency-injector/pull/442>`_.
Thanks to `@sonthonaxrk <https://github.com/sonthonaxrk>`_ for the contribution.
- Regenerate C sources using Cython 0.29.22.
4.31.2
------
- Fix an issue with ``Dict`` provider non-string keys.
See issue: `#435 <https://github.com/ets-labs/python-dependency-injector/issues/435>`_.
Thanks to `@daniel55411 <https://github.com/daniel55411>`_ for reporting the issue.
- Fix Flask scoped contexts example.
See issue: `#440 <https://github.com/ets-labs/python-dependency-injector/pull/440>`_.
Thanks to `@sonthonaxrk <https://github.com/sonthonaxrk>`_ for the contribution.
4.31.1
------
- Fix ``ThreadSafeSingleton`` synchronization issue.
See issue: `#433 <https://github.com/ets-labs/python-dependency-injector/issues/433>`_.
Thanks to `@garlandhu <https://github.com/garlandhu>`_ for reporting the issue.
4.31.0
------
- Implement providers' lazy initialization.
- Improve providers' copying.
- Improve typing in wiring module.
- Fix wiring module loader uninstallation issue.
- Fix provided instance providers error handing in asynchronous mode.
- Fix overridden configuration option cache resetting.
See issue: `#428 <https://github.com/ets-labs/python-dependency-injector/issues/428>`_.
Thanks to `@dcendents <https://github.com/dcendents>`_ for reporting the issue.
4.30.0
------
- Remove restriction to wire a dynamic container.
4.29.2
------
- Fix wiring to not crash on missing signatures.
See issue: `#420 <https://github.com/ets-labs/python-dependency-injector/issues/420>`_.
Thanks to `@Balthus1989 <https://github.com/Balthus1989>`_ for reporting the issue.
4.29.1
------
- Fix recursive copying issue in ``Delegate`` provider.
See issue: `#245 <https://github.com/ets-labs/python-dependency-injector/issues/245>`_.
Thanks to `@GitterRemote <https://github.com/GitterRemote>`_ for reporting the issue.
- Add docs and example for ``Factory.add_attributes()`` method.
- Remove legacy css file.
- Remove ``unittest2`` test dependency.
4.29.0
------
- Implement context manager interface for resetting a singleton provider.
See issue: `#413 <https://github.com/ets-labs/python-dependency-injector/issues/413>`_.
Thanks to `@Arrowana <https://github.com/Arrowana>`_ for suggesting the improvement.
- Implement overriding interface to container provider.
See issue: `#415 <https://github.com/ets-labs/python-dependency-injector/issues/415>`_.
Thanks to `@wackazong <https://github.com/wackazong>`_ for bringing up the use case.
4.28.1
------
- Fix async mode mode exception handling issue in ``Dependency`` provider.
See issue: `#409 <https://github.com/ets-labs/python-dependency-injector/issues/409>`_.
Thanks to `@wackazong <https://github.com/wackazong>`_ for reporting the issue.
- Fix links to ``boto3`` example.
4.28.0
------
- Add wiring injections into modules and class attributes.
See issue: `#411 <https://github.com/ets-labs/python-dependency-injector/issues/411>`_.
Many thanks to `@brunopereira27 <https://github.com/brunopereira27>`_ for submitting
the use case.
4.27.0
------
- Introduce wiring inspect filter to filter out ``flask.request`` and other local proxy objects
from the inspection.
See issue: `#408 <https://github.com/ets-labs/python-dependency-injector/issues/408>`_.
Many thanks to `@bvanfleet <https://github.com/bvanfleet>`_ for reporting the issue and
help in finding the root cause.
- Add ``boto3`` example.
- Add tests for ``.as_float()`` modifier usage with wiring.
- Make refactoring of wiring module and tests.
See PR # `#406 <https://github.com/ets-labs/python-dependency-injector/issues/406>`_.
Thanks to `@withshubh <https://github.com/withshubh>`_ for the contribution:
- Remove unused imports in tests.
- Use literal syntax to create data structure in tests.
- Add integration with a static analysis tool `DeepSource <https://deepsource.io/>`_.
4.26.0
------
- Add wiring by string id.
- Improve error message for ``Dependency`` provider missing attribute.
4.25.1
------
- Amend docs and add another example for ``@containers.copy()`` decorator.
4.25.0
------
- Add ``application-multiple-containers-runtime-overriding`` example. This example demonstrates
how to build application from multiple containers and override one container config from
another one in the runtime.
See issue: `#207 <https://github.com/ets-labs/python-dependency-injector/issues/207>`_.
- Add attributes forwarding for the ``Dependency`` provider.
4.24.0
------
- Add docs on ``@containers.copy()`` decorator.
- Refactor ``@containers.copy()`` decorator.
- Refactor async mode support in containers module.
4.23.5
------
- Fix docs publishing.
4.23.4
------
- Fix a typo.
4.23.3
------
- Fix mistakenly processed awaitable objects in async mode. This bug has corrupted
``fastapi-redis`` example causing pool exhaustion.
Thanks to `@iliamir <https://github.com/iliamir>`_ and Valery Komarov for finding and
reporting the issue.
- Refactor async mode.
4.23.2
------
- Improve async mode exceptions handling.
- Fix double printing of exception when async resource initialization causes an error.
4.23.1
------
- Hotfix a bug with importing FastAPI ``Request``.
See issue: `#398 <https://github.com/ets-labs/python-dependency-injector/issues/398>`_.
Thanks to `@tapm <https://github.com/tapm>`_ for reporting the bug.
4.23.0
------
- Add support of aliases for ``Configuration`` provider.
See issue: `#394 <https://github.com/ets-labs/python-dependency-injector/issues/394>`_.
Thanks to `@gtors <https://github.com/gtors>`_ for suggesting the feature.
4.22.1
------
- Pin ``sphinx`` version to hotfix docs build.
- Fix a typo in docs.
4.22.0
------
- Add method ``container.check_dependencies()`` to check if all container dependencies
are defined.
See issue: `#383 <https://github.com/ets-labs/python-dependency-injector/issues/383>`_.
Thanks to `@shaunc <https://github.com/shaunc>`_ for suggesting the feature.
- Add container name to the representation of the ``Dependency`` provider.
- Add docs cross-links between ``Singleton`` provider and "Reset container singletons"
pages.
4.21.0
------
- Improve ``Dependency`` provider error message: when dependency is undefined,
error message contains its name.
4.20.2
------
- Move docs on container "self" injections to "Providers" section.
4.20.1
------
- Refactor containers module.
4.20.0
------
- Add container "self" injections.
See issue: `#364 <https://github.com/ets-labs/python-dependency-injector/issues/364>`_.
Thanks to `@shaunc <https://github.com/shaunc>`_ for suggesting the feature.
4.19.0
------
- Add ``singleton.full_reset()`` method to reset all underlying singleton providers.
- Fix ``container.reset_singleton()`` to reset all provider types, not only ``Singleton``.
- Improve ``container.traverse(types=[...])`` and ``provider.traverse(types=[...])`` typing stubs
to return ``types`` -typed iterator.
- Update docs on creating custom providers with a requirement to specify ``.related`` property.
4.18.0
------
- Add ``container.reset_singleton()`` method to reset container singletons.
- Refactor ``container.apply_container_providers_overridings()`` to use ``container.traverse()``.
This enables deep lazy initialization of ``Container`` providers.
- Add tests for ``Selector`` provider.
- Add tests for ``ProvidedInstance`` and ``MethodCaller`` providers.
- Update Makefile to make Python 3 tests to be a default test command: ``make test``.
4.17.0
------
- Add ``FastAPI`` + ``SQLAlchemy`` example.
Thanks to `@ShvetsovYura <https://github.com/ShvetsovYura>`_ for providing initial example:
`FastAPI_DI_SqlAlchemy <https://github.com/ShvetsovYura/FastAPI_DI_SqlAlchemy>`_.
4.16.0
------
- Add container base class ``containers.Container``. ``DynamicContainer``
and ``DeclarativeContainer`` become subclasses of the ``Container``.
See issue: `#386 <https://github.com/ets-labs/python-dependency-injector/issues/386>`_.
Thanks to `@ventaquil <https://github.com/ventaquil>`_ for reporting the issue.
4.15.0
------
- Add ``Configuration.from_pydantic()`` method to load configuration from a ``pydantic`` settings.
4.14.0
------
- Add container providers traversal.
- Fix an issue with ``container.init_resource()`` and ``container.shutdown_resource()`` ignoring
nested resources that are not present on the root level.
See issue: `#380 <https://github.com/ets-labs/python-dependency-injector/issues/380>`_.
Thanks to `@approxit <https://github.com/approxit>`_ for finding and reporting the issue.
- Add ``.provides`` attribute to ``Singleton`` and its subclasses.
It's a consistency change to make ``Singleton`` match ``Callable``
and ``Factory`` interfaces.
- Add ``.initializer`` attribute to ``Resource`` provider.
- Update string representation of ``Resource`` provider.
4.13.2
------
- Fix PyCharm typing warning "Expected type 'Optional[Iterable[ModuleType]]',
got 'List[module.py]' instead" in ``container.wire()`` method.
4.13.1
------
- Fix declarative container metaclass bug: parent container providers replaced child container providers.
See issue: `#367 <https://github.com/ets-labs/python-dependency-injector/issues/367>`_.
Many thanks to `Shaun Cutts <https://github.com/shaunc>`_ for finding and report the issue.
4.13.0
------
- Add ``default`` argument to the dependency provider: ``Dependency(..., default=...)``.
See issue: `#336 <https://github.com/ets-labs/python-dependency-injector/issues/336>`_.
Many thanks to `Shaun Cutts <https://github.com/shaunc>`_ for providing the use case.
4.12.0
------
- Add wiring import hook that auto-wires dynamically imported modules.
See issue: `#365 <https://github.com/ets-labs/python-dependency-injector/issues/365>`_.
Thanks to `@Balthus1989 <https://github.com/Balthus1989>`_ for providing a use case.
4.11.3
------
- Replace weakrefs with normal refs in ``ConfigurationOption`` to support
``Container().provider()`` use case. Test that it does not introduce a memory leak.
See issue: `#358#issuecomment-764482059 <https://github.com/ets-labs/python-dependency-injector/issues/358#issuecomment-764482059>`_.
Many thanks to `@Minitour <https://github.com/Minitour>`_ for reporting the issue.
4.11.2
------
- Fix a bug in ``providers.Container`` when it's declared not at class root level.
See issue `#379 <https://github.com/ets-labs/python-dependency-injector/issues/379>`_.
Many thanks to `@approxit <https://github.com/approxit>`_ for reporting the issue.
4.11.1
------
- Fix a bug in ``@containers.copy`` to improve replacing of subcontainer providers.
See issue `#378 <https://github.com/ets-labs/python-dependency-injector/issues/378>`_.
Many thanks to `Shaun Cutts <https://github.com/shaunc>`_ for reporting the issue.
4.11.0
------
- Add ``loader`` argument to the configuration provider ``Configuration.from_yaml(..., loader=...)``
to override the default YAML loader.
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for suggesting an improvement.
- Make security improvement: change default YAML loader to the custom ``yaml.SafeLoader`` with a support
of environment variables interpolation.
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for suggesting an improvement.
- Update configuration provider ``.from_*()`` methods to raise an exception in strict mode if
configuration file does not exist or configuration data is undefined.
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for suggesting an improvement.
- Add ``required`` argument to the configuration provider ``.from_*()`` methods to specify
mandatory configuration sources.
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for suggesting an improvement.
- Fix a bug with asynchronous injections: async providers do not work with async dependencies.
See issue: `#368 <https://github.com/ets-labs/python-dependency-injector/issues/368>`_.
Thanks `@kolypto <https://github.com/kolypto>`_ for the bug report.
- Refactor asynchronous injections.
- Add extra tests for asynchronous injections.
- Migrate CI to Github Actions.
4.10.3
------
- Fix a bug in the ``Configuration`` provider: strict mode didn't work when provider
is overridden by ``None``.
See issue: `#358#issuecomment-761607432 <https://github.com/ets-labs/python-dependency-injector/issues/358#issuecomment-761607432>`_.
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for reporting the issue.
4.10.2
------
- Fix a bug in ``Resource`` that cause failure when async resource depends on
another async resource.
See issue `#361 <https://github.com/ets-labs/python-dependency-injector/issues/361>`_.
Thanks `@kolypto <https://github.com/kolypto>`_ for the bug report.
4.10.1
------
- Fix a Python 3.9 specific bug in ``wiring`` module: introspection doesn't work for
builtin ``types.GenericAlias``. This resulted in wiring failure for modules
importing ``queue.Queue``.
See issue `#362 <https://github.com/ets-labs/python-dependency-injector/issues/362>`_.
Thanks `@ventaquil <https://github.com/ventaquil>`_ for the bug report.
- Switch Coveralls reporting Travis Job to run on Python 3.9.
4.10.0
------
- Add ``strict`` mode and ``required`` modifier for ``Configuration`` provider.
See issue `#341 <https://github.com/ets-labs/python-dependency-injector/issues/341>`_.
Thanks `ms-lolo <https://github.com/ms-lolo>`_ for the feature request.
4.9.1
-----
- Fix a bug in the ``Configuration`` provider to correctly handle undefined values.
See issue `#358 <https://github.com/ets-labs/python-dependency-injector/issues/358>`_.
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for reporting the issue.
4.9.0
-----
- Add ``.dependencies`` attribute to the ``DeclarativeContainer`` and ``DynamicContainer``.
It returns dictionary of container ``Dependency`` and ``DependenciesContainer`` providers.
See issue `#357 <https://github.com/ets-labs/python-dependency-injector/issues/357>`_.
Many thanks to `Shaun Cutts <https://github.com/shaunc>`_ for suggesting the feature.
4.8.3
-----
- Fix a bug in the ``Configuration`` provider to correctly handle overriding by ``None``.
See issue `#358 <https://github.com/ets-labs/python-dependency-injector/issues/358>`_.
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for reporting the issue.
4.8.2
-----
- Fix ``Container`` provider to apply context overridings on root container initialization.
See issue `#354 <https://github.com/ets-labs/python-dependency-injector/issues/354>`_.
Many thanks to `Shaun Cutts <https://github.com/shaunc>`_ for submitting the issue.
- Hotfix for version ``4.8.0``: fix side effect in ``Container`` provider overriding.
4.8.1
-----
- Fix declarative container multi-level inheritance issue.
See issue `#350 <https://github.com/ets-labs/python-dependency-injector/issues/350>`_.
Many thanks to `Shaun Cutts <https://github.com/shaunc>`_ for submitting the issue.
4.8.0
-----
- Add support of overriding ``Container`` provider.
See issue `#354 <https://github.com/ets-labs/python-dependency-injector/issues/354>`_.
Many thanks to `Shaun Cutts <https://github.com/shaunc>`_ for submitting the issue.
4.7.0
-----
- Add container injection support for wiring.
4.6.1
-----
- Add Disqus comments widget to the provider's async injections docs page.
4.6.0
-----
- Add support of async injections for providers.
- Add support of async injections for wiring.
- Add support of async initializers for ``Resource`` provider.
- Add ``FastAPI`` + ``Redis`` example.
- Add ARM wheel builds.
See issue `#342 <https://github.com/ets-labs/python-dependency-injector/issues/342>`_ for details.
- Fix a typo in `ext.flask` deprecation warning.
See PR `#345 <https://github.com/ets-labs/python-dependency-injector/pull/345>`_ for details.
Thanks to `Fotis Koutoupas <https://github.com/kootoopas>`_ for the fix.
- Update copyright year.
4.5.4
-----
- Fix manylinux wheels uploading issue.
See issue `#333 <https://github.com/ets-labs/python-dependency-injector/issues/333>`_ for details.
Thanks to `Richard Jones <https://github.com/RichardDRJ>`_ for reporting the issue.
4.5.3
-----
- Fix ``4.5.2`` degradation bug in wiring ``@inject`` with not working ``FastAPI.Depends`` directive.
See issue `#331 <https://github.com/ets-labs/python-dependency-injector/issues/331>`_ for details.
Thanks to `Juan Esteban Marín <https://github.com/juanmarin96>`_ for reporting the issue.
- Add ``FastAPI`` tests.
4.5.2
-----
- Fix a bug in wiring ``@inject`` with not properly working ``FastAPI.Depends`` directive.
See issue `#330 <https://github.com/ets-labs/python-dependency-injector/issues/330>`_ for details.
Thanks to `Lojka-oops <https://github.com/Lojka-oops>`_ for reporting the issue.
4.5.1
-----
- Fix flake8 issue in ``Commands and Handlers`` example.
4.5.0
-----
- Add support of non-string keys for ``Dict`` provider.
- Add simple ``FastAPI`` example.
- Add ``Commands and Handlers`` example from
issue `#327 <https://github.com/ets-labs/python-dependency-injector/issues/327>`_.
- Add extra typing test for provided instance of ``DependenciesContainer`` provider.
4.4.1
-----
- Improve ``FastAPI`` integration: handle ``Depends(Provide[...])``.
- Update ``FastAPI`` example.
- Remove a typo from the ``Flask`` tutorial.
4.4.0
-----
- Add ``@inject`` decorator. It helps to fix a number of wiring bugs and make wiring be more resilient.
- Refactor ``wiring`` module.
- Update documentation and examples to use ``@inject`` decorator.
- Add ``Flask`` blueprints example.
- Fix wiring bug when wiring doesn't work with the class-based decorators.
- Fix wiring bug when wiring doesn't work with the decorators that doesn't use ``functools.wraps(...)``.
- Fix wiring bug with ``@app.route(...)`` -style decorators (Flask, Sanic, FastAPI, etc.).
- Fix wiring bug when wiring doesn't work with Flask blueprints.
4.3.9
-----
- Add ``FastAPI`` example.
4.3.8
-----
- Add a hotfix to support wiring for ``FastAPI`` endpoints.
4.3.7
-----
- Fix race in ``ThreadSafeSingleton``. Many thanks to
`Dmitry Rassoshenko aka rda-dev <https://github.com/rda-dev>`_ for the pull request
(See PR `#322 <https://github.com/ets-labs/python-dependency-injector/pull/322>`_).
4.3.6
-----
- Fix changelog typo.
4.3.5
-----
- Fix a bug in ``wiring`` module that caused multiple imports of the modules
when ``.wire(packages=[...])`` is used
(See issue `#320 <https://github.com/ets-labs/python-dependency-injector/issues/320>`_). Thanks
to `Federico iskorini <https://github.com/iskorini>`_ for reporting the issue.
4.3.4
-----
- Fix a bug in ``Configuration`` provider that resulted in not working ``.reset_override()``
(See issue `#319 <https://github.com/ets-labs/python-dependency-injector/issues/319>`_). Thanks
to `Jun lust4life <https://github.com/lust4life>`_ for reporting the issue and suggesting a fix.
4.3.3
-----
- Fix a bug in ``wiring`` with improper patching of ``@classmethod`` and ``@staticmethod`` decorated methods
(See issue `#318 <https://github.com/ets-labs/python-dependency-injector/issues/318>`_).
4.3.2
-----
- Fix a bug in ``wiring`` with mistakenly initialized and shutdown resource with ``Closing``
marker on context argument providing.
4.3.1
-----
- Fix README.
4.3.0
-----
- Implement per-function execution scope for ``Resource`` provider in tandem
with ``wiring.Closing``.
4.2.0
-----
- Add support of Python 3.9.
- Update readme.
4.1.8
-----
- Update asyncio daemon, single- and multi-container examples to use ``Resource`` provider.
4.1.7
-----
- Add CI job to build and push documentation to S3 bucket.
4.1.6
-----
- Fix wiring of multiple containers
(see issue `#313 <https://github.com/ets-labs/python-dependency-injector/issues/313>`_).
Thanks to `iskorini <https://github.com/iskorini>`_ for reporting the issue.
- Fix wiring for ``@classmethod``.
4.1.5
-----
- Fix Travis CI windows and MacOS builds.
4.1.4
-----
- Fix version of ``cibuildwheel==1.63``.
- Update Travis CI webhooks to fix builds triggering.
4.1.3
-----
- Migrate from ``travis-ci.org`` to ``travis-ci.com`` to fix build issues.
- Add explicit installation of ``certifi`` for Windows build to resolve build problems.
4.1.2
-----
- Bump version of ``cibuildwheel>=1.5.1`` to resolve Windows build problem.
4.1.1
-----
- Fix a few typos in ``Resource`` provider docs.
4.1.0
-----
- Add ``Resource`` provider.
- Add ``Dict`` provider.
- "Un-deprecate" ``@containers.override()`` and ``@containers.copy()`` decorators (
see `Issue 301 <https://github.com/ets-labs/python-dependency-injector/issues/301>`_
for more information).
- Add favicon.
- Remove redirects that occur while getting badge images to optimize docs load speed.
- Update license year.
- Update short description on PyPI.
4.0.6
-----
- Fix wiring for top-level package ``__init__.py``.
4.0.5
-----
- Move ``.provided`` attribute to ``providers.Provider``.
- Update all links in documentation and examples to use ``https://`` instead of ``http``.
4.0.4
-----
- Fix typing stubs for ``container.override()`` method.
4.0.3
-----
- Deprecate ``@containers.override()`` and ``@containers.copy()`` decorators.
- Update changelog of version ``4.0.0`` so it lists all deprecated features.
4.0.2
-----
- Fix typing stubs for ``@container.override()`` and ``@containers.copy()`` decorators (
see `PR 302 <https://github.com/ets-labs/python-dependency-injector/pull/302>`_). Thanks
to `JarnoRFB <https://github.com/JarnoRFB>`_ for reporting the issue.
4.0.1
-----
- Extend ``Configuration.from_ini()`` and ``Configuration.from_yaml()`` typing stubs to
accept ``pathlib.Path``. The methods were already compatible with ``pathlib.Path``
and just did not accept it in their signatures (see
`PR 300 <https://github.com/ets-labs/python-dependency-injector/pull/300>`_). Fix
was provided by `JarnoRFB <https://github.com/JarnoRFB>`_. Many thanks to you again,
JarnoRFB.
4.0.0
-----
New features:
- Add ``wiring`` feature.
Deprecations:
- Deprecate ``ext.aiohttp`` module in favor of ``wiring`` feature.
- Deprecate ``ext.flask`` module in favor of ``wiring`` feature.
- Deprecate ``.delegate()`` provider method in favor of ``.provider`` attribute.
Removals:
- Deprecate ``ext.aiohttp`` module.
- Deprecate ``ext.flask`` module.
- Remove deprecated ``types`` module.
Tutorials:
- Update ``flask`` tutorial.
- Update ``aiohttp`` tutorial.
- Update ``asyncio`` daemon tutorial.
- Update CLI application tutorial.
Examples:
- Add ``django`` example.
- Deprecate ``.delegate()`` provider method in favor of ``.provider`` attribute.
- Add ``sanic`` example.
- Update ``aiohttp`` example.
- Update ``flask`` example.
- Update ``asyncio`` daemon example.
- Update ``movie-lister`` example.
- Update CLI application example.
Misc:
- Regenerate C sources using Cython 0.29.21.
- Improve documentation and README (typos removal, rewording, etc).
3.44.0
------
@ -2030,4 +1138,4 @@ Previous versions
.. disqus::
.. _Semantic versioning: https://semver.org/
.. _Semantic versioning: http://semver.org/

View File

@ -1,72 +0,0 @@
.. _aggregate-provider:
Aggregate provider
==================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
Aggregate,Polymorphism,Environment Variable,Flexibility
:description: Aggregate provider aggregates other providers.
This page demonstrates how to implement the polymorphism and increase the
flexibility of your application using the Aggregate provider.
:py:class:`Aggregate` provider aggregates a group of other providers.
.. currentmodule:: dependency_injector.providers
.. literalinclude:: ../../examples/providers/aggregate.py
:language: python
:lines: 3-
:emphasize-lines: 24-27
Each provider in the ``Aggregate`` is associated with a key. You can call aggregated providers by providing
their key as a first argument. All positional and keyword arguments following the key will be forwarded to
the called provider:
.. code-block:: python
yaml_reader = container.config_readers("yaml", "./config.yml", foo=...)
You can also retrieve an aggregated provider by providing its key as an attribute name:
.. code-block:: python
yaml_reader = container.config_readers.yaml("./config.yml", foo=...)
To retrieve a dictionary of aggregated providers, use ``.providers`` attribute:
.. code-block:: python
container.config_readers.providers == {
"yaml": <YAML provider>,
"json": <JSON provider>,
}
.. note::
You can not override the ``Aggregate`` provider.
.. note::
When you inject the ``Aggregate`` provider, it is passed "as is".
To use non-string keys or string keys with ``.`` and ``-``, provide a dictionary as a positional argument:
.. code-block:: python
aggregate = providers.Aggregate({
SomeClass: providers.Factory(...),
"key.with.periods": providers.Factory(...),
"key-with-dashes": providers.Factory(...),
})
.. seealso::
:ref:`selector-provider` to make injections based on a configuration value, environment variable, or a result of a callable.
``Aggregate`` provider is different from the :ref:`selector-provider`. ``Aggregate`` provider doesn't select which provider
to inject and doesn't have a selector. It is a group of providers and is always injected "as is". The rest of the interface
of both providers is similar.
.. note::
``Aggregate`` provider is a successor of :ref:`factory-aggregate-provider` provider. ``Aggregate`` provider doesn't have
a restriction on the provider type, while ``FactoryAggregate`` aggregates only ``Factory`` providers.
.. disqus::

View File

@ -1,110 +0,0 @@
.. _async-injections:
Asynchronous injections
=======================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Providers,Async,Injections,Asynchronous,Await,
Asyncio
:description: Dependency Injector providers support asynchronous injections. This page
demonstrates how make asynchronous dependency injections in Python.
Providers support asynchronous injections.
.. literalinclude:: ../../examples/providers/async.py
:language: python
:emphasize-lines: 26-29
:lines: 3-
If provider has any awaitable injections it switches into async mode. In async mode provider always returns awaitable.
This causes a cascade effect:
.. code-block:: bash
provider1() <── Async mode enabled <──┐
│ │
├──> provider2() │
│ │
├──> provider3() <── Async mode enabled <──┤
│ │ │
│ └──> provider4() <── Async provider ───────┘
└──> provider5()
└──> provider6()
In async mode provider prepares injections asynchronously.
If provider has multiple awaitable dependencies, it will run them concurrently. Provider will wait until all
dependencies are ready and inject them afterwards.
.. code-block:: bash
provider1()
├──> provider2() <── Async mode enabled
├──> provider3() <── Async mode enabled
└──> provider4() <── Async mode enabled
Here is what provider will do for the previous example:
.. code-block:: python
injections = await asyncio.gather(
provider2(),
provider3(),
provider4(),
)
await provider1(*injections)
Overriding behaviour
--------------------
In async mode provider always returns awaitable. It applies to the overriding too. If provider in async mode is
overridden by a provider that doesn't return awaitable result, the result will be wrapped into awaitable.
.. literalinclude:: ../../examples/providers/async_overriding.py
:language: python
:emphasize-lines: 19-24
:lines: 3-
Async mode mechanics and API
----------------------------
By default provider's async mode is undefined.
When provider async mode is undefined, provider will automatically select the mode during the next call.
If the result is awaitable, provider will enable async mode, if not - disable it.
If provider async mode is enabled, provider always returns awaitable. If the result is not awaitable,
provider wraps it into awaitable explicitly. You can safely ``await`` provider in async mode.
If provider async mode is disabled, provider behaves the regular way. It doesn't do async injections
preparation or non-awaitables to awaitables conversion.
Once provider async mode is enabled or disabled, provider will stay in this state. No automatic switching
will be done.
.. image:: images/async_mode.png
You can also use following methods to change provider's async mode manually:
- ``Provider.enable_async_mode()``
- ``Provider.disable_async_mode()``
- ``Provider.reset_async_mode()``
To check the state of provider's async mode use:
- ``Provider.is_async_mode_enabled()``
- ``Provider.is_async_mode_disabled()``
- ``Provider.is_async_mode_undefined()``
See also:
- Wiring :ref:`async-injections-wiring`
- Resource provider :ref:`resource-async-initializers`
- :ref:`fastapi-redis-example`
.. disqus::

View File

@ -5,14 +5,10 @@ Configuration provider
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
Option,Ini,Json,Yaml,Pydantic,Dict,Environment Variable Interpolation,
Environment Variable Substitution,Environment Variable in Config,
Environment Variable in YAML file,Environment Variable in INI file,Default,Load,Read
Option,Ini,Json,Yaml,Dict,Environment Variable,Load,Read,Get
:description: Configuration provides configuration options to the other providers. This page
demonstrates how to use Configuration provider to inject the dependencies, load
a configuration from an ini or yaml file, a dictionary, an environment variable,
or a pydantic settings object. This page also describes how to substitute (interpolate)
environment variables in YAML and INI configuration files.
a configuration from an ini or yaml file, dictionary or an environment variable.
.. currentmodule:: dependency_injector.providers
@ -25,10 +21,6 @@ Configuration provider
It implements the principle "use first, define later".
.. contents::
:local:
:backlinks: none
Loading from an INI file
------------------------
@ -45,31 +37,9 @@ where ``examples/providers/configuration/config.ini`` is:
.. literalinclude:: ../../examples/providers/configuration/config.ini
:language: ini
Alternatively, you can provide a path to the INI file over the configuration provider argument. In that case,
the container will call ``config.from_ini()`` automatically:
.. code-block:: python
:emphasize-lines: 3
class Container(containers.DeclarativeContainer):
config = providers.Configuration(ini_files=["./config.ini"])
if __name__ == "__main__":
container = Container() # Config is loaded from ./config.ini
:py:meth:`Configuration.from_ini` method supports environment variables interpolation.
.. code-block:: ini
[section]
option1 = ${ENV_VAR}
option2 = ${ENV_VAR}/path
option3 = ${ENV_VAR:default}
See also: :ref:`configuration-envs-interpolation`.
:py:meth:`Configuration.from_ini` method supports environment variables interpolation. Use
``${ENV_NAME}`` format in the configuration file to substitute value of the environment
variable ``ENV_NAME``.
Loading from a YAML file
------------------------
@ -87,40 +57,9 @@ where ``examples/providers/configuration/config.yml`` is:
.. literalinclude:: ../../examples/providers/configuration/config.yml
:language: ini
Alternatively, you can provide a path to the YAML file over the configuration provider argument. In that case,
the container will call ``config.from_yaml()`` automatically:
.. code-block:: python
:emphasize-lines: 3
class Container(containers.DeclarativeContainer):
config = providers.Configuration(yaml_files=["./config.yml"])
if __name__ == "__main__":
container = Container() # Config is loaded from ./config.yml
:py:meth:`Configuration.from_yaml` method supports environment variables interpolation.
.. code-block:: ini
section:
option1: ${ENV_VAR}
option2: ${ENV_VAR}/path
option3: ${ENV_VAR:default}
See also: :ref:`configuration-envs-interpolation`.
:py:meth:`Configuration.from_yaml` method uses custom version of ``yaml.SafeLoader``.
To use another loader use ``loader`` argument:
.. code-block:: python
import yaml
container.config.from_yaml("config.yml", loader=yaml.UnsafeLoader)
:py:meth:`Configuration.from_yaml` method supports environment variables interpolation. Use
``${ENV_NAME}`` format in the configuration file to substitute value of the environment
variable ``ENV_NAME``.
.. note::
@ -136,102 +75,6 @@ To use another loader use ``loader`` argument:
*Don't forget to mirror the changes in the requirements file.*
Loading from a JSON file
------------------------
``Configuration`` provider can load configuration from a ``json`` file using the
:py:meth:`Configuration.from_json` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_json.py
:language: python
:lines: 3-
:emphasize-lines: 12
where ``examples/providers/configuration/config.json`` is:
.. literalinclude:: ../../examples/providers/configuration/config.json
:language: json
Alternatively, you can provide a path to a json file over the configuration provider argument. In that case,
the container will call ``config.from_json()`` automatically:
.. code-block:: python
:emphasize-lines: 3
class Container(containers.DeclarativeContainer):
config = providers.Configuration(json_files=["./config.json"])
if __name__ == "__main__":
container = Container() # Config is loaded from ./config.json
:py:meth:`Configuration.from_json` method supports environment variables interpolation.
.. code-block:: json
{
"section": {
"option1": "${ENV_VAR}",
"option2": "${ENV_VAR}/path",
"option3": "${ENV_VAR:default}"
}
}
See also: :ref:`configuration-envs-interpolation`.
Loading from a Pydantic settings
--------------------------------
``Configuration`` provider can load configuration from a ``pydantic_settings.BaseSettings`` object using the
:py:meth:`Configuration.from_pydantic` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py
:language: python
:lines: 3-
:emphasize-lines: 32
To get the data from pydantic settings ``Configuration`` provider calls its ``model_dump()`` method.
If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments.
.. code-block:: python
container.config.from_pydantic(Settings(), exclude={"optional"})
Alternatively, you can provide a ``pydantic_settings.BaseSettings`` object over the configuration provider argument. In that case,
the container will call ``config.from_pydantic()`` automatically:
.. code-block:: python
:emphasize-lines: 3
class Container(containers.DeclarativeContainer):
config = providers.Configuration(pydantic_settings=[Settings()])
if __name__ == "__main__":
container = Container() # Config is loaded from Settings()
.. note::
``Dependency Injector`` doesn't install ``pydantic-settings`` by default.
You can install the ``Dependency Injector`` with an extra dependency::
pip install dependency-injector[pydantic2]
or install ``pydantic-settings`` directly::
pip install pydantic-settings
*Don't forget to mirror the changes in the requirements file.*
.. note::
For backward-compatibility, Pydantic v1 is still supported.
Passing ``pydantic.BaseSettings`` instances will work just as fine as ``pydantic_settings.BaseSettings``.
Loading from a dictionary
-------------------------
@ -254,35 +97,6 @@ Loading from an environment variable
:lines: 3-
:emphasize-lines: 18-20
You can use ``as_`` argument for the type casting of an environment variable value:
.. code-block:: python
:emphasize-lines: 2,6,10
# API_KEY=secret
container.config.api_key.from_env("API_KEY", as_=str, required=True)
assert container.config.api_key() == "secret"
# SAMPLING_RATIO=0.5
container.config.sampling.from_env("SAMPLING_RATIO", as_=float, required=True)
assert container.config.sampling() == 0.5
# TIMEOUT undefined, default is used
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
assert container.config.timeout() == 5
Loading a value
---------------
``Configuration`` provider can load configuration value using the
:py:meth:`Configuration.from_value` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_value.py
:language: python
:lines: 3-
:emphasize-lines: 14-15
Loading from the multiple sources
---------------------------------
@ -299,123 +113,6 @@ where ``examples/providers/configuration/config.local.yml`` is:
.. literalinclude:: ../../examples/providers/configuration/config.local.yml
:language: ini
.. _configuration-envs-interpolation:
Using environment variables in configuration files
--------------------------------------------------
``Configuration`` provider supports environment variables interpolation in configuration files.
Use ``${ENV_NAME}`` in the configuration file to substitute value from environment
variable ``ENV_NAME``.
.. code-block:: ini
section:
option: ${ENV_NAME}
You can also specify a default value using ``${ENV_NAME:default}`` format. If environment
variable ``ENV_NAME`` is undefined, configuration provider will substitute value ``default``.
.. code-block:: ini
[section]
option = ${ENV_NAME:default}
If you'd like to specify a default value for environment variable inside of the application you can use
``os.environ.setdefault()``.
.. literalinclude:: ../../examples/providers/configuration/configuration_env_interpolation_os_default.py
:language: python
:lines: 3-
:emphasize-lines: 12
If environment variable is undefined and doesn't have a default, ``Configuration`` provider
will replace it with an empty value. This is a default behavior. To raise an error on
undefined environment variable that doesn't have a default value, pass argument
``envs_required=True`` to a configuration reading method:
.. code-block:: python
container.config.from_yaml("config.yml", envs_required=True)
See also: :ref:`configuration-strict-mode`.
.. note::
``Configuration`` provider makes environment variables interpolation before parsing. This preserves
original parser behavior. For instance, undefined environment variable in YAML configuration file
will be replaced with an empty value and then YAML parser will load the file.
Original configuration file:
.. code-block:: ini
section:
option: ${ENV_NAME}
Configuration file after interpolation where ``ENV_NAME`` is undefined:
.. code-block:: ini
section:
option:
Configuration provider after parsing interpolated YAML file contains ``None`` in
option ``section.option``:
.. code-block:: python
assert container.config.section.option() is None
If you want to disable environment variables interpolation, pass ``envs_required=None``:
.. code-block:: yaml
:caption: templates.yml
template_string: 'Hello, ${name}!'
.. code-block:: python
>>> container.config.from_yaml("templates.yml", envs_required=None)
>>> container.config.template_string()
'Hello, ${name}!'
Mandatory and optional sources
------------------------------
By default, methods ``.from_yaml()`` and ``.from_ini()`` ignore errors if configuration file does not exist.
You can use this to specify optional configuration files.
If configuration file is mandatory, use ``required`` argument. Configuration provider will raise an error
if required file does not exist.
You can also use ``required`` argument when loading configuration from dictionaries and environment variables.
Mandatory YAML file:
.. code-block:: python
container.config.from_yaml("config.yaml", required=True)
Mandatory INI file:
.. code-block:: python
container.config.from_ini("config.ini", required=True)
Mandatory dictionary:
.. code-block:: python
container.config.from_dict(config_dict, required=True)
Mandatory environment variable:
.. code-block:: python
container.config.api_key.from_env("API_KEY", required=True)
See also: :ref:`configuration-strict-mode`.
Specifying the value type
-------------------------
@ -446,119 +143,6 @@ With the ``.as_(callback, *args, **kwargs)`` you can specify a function that wil
before the injection. The value from the config will be passed as a first argument. The returned
value will be injected. Parameters ``*args`` and ``**kwargs`` are handled as any other injections.
.. _configuration-strict-mode:
Strict mode and required options
--------------------------------
You can use configuration provider in strict mode. In strict mode configuration provider raises an error
on access to any undefined option.
.. literalinclude:: ../../examples/providers/configuration/configuration_strict.py
:language: python
:lines: 3-
:emphasize-lines: 12
Methods ``.from_*()`` in strict mode raise an exception if configuration file does not exist or
configuration data is undefined:
.. code-block:: python
:emphasize-lines: 10,15,20,25,30
class Container(containers.DeclarativeContainer):
config = providers.Configuration(strict=True)
if __name__ == "__main__":
container = Container()
try:
container.config.from_yaml("does-not_exist.yml") # raise exception
except FileNotFoundError:
...
try:
container.config.from_ini("does-not_exist.ini") # raise exception
except FileNotFoundError:
...
try:
container.config.from_pydantic(EmptySettings()) # raise exception
except ValueError:
...
try:
container.config.from_env("UNDEFINED_ENV_VAR") # raise exception
except ValueError:
...
try:
container.config.from_dict({}) # raise exception
except ValueError:
...
Environment variables interpolation in strict mode raises an exception when encounters
an undefined environment variable without a default value.
.. code-block:: ini
section:
option: ${UNDEFINED}
.. code-block:: python
try:
container.config.from_yaml("undefined_env.yml") # raise exception
except ValueError:
...
You can override ``.from_*()`` methods behaviour in strict mode using ``required`` argument:
.. code-block:: python
class Container(containers.DeclarativeContainer):
config = providers.Configuration(strict=True)
if __name__ == "__main__":
container = Container()
container.config.from_yaml("config.yml")
container.config.from_yaml("config.local.yml", required=False)
You can also use ``.required()`` option modifier when making an injection. It does not require to switch
configuration provider to strict mode.
.. literalinclude:: ../../examples/providers/configuration/configuration_required.py
:language: python
:lines: 11-20
:emphasize-lines: 8-9
.. note::
Modifier ``.required()`` should be specified before type modifier ``.as_*()``.
Aliases
-------
You can use ``Configuration`` provider with a context manager to create aliases.
.. literalinclude:: ../../examples/providers/configuration/configuration_alias.py
:language: python
:lines: 3-
:emphasize-lines: 14,22
.. note::
Library ``environs`` is a 3rd party library. You need to install it
separately::
pip install environs
Documentation is available on GitHub: https://github.com/sloria/environs
Injecting invariants
--------------------

View File

@ -16,16 +16,12 @@ To create a custom provider you need to follow these rules:
1. New provider class should inherit :py:class:`Provider`.
2. You need to implement the ``Provider._provide()`` method.
3. You need to implement the ``Provider.__deepcopy__()`` method. It should return an
equivalent copy of a provider. All providers must be copied with the ``deepcopy()`` function
from the ``providers`` module. It's essential to pass ``memo`` into ``deepcopy`` in order to keep
the preconfigured ``args`` and ``kwargs`` of stored providers. After the a new provider object
is created, use ``Provider._copy_overriding()`` method to copy all overriding providers. See the
example below.
4. If new provider has a ``__init__()`` method, it should call the parent
equivalent copy of a provider. All providers must be copied with a ``deepcopy()`` function
from the ``providers`` module. After the a new provider object is created use
``Provider._copy_overriding()`` method to copy all overriding providers. See the example
below.
4. If the new provider has a ``__init__()`` method, it should call the parent
``Provider.__init__()``.
5. If new provider stores any other providers, these providers should be listed in
``.related`` property. Property ``.related`` also should yield providers from parent
``.related`` property.
.. literalinclude:: ../../examples/providers/custom_factory.py
:language: python
@ -34,7 +30,7 @@ To create a custom provider you need to follow these rules:
.. note::
1. Prefer delegation over inheritance. If you choose between inheriting a ``Factory`` or
inheriting a ``Provider`` and use ``Factory`` internally - the last is better.
2. When creating a new provider follow the ``Factory``-like injections style. Consistency matters.
2. When create a new provider follow the ``Factory``-like injections style. Consistency matters.
3. Use the ``__slots__`` attribute to make sure nothing could be attached to your provider. You
will also save some memory.

View File

@ -1,38 +1,21 @@
.. _dependency-provider:
Dependency provider
===================
.. currentmodule:: dependency_injector.providers
:py:class:`Dependency` provider is a placeholder for a dependency of a certain type.
:py:class:`Dependency` provider is a placeholder for the dependency of the specified type.
To specify a type of the dependency use ``instance_of`` argument: ``Dependency(instance_of=SomeClass)``.
Dependency provider will control that returned object is an instance of ``instance_of`` type.
The first argument of the ``Dependency`` provider specifies a type of the dependency. It is
called ``instance_of``. ``Dependency`` provider controls the type of the returned object to be an
instance of the ``instance_of`` type.
The ``Dependency`` provider must be overridden before usage. It can be overridden by any type of
the provider. The only rule is that overriding provider must return an instance of ``instance_of``
dependency type.
.. literalinclude:: ../../examples/providers/dependency.py
:language: python
:lines: 3-
:emphasize-lines: 26,35-36
To provide a dependency you need to override the ``Dependency`` provider. You can call
provider ``.override()`` method or provide an overriding provider when creating a container.
See :ref:`provider-overriding`. If you don't provide a dependency, ``Dependency`` provider
will raise an error:
.. literalinclude:: ../../examples/providers/dependency_undefined_error.py
:language: python
:lines: 18-
You also can provide a default for the dependency. To provide a default use ``default`` argument:
``Dependency(..., default=...)``. Default can be a value or a provider. If default is not a provider,
dependency provider will wrap it into the ``Object`` provider.
.. literalinclude:: ../../examples/providers/dependency_default.py
:language: python
:lines: 16-23
:emphasize-lines: 3
See also: :ref:`check-container-dependencies`.
:emphasize-lines: 26
.. disqus::

View File

@ -1,37 +0,0 @@
Dict provider
=============
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Dict,Injection
:description: Dict provider helps to inject a dictionary of the dependencies. This page demonstrates
how to use Dict provider.
.. currentmodule:: dependency_injector.providers
:py:class:`Dict` provider provides a dictionary of values.
.. literalinclude:: ../../examples/providers/dict.py
:language: python
:lines: 3-
:emphasize-lines: 21-24
``Dict`` provider handles keyword arguments the same way as a :ref:`factory-provider`.
To use non-string keys or keys with ``.`` and ``-`` provide a dictionary as a positional argument:
.. code-block:: python
providers.Dict({
SomeClass: providers.Factory(...),
"key.with.periods": providers.Factory(...),
"key-with-dashes": providers.Factory(...),
})
Example:
.. literalinclude:: ../../examples/providers/dict_non_string_keys.py
:language: python
:lines: 3-
:emphasize-lines: 40-43
.. disqus::

View File

@ -40,14 +40,6 @@ injected following these rules:
:language: python
:lines: 3-
``Factory`` provider can inject attributes. Use ``.add_attributes()`` method to specify
attribute injections.
.. literalinclude:: ../../examples/providers/factory_attribute_injections.py
:language: python
:lines: 3-
:emphasize-lines: 18-18
Passing arguments to the underlying providers
---------------------------------------------
@ -110,45 +102,6 @@ attribute of the provider that you're going to inject.
.. note:: Any provider has a ``.provider`` attribute.
.. _factory-string-imports:
String imports
--------------
``Factory`` provider can handle string imports:
.. code-block:: python
class Container(containers.DeclarativeContainer):
service = providers.Factory("myapp.mypackage.mymodule.Service")
You can also make a relative import:
.. code-block:: python
# in myapp/container.py
class Container(containers.DeclarativeContainer):
service = providers.Factory(".mypackage.mymodule.Service")
or import a member of the current module just specifying its name:
.. code-block:: python
class Service:
...
class Container(containers.DeclarativeContainer):
service = providers.Factory("Service")
.. note::
``Singleton``, ``Callable``, ``Resource``, and ``Coroutine`` providers handle string imports
the same way as a ``Factory`` provider.
.. _factory-specialize-provided-type:
Specializing the provided type
@ -184,20 +137,16 @@ provider with two peculiarities:
:lines: 3-
:emphasize-lines: 34
.. _factory-aggregate-provider:
Factory aggregate
-----------------
:py:class:`FactoryAggregate` provider aggregates multiple factories.
:py:class:`FactoryAggregate` provider aggregates multiple factories. When you call the
``FactoryAggregate`` it delegates the call to one of the factories.
.. seealso::
:ref:`aggregate-provider` it's a successor of ``FactoryAggregate`` provider that can aggregate
any type of provider, not only ``Factory``.
The aggregated factories are associated with the string keys. When you call the
``FactoryAggregate`` you have to provide one of the these keys as a first argument.
``FactoryAggregate`` looks for the factory with a matching key and calls it with the rest of the arguments.
The aggregated factories are associated with the string names. When you call the
``FactoryAggregate`` you have to provide one of the these names as a first argument.
``FactoryAggregate`` looks for the factory with a matching name and delegates it the work. The
rest of the arguments are passed to the delegated ``Factory``.
.. image:: images/factory_aggregate.png
:width: 100%
@ -208,12 +157,12 @@ The aggregated factories are associated with the string keys. When you call the
:lines: 3-
:emphasize-lines: 33-37,47
You can get a dictionary of the aggregated providers using ``.providers`` attribute.
To get a game provider dictionary from the previous example you can use
``game_factory.providers`` attribute.
You can get a dictionary of the aggregated factories using the ``.factories`` attribute of the
``FactoryAggregate``. To get a game factories dictionary from the previous example you can use
``game_factory.factories`` attribute.
You can also access an aggregated factory as an attribute. To create the ``Chess`` object from the
previous example you can do ``chess = game_factory.chess("John", "Jane")``.
previous example you can do ``chess = game_factory.chess('John', 'Jane')``.
.. note::
You can not override the ``FactoryAggregate`` provider.
@ -221,22 +170,4 @@ previous example you can do ``chess = game_factory.chess("John", "Jane")``.
.. note::
When you inject the ``FactoryAggregate`` provider it is passed "as is".
To use non-string keys or string keys with ``.`` and ``-``, you can provide a dictionary as a positional argument:
.. code-block:: python
providers.FactoryAggregate({
SomeClass: providers.Factory(...),
"key.with.periods": providers.Factory(...),
"key-with-dashes": providers.Factory(...),
})
Example:
.. literalinclude:: ../../examples/providers/factory_aggregate_non_string_keys.py
:language: python
:lines: 3-
:emphasize-lines: 30-33,39-40
.. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -43,15 +43,10 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
coroutine
object
list
dict
configuration
resource
aggregate
selector
dependency
overriding
provided_instance
inject_self
custom
async
typing_mypy

View File

@ -1,20 +0,0 @@
Injecting container "self"
==========================
You can inject container "self" into container providers.
.. literalinclude:: ../../examples/containers/inject_self.py
:language: python
:lines: 3-
:emphasize-lines: 20, 26
To inject container "self" you need to define ``Self`` provider. Container can have only one ``Self`` provider.
Usually you will use name ``__self__``.
You can also use different name. When you use different name container will also reference
defined ``Self`` provider in ``.__self__`` attribute.
Provider ``Self`` is not listed in container ``.providers`` attributes.
.. disqus::

View File

@ -35,4 +35,24 @@ You can do nested constructions:
:emphasize-lines: 26-32
:lines: 3-
The ``.provided`` attribute is available for the next providers:
- :py:class:`Factory` and its subclasses
- :py:class:`Singleton` and its subclasses
- :py:class:`Callable` and its subclasses
- :py:class:`Object`
- :py:class:`List`
- :py:class:`Selector`
- :py:class:`Dependency`
When you create a new provider subclass and want to implement the ``.provided`` attribute, you
should use the :py:class:`ProvidedInstance` provider. Add the ``.provided`` property
implementation to a new subclass:
.. code-block:: python
@property
def provided(self):
return ProvidedInstance(self)
.. disqus::

View File

@ -1,520 +0,0 @@
.. _resource-provider:
Resource provider
=================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Resource,Injection,
Logging,Event Loop,Thread Pool
:description: Resource provider provides a component with initialization and shutdown. It works
well for configuring logging, event loop, thread or process pool, etc.
This page demonstrates how to use resource provider.
.. currentmodule:: dependency_injector.providers
:py:class:`Resource` provider provides a component with initialization and shutdown.
.. literalinclude:: ../../examples/providers/resource.py
:language: python
:lines: 3-
Resource providers help to initialize and configure logging, event loop, thread or process pool, etc.
Resource provider is similar to ``Singleton``. Resource initialization happens only once.
You can make injections and use provided instance the same way like you do with any other provider.
.. code-block:: python
:emphasize-lines: 12
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
thread_pool = providers.Resource(
init_thread_pool,
max_workers=config.max_workers,
)
dispatcher = providers.Factory(
TaskDispatcher,
executor=thread_pool,
)
Container has an interface to initialize and shutdown all resources at once:
.. code-block:: python
container = Container()
container.init_resources()
container.shutdown_resources()
You can also initialize and shutdown resources one-by-one using ``init()`` and
``shutdown()`` methods of the provider:
.. code-block:: python
container = Container()
container.thread_pool.init()
container.thread_pool.shutdown()
When you call ``.shutdown()`` method on a resource provider, it will remove the reference to the initialized resource,
if any, and switch to uninitialized state. Some of resource initializer types support specifying custom
resource shutdown.
Resource provider supports 4 types of initializers:
- Function
- Context Manager
- Generator (legacy)
- Subclass of ``resources.Resource`` (legacy)
Function initializer
--------------------
Function is the most common way to specify resource initialization:
.. code-block:: python
def init_resource(argument1=..., argument2=...):
return SomeResource()
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
init_resource,
argument1=...,
argument2=...,
)
Function initializer may not return a value. This often happens when
you configure global resource:
.. code-block:: python
import logging.config
class Container(containers.DeclarativeContainer):
configure_logging = providers.Resource(
logging.config.fileConfig,
fname="logging.ini",
)
Function initializer does not provide a way to specify custom resource shutdown.
Context Manager initializer
---------------------------
This is an extension to the Function initializer. Resource provider automatically detects if the initializer returns a
context manager and uses it to manage the resource lifecycle.
.. code-block:: python
from dependency_injector import containers, providers
class DatabaseConnection:
def __init__(self, host, port, user, password):
self.host = host
self.port = port
self.user = user
self.password = password
def __enter__(self):
print(f"Connecting to {self.host}:{self.port} as {self.user}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Closing connection")
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
db = providers.Resource(
DatabaseConnection,
host=config.db.host,
port=config.db.port,
user=config.db.user,
password=config.db.password,
)
Generator initializer (legacy)
------------------------------
Resource provider can use 2-step generators:
- First step of generator is an initialization phase
- The second is step is a shutdown phase
.. code-block:: python
def init_resource(argument1=..., argument2=...):
resource = SomeResource() # initialization
yield resource
# shutdown
...
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
init_resource,
argument1=...,
argument2=...,
)
Generator initialization phase ends on the first ``yield`` statement. You can return a
resource object using ``yield resource`` like in the example above. Returning of the
object is not mandatory. You can leave ``yield`` statement empty:
.. code-block:: python
def init_resource(argument1=..., argument2=...):
# initialization
...
yield
# shutdown
...
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
init_resource,
argument1=...,
argument2=...,
)
.. note::
Generator initializers are automatically wrapped with ``contextmanager`` or ``asynccontextmanager`` decorator when
provided to a ``Resource`` provider.
Subclass initializer (legacy)
-----------------------------
You can create resource initializer by implementing a subclass of the ``resources.Resource``:
.. code-block:: python
from dependency_injector import resources
class MyResource(resources.Resource):
def init(self, argument1=..., argument2=...) -> SomeResource:
return SomeResource()
def shutdown(self, resource: SomeResource) -> None:
# shutdown
...
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
MyResource,
argument1=...,
argument2=...,
)
Subclass must implement two methods: ``init()`` and ``shutdown()``.
Method ``init()`` receives arguments specified in resource provider.
It performs initialization and returns resource object. Returning of the object
is not mandatory.
Method ``shutdown()`` receives resource object returned from ``init()``. If ``init()``
didn't return an object ``shutdown()`` method will be called anyway with ``None`` as a
first argument.
.. code-block:: python
from dependency_injector import resources
class MyResource(resources.Resource):
def init(self, argument1=..., argument2=...) -> None:
# initialization
...
def shutdown(self, _: None) -> None:
# shutdown
...
.. _resource-provider-wiring-closing:
Scoping Resources using specialized subclasses
----------------------------------------------
You can use specialized subclasses of ``Resource`` provider to initialize and shutdown resources by type.
Allowing for example to only initialize a subgroup of resources.
.. code-block:: python
class ScopedResource(resources.Resource):
pass
def init_service(name) -> Service:
print(f"Init {name}")
yield Service()
print(f"Shutdown {name}")
class Container(containers.DeclarativeContainer):
scoped = ScopedResource(
init_service,
"scoped",
)
generic = providers.Resource(
init_service,
"generic",
)
To initialize resources by type you can use ``init_resources(resource_type)`` and ``shutdown_resources(resource_type)``
methods adding the resource type as an argument:
.. code-block:: python
def main():
container = Container()
container.init_resources(ScopedResource)
# Generates:
# >>> Init scoped
container.shutdown_resources(ScopedResource)
# Generates:
# >>> Shutdown scoped
And to initialize all resources you can use ``init_resources()`` and ``shutdown_resources()`` without arguments:
.. code-block:: python
def main():
container = Container()
container.init_resources()
# Generates:
# >>> Init scoped
# >>> Init generic
container.shutdown_resources()
# Generates:
# >>> Shutdown scoped
# >>> Shutdown generic
It works using the ``traverse()`` method to find all resources of the specified type, selecting all resources
which are instances of the specified type.
Resources, wiring, and per-function execution scope
---------------------------------------------------
You can compound ``Resource`` provider with :ref:`wiring` to implement per-function
execution scope. For doing this you need to use additional ``Closing`` marker from
``wiring`` module.
.. literalinclude:: ../../examples/wiring/flask_resource_closing.py
:language: python
:lines: 3-
:emphasize-lines: 22
Framework initializes and injects the resource into the function. With the ``Closing`` marker
framework calls resource ``shutdown()`` method when function execution is over.
The example above produces next output:
.. code-block:: bash
Init service
Shutdown service
127.0.0.1 - - [29/Oct/2020 22:39:40] "GET / HTTP/1.1" 200 -
Init service
Shutdown service
127.0.0.1 - - [29/Oct/2020 22:39:41] "GET / HTTP/1.1" 200 -
Init service
Shutdown service
127.0.0.1 - - [29/Oct/2020 22:39:41] "GET / HTTP/1.1" 200 -
.. _resource-async-initializers:
Asynchronous initializers
-------------------------
When you write an asynchronous application, you might need to initialize resources asynchronously. Resource
provider supports asynchronous initialization and shutdown.
Asynchronous function initializer:
.. code-block:: python
async def init_async_resource(argument1=..., argument2=...):
return await connect()
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
init_resource,
argument1=...,
argument2=...,
)
Asynchronous Context Manager initializer:
.. code-block:: python
@asynccontextmanager
async def init_async_resource(argument1=..., argument2=...):
connection = await connect()
yield connection
await connection.close()
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
init_async_resource,
argument1=...,
argument2=...,
)
Asynchronous subclass initializer:
.. code-block:: python
from dependency_injector import resources
class AsyncConnection(resources.AsyncResource):
async def init(self, argument1=..., argument2=...):
yield await connect()
async def shutdown(self, connection):
await connection.close()
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
AsyncConnection,
argument1=...,
argument2=...,
)
When you use resource provider with asynchronous initializer you need to call its ``__call__()``,
``init()``, and ``shutdown()`` methods asynchronously:
.. code-block:: python
import asyncio
class Container(containers.DeclarativeContainer):
connection = providers.Resource(init_async_connection)
async def main():
container = Container()
connection = await container.connection()
connection = await container.connection.init()
connection = await container.connection.shutdown()
if __name__ == "__main__":
asyncio.run(main())
Container ``init_resources()`` and ``shutdown_resources()`` methods should be used asynchronously if there is
at least one asynchronous resource provider:
.. code-block:: python
import asyncio
class Container(containers.DeclarativeContainer):
connection1 = providers.Resource(init_async_connection)
connection2 = providers.Resource(init_sync_connection)
async def main():
container = Container()
await container.init_resources()
await container.shutdown_resources()
if __name__ == "__main__":
asyncio.run(main())
See also:
- Provider :ref:`async-injections`
- Wiring :ref:`async-injections-wiring`
- :ref:`fastapi-redis-example`
ASGI Lifespan Protocol Support
------------------------------
The :mod:`dependency_injector.ext.starlette` module provides a :class:`~dependency_injector.ext.starlette.Lifespan`
class that integrates resource providers with ASGI applications using the `Lifespan Protocol`_. This allows resources to
be automatically initialized at application startup and properly shut down when the application stops.
.. code-block:: python
from contextlib import asynccontextmanager
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
from dependency_injector.ext.starlette import Lifespan
from fastapi import FastAPI, Request, Depends, APIRouter
class Connection: ...
@asynccontextmanager
async def init_database():
print("opening database connection")
yield Connection()
print("closing database connection")
router = APIRouter()
@router.get("/")
@inject
async def index(request: Request, db: Connection = Depends(Provide["db"])):
# use the database connection here
return "OK!"
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
db = providers.Resource(init_database)
lifespan = providers.Singleton(Lifespan, __self__)
app = providers.Singleton(FastAPI, lifespan=lifespan)
_include_router = providers.Resource(
app.provided.include_router.call(),
router,
)
if __name__ == "__main__":
import uvicorn
container = Container()
app = container.app()
uvicorn.run(app, host="localhost", port=8000)
.. _Lifespan Protocol: https://asgi.readthedocs.io/en/latest/specs/lifespan.html
.. disqus::

View File

@ -30,7 +30,4 @@ When a ``Selector`` provider is called, it gets a ``selector`` value and delegat
the provider with a matching name. The ``selector`` callable works as a switch: when the returned
value is changed the ``Selector`` provider will delegate the work to another provider.
.. seealso::
:ref:`aggregate-provider` to inject a group of providers.
.. disqus::

View File

@ -1,5 +1,3 @@
.. _singleton-provider:
Singleton provider
==================
@ -20,12 +18,13 @@ returns it on the rest of the calls.
:language: python
:lines: 3-
``Singleton`` provider handles dependencies injection the same way like a :ref:`factory-provider`.
``Singleton`` provider handles an injection of the dependencies the same way like a
:ref:`factory-provider`.
.. note::
``Singleton`` provider makes dependencies injection only when it creates an object. When an object
is created and memorized ``Singleton`` provider just returns it without applying injections.
``Singleton`` provider does dependencies injection only when creates the object. When the object
is created and memorized ``Singleton`` provider just returns it without applying the injections.
Specialization of the provided type and abstract singletons work the same like like for the
factories:
@ -55,38 +54,6 @@ provider.
Resetting of the memorized object clears the reference to it. Further object's lifecycle is
managed by the garbage collector.
You can use ``.reset()`` method with a context manager. Memorized instance will be reset on
both entering and exiting a context.
.. literalinclude:: ../../examples/providers/singleton_resetting_with.py
:language: python
:lines: 3-
:emphasize-lines: 18-19
Context manager ``.reset()`` returns resetting singleton provider. You can use it for aliasing.
.. code-block:: python
with container.user_service.reset() as user_service:
...
Method ``.reset()`` resets only current provider. To reset all dependent singleton providers
call ``.full_reset()`` method.
.. literalinclude:: ../../examples/providers/singleton_full_resetting.py
:language: python
:lines: 3-
:emphasize-lines: 25
Method ``.full_reset()`` supports context manager interface like ``.reset()`` does.
.. code-block:: python
with container.user_service.full_reset() as user_service:
...
See also: :ref:`reset-container-singletons`.
Using singleton with multiple threads
-------------------------------------

View File

@ -30,7 +30,7 @@ IDE.
provider = providers.Factory(Cat)
if __name__ == "__main__":
if __name__ == '__main__':
animal = provider() # mypy knows that animal is of type "Cat"
@ -54,7 +54,5 @@ function or method.
provider: providers.Provider[Animal] = providers.Factory(Cat)
if __name__ == "__main__":
if __name__ == '__main__':
animal = provider() # mypy knows that animal is of type "Animal"
.. disqus::

View File

@ -1,7 +0,0 @@
.. list-table::
:class: no-border
:align: left
* - Sponsor the project on GitHub:
- .. raw:: html
:file: _static/sponsor.html

View File

@ -21,7 +21,7 @@ Start from the scratch or jump to the section:
:backlinks: none
You can find complete project on the
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/giphynav-aiohttp>`_.
What are we going to build?
---------------------------
@ -88,18 +88,18 @@ Prepare the environment
Let's create the environment for the project.
First we need to create a project folder:
First we need to create a project folder and the virtual environment:
.. code-block:: bash
mkdir giphynav-aiohttp-tutorial
cd giphynav-aiohttp-tutorial
python3 -m venv venv
Now let's create and activate virtual environment:
Now let's activate the virtual environment:
.. code-block:: bash
python3 -m venv venv
. venv/bin/activate
Environment is ready and now we're going to create the layout of the project.
@ -116,7 +116,7 @@ Initial project layout::
│ ├── __init__.py
│ ├── application.py
│ ├── containers.py
│ └── handlers.py
│ └── views.py
├── venv/
└── requirements.txt
@ -127,6 +127,8 @@ Now it's time to install the project requirements. We will use next packages:
- ``dependency-injector`` - the dependency injection framework
- ``aiohttp`` - the web framework
- ``aiohttp-devtools`` - the helper library that will provide a development server with live
reloading
- ``pyyaml`` - the YAML files parsing library, used for the reading of the configuration files
- ``pytest-aiohttp`` - the helper library for the testing of the ``aiohttp`` application
- ``pytest-cov`` - the helper library for measuring the test coverage
@ -137,6 +139,7 @@ Put next lines into the ``requirements.txt`` file:
dependency-injector
aiohttp
aiohttp-devtools
pyyaml
pytest-aiohttp
pytest-cov
@ -161,51 +164,60 @@ The requirements are setup. Now we will build a minimal application.
Minimal application
-------------------
In this section we will build a minimal application. It will have an endpoint that
will answer our requests in json format. There will be no payload for now.
In this section we will build a minimal application. It will have an endpoint that we can call.
The endpoint will answer in the right format and will have no data.
Edit ``handlers.py``:
Edit ``views.py``:
.. code-block:: python
"""Handlers module."""
"""Views module."""
from aiohttp import web
async def index(request: web.Request) -> web.Response:
query = request.query.get("query", "Dependency Injector")
limit = int(request.query.get("limit", 10))
query = request.query.get('query', 'Dependency Injector')
limit = int(request.query.get('limit', 10))
gifs = []
return web.json_response(
{
"query": query,
"limit": limit,
"gifs": gifs,
'query': query,
'limit': limit,
'gifs': gifs,
},
)
Now let's create a container. Container will keep all of the application components and their dependencies.
Now let's create the main part of our application - the container. Container will keep all of the
application components and their dependencies. First two providers we need to add are
the ``aiohttp`` application provider and the view provider.
Edit ``containers.py``:
Put next into the ``containers.py``:
.. code-block:: python
"""Containers module."""
"""Application containers module."""
from dependency_injector import containers
from dependency_injector.ext import aiohttp
from aiohttp import web
from . import views
class Container(containers.DeclarativeContainer):
...
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
Container is empty for now. We will add the providers in the following sections.
app = aiohttp.Application(web.Application)
Finally we need to create ``aiohttp`` application factory. It will create and configure container
and ``web.Application``. It is traditionally called ``create_app()``.
We will assign ``index`` handler to handle user requests to the root ``/`` of our web application.
index_view = aiohttp.View(views.index)
At the last we need to create the ``aiohttp`` application factory. It is traditionally called
``create_app()``. It will create the container. Then it will use the container to create
the ``aiohttp`` application. Last step is to configure the routing - we will assign
``index_view`` from the container to handle the requests to the root ``/`` of our REST API server.
Put next into the ``application.py``:
@ -215,24 +227,27 @@ Put next into the ``application.py``:
from aiohttp import web
from .containers import Container
from . import handlers
from .containers import ApplicationContainer
def create_app() -> web.Application:
container = Container()
def create_app():
"""Create and return aiohttp application."""
container = ApplicationContainer()
app = web.Application()
app: web.Application = container.app()
app.container = container
app.add_routes([
web.get("/", handlers.index),
web.get('/', container.index_view.as_view()),
])
return app
.. note::
if __name__ == "__main__":
app = create_app()
web.run_app(app)
Container is the first object in the application.
The container is used to create all other objects.
Now we're ready to run our application
@ -240,30 +255,30 @@ Do next in the terminal:
.. code-block:: bash
python -m giphynavigator.application
adev runserver giphynavigator/application.py --livereload
The output should be something like:
.. code-block:: bash
======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)
[18:52:59] Starting aux server at http://localhost:8001 ◆
[18:52:59] Starting dev server at http://localhost:8000 ●
Let's check that it works. Open another terminal session and use ``httpie``:
Let's use ``httpie`` to check that it works:
.. code-block:: bash
http http://0.0.0.0:8080/
http http://127.0.0.1:8000/
You should see:
.. code-block:: http
.. code-block:: json
HTTP/1.1 200 OK
Content-Length: 844
Content-Type: application/json; charset=utf-8
Date: Wed, 29 Jul 2020 21:01:50 GMT
Server: Python/3.10 aiohttp/3.6.2
Server: Python/3.8 aiohttp/3.6.2
{
"gifs": [],
@ -291,7 +306,7 @@ Create ``giphy.py`` module in the ``giphynavigator`` package:
│ ├── application.py
│ ├── containers.py
│ ├── giphy.py
│ └── handlers.py
│ └── views.py
├── venv/
└── requirements.txt
@ -306,7 +321,7 @@ and put next into it:
class GiphyClient:
API_URL = "https://api.giphy.com/v1"
API_URL = 'http://api.giphy.com/v1'
def __init__(self, api_key, timeout):
self._api_key = api_key
@ -314,11 +329,11 @@ and put next into it:
async def search(self, query, limit):
"""Make search API call and return result."""
url = f"{self.API_URL}/gifs/search"
url = f'{self.API_URL}/gifs/search'
params = {
"q": query,
"api_key": self._api_key,
"limit": limit,
'q': query,
'api_key': self._api_key,
'limit': limit,
}
async with ClientSession(timeout=self._timeout) as session:
async with session.get(url, params=params) as response:
@ -330,26 +345,29 @@ Now we need to add ``GiphyClient`` into the container. The ``GiphyClient`` has t
that have to be injected: the API key and the request timeout. We will need to use two more
providers from the ``dependency_injector.providers`` module:
- ``Factory`` provider. It will create a ``GiphyClient`` client.
- ``Configuration`` provider. It will provide an API key and a request timeout for the ``GiphyClient``
client. We will specify the location of the configuration file. The configuration provider will parse
the configuration file when we create a container instance.
- ``Factory`` provider that will create the ``GiphyClient`` client.
- ``Configuration`` provider that will provide the API key and the request timeout.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 3-5,10-16
:emphasize-lines: 3,7,15,17-21
"""Containers module."""
"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import aiohttp
from aiohttp import web
from . import giphy
from . import giphy, views
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
config = providers.Configuration(yaml_files=["config.yml"])
app = aiohttp.Application(web.Application)
config = providers.Configuration()
giphy_client = providers.Factory(
giphy.GiphyClient,
@ -357,8 +375,20 @@ Edit ``containers.py``:
timeout=config.giphy.request_timeout,
)
Now let's add the configuration file. We will use YAML. Create an empty file ``config.yml`` in
the root root of the project:
index_view = aiohttp.View(views.index)
.. note::
We have used the configuration value before it was defined. That's the principle how the
``Configuration`` provider works.
Use first, define later.
Now let's add the configuration file.
We will use YAML.
Create an empty file ``config.yml`` in the root root of the project:
.. code-block:: bash
:emphasize-lines: 9
@ -369,7 +399,7 @@ the root root of the project:
│ ├── application.py
│ ├── containers.py
│ ├── giphy.py
│ └── handlers.py
│ └── views.py
├── venv/
├── config.yml
└── requirements.txt
@ -381,39 +411,40 @@ and put next into it:
giphy:
request_timeout: 10
We will use an environment variable ``GIPHY_API_KEY`` to provide the API key.
We will use the ``GIPHY_API_KEY`` environment variable to provide the API key. Lets edit
``create_app()`` to fetch the key value from it.
Now we need to edit ``create_app()`` to make two things when application starts:
- Load the configuration file the ``config.yml``.
- Load the API key from the ``GIPHY_API_KEY`` environment variable.
Edit ``application.py``:
.. code-block:: python
:emphasize-lines: 11
:emphasize-lines: 11-12
"""Application module."""
from aiohttp import web
from .containers import Container
from . import handlers
from .containers import ApplicationContainer
def create_app() -> web.Application:
container = Container()
container.config.giphy.api_key.from_env("GIPHY_API_KEY")
def create_app():
"""Create and return aiohttp application."""
container = ApplicationContainer()
container.config.from_yaml('config.yml')
container.config.giphy.api_key.from_env('GIPHY_API_KEY')
app = web.Application()
app: web.Application = container.app()
app.container = container
app.add_routes([
web.get("/", handlers.index),
web.get('/', container.index_view.as_view()),
])
return app
if __name__ == "__main__":
app = create_app()
web.run_app(app)
Now we need to create an API key and set it to the environment variable.
As for now, dont worry, just take this one:
@ -442,7 +473,7 @@ Now it's time to add the ``SearchService``. It will:
Create ``services.py`` module in the ``giphynavigator`` package:
.. code-block:: bash
:emphasize-lines: 8
:emphasize-lines: 7
./
├── giphynavigator/
@ -450,10 +481,9 @@ Create ``services.py`` module in the ``giphynavigator`` package:
│ ├── application.py
│ ├── containers.py
│ ├── giphy.py
│ ├── handlers.py
│ └── services.py
│ ├── services.py
│ └── views.py
├── venv/
├── config.yml
└── requirements.txt
and put next into it:
@ -477,26 +507,31 @@ and put next into it:
result = await self._giphy_client.search(query, limit)
return [{"url": gif["url"]} for gif in result["data"]]
return [{'url': gif['url']} for gif in result['data']]
The ``SearchService`` has a dependency on the ``GiphyClient``. This dependency will be
injected when we add ``SearchService`` to the container.
The ``SearchService`` has a dependency on the ``GiphyClient``. This dependency will be injected.
Let's add ``SearchService`` to the container.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 5,18-21
:emphasize-lines: 7,23-26
"""Containers module."""
"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import aiohttp
from aiohttp import web
from . import giphy, services
from . import giphy, services, views
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
config = providers.Configuration(yaml_files=["config.yml"])
app = aiohttp.Application(web.Application)
config = providers.Configuration()
giphy_client = providers.Factory(
giphy.GiphyClient,
@ -509,67 +544,67 @@ Edit ``containers.py``:
giphy_client=giphy_client,
)
The search service is ready. In next section we're going to put it to work.
index_view = aiohttp.View(views.index)
The search service is ready. In the next section we're going to make it work.
Make the search work
--------------------
Now we are ready to put the search into work. Let's inject ``SearchService`` into
the ``index`` handler. We will use :ref:`wiring` feature.
Now we are ready to make the search work. Let's use the ``SearchService`` in the ``index`` view.
Edit ``handlers.py``:
Edit ``views.py``:
.. code-block:: python
:emphasize-lines: 4-7,10-14,18
:emphasize-lines: 5,8-11,15
"""Handlers module."""
"""Views module."""
from aiohttp import web
from dependency_injector.wiring import Provide, inject
from .services import SearchService
from .containers import Container
@inject
async def index(
request: web.Request,
search_service: SearchService = Provide[Container.search_service],
search_service: SearchService,
) -> web.Response:
query = request.query.get("query", "Dependency Injector")
limit = int(request.query.get("limit", 10))
query = request.query.get('query', 'Dependency Injector')
limit = int(request.query.get('limit', 10))
gifs = await search_service.search(query, limit)
return web.json_response(
{
"query": query,
"limit": limit,
"gifs": gifs,
'query': query,
'limit': limit,
'gifs': gifs,
},
)
To make the injection work we need to wire the container with the ``handlers`` module.
Let's configure the container to automatically make wiring with the ``handlers`` module when we
create a container instance.
Now let's inject the ``SearchService`` dependency into the ``index`` view.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 10
:emphasize-lines: 28-31
"""Containers module."""
"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import aiohttp
from aiohttp import web
from . import giphy, services
from . import giphy, services, views
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
wiring_config = containers.WiringConfiguration(modules=[".handlers"])
app = aiohttp.Application(web.Application)
config = providers.Configuration(yaml_files=["config.yml"])
config = providers.Configuration()
giphy_client = providers.Factory(
giphy.GiphyClient,
@ -582,47 +617,52 @@ Edit ``containers.py``:
giphy_client=giphy_client,
)
Make sure the app is running:
index_view = aiohttp.View(
views.index,
search_service=search_service,
)
Make sure the app is running or use:
.. code-block:: bash
python -m giphynavigator.application
adev runserver giphynavigator/application.py --livereload
and make a request to the API in the terminal:
.. code-block:: bash
http http://0.0.0.0:8080/ query=="wow,it works" limit==5
http http://localhost:8000/ query=="wow,it works" limit==5
You should see:
.. code-block:: http
.. code-block:: json
HTTP/1.1 200 OK
Content-Length: 492
Content-Length: 850
Content-Type: application/json; charset=utf-8
Date: Fri, 09 Oct 2020 01:35:48 GMT
Server: Python/3.10 aiohttp/3.6.2
Date: Wed, 29 Jul 2020 22:22:55 GMT
Server: Python/3.8 aiohttp/3.6.2
{
"gifs": [
{
"url": "https://giphy.com/gifs/dollyparton-3xIVVMnZfG3KQ9v4Ye"
},
{
"url": "https://giphy.com/gifs/tennistv-unbelievable-disbelief-cant-believe-UWWJnhHHbpGvZOapEh"
},
{
"url": "https://giphy.com/gifs/discoverychannel-nugget-gold-rush-rick-ness-KGGPIlnC4hr4u2s3pY"
},
{
"url": "https://giphy.com/gifs/soulpancake-wow-work-xUe4HVXTPi0wQ2OAJC"
"url": "https://giphy.com/gifs/primevideoin-ll1hyBS2IrUPLE0E71"
},
{
"url": "https://giphy.com/gifs/readingrainbow-teamwork-levar-burton-reading-rainbow-3o7qE1EaTWLQGDSabK"
}
"url": "https://giphy.com/gifs/jackman-works-jackmanworks-l4pTgQoCrmXq8Txlu"
},
{
"url": "https://giphy.com/gifs/cat-massage-at-work-l46CzMaOlJXAFuO3u"
},
{
"url": "https://giphy.com/gifs/everwhatproductions-fun-christmas-3oxHQCI8tKXoeW4IBq"
},
],
"limit": 5,
"limit": 10,
"query": "wow,it works"
}
@ -633,48 +673,86 @@ The search works!
Make some refactoring
---------------------
Our ``index`` handler has two hardcoded config values:
Our ``index`` view has two hardcoded config values:
- Default search query
- Default results limit
Let's make some refactoring. We will move these values to the config.
Edit ``handlers.py``:
Edit ``views.py``:
.. code-block:: python
:emphasize-lines: 14-15,17-18
:emphasize-lines: 11-12,14-15
"""Handlers module."""
"""Views module."""
from aiohttp import web
from dependency_injector.wiring import Provide, inject
from .services import SearchService
from .containers import Container
@inject
async def index(
request: web.Request,
search_service: SearchService = Provide[Container.search_service],
default_query: str = Provide[Container.config.default.query],
default_limit: int = Provide[Container.config.default.limit.as_int()],
search_service: SearchService,
default_query: str,
default_limit: int,
) -> web.Response:
query = request.query.get("query", default_query)
limit = int(request.query.get("limit", default_limit))
query = request.query.get('query', default_query)
limit = int(request.query.get('limit', default_limit))
gifs = await search_service.search(query, limit)
return web.json_response(
{
"query": query,
"limit": limit,
"gifs": gifs,
'query': query,
'limit': limit,
'gifs': gifs,
},
)
Let's update the config.
Now we need to inject these values. Let's update the container.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 31-32
"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import aiohttp
from aiohttp import web
from . import giphy, services, views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = aiohttp.Application(web.Application)
config = providers.Configuration()
giphy_client = providers.Factory(
giphy.GiphyClient,
api_key=config.giphy.api_key,
timeout=config.giphy.request_timeout,
)
search_service = providers.Factory(
services.SearchService,
giphy_client=giphy_client,
)
index_view = aiohttp.View(
views.index,
search_service=search_service,
default_query=config.search.default_query,
default_limit=config.search.default_limit,
)
Finally let's update the config.
Edit ``config.yml``:
@ -683,21 +761,26 @@ Edit ``config.yml``:
giphy:
request_timeout: 10
default:
query: "Dependency Injector"
limit: 10
search:
default_query: "Dependency Injector"
default_limit: 10
The refactoring is done. We've made it cleaner - hardcoded values are now moved to the config.
In the next section we will add some tests.
Tests
-----
In this section we will add some tests.
It would be nice to add some tests. Let's do it.
We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
`coverage <https://coverage.readthedocs.io/>`_.
Create ``tests.py`` module in the ``giphynavigator`` package:
.. code-block:: bash
:emphasize-lines: 9
:emphasize-lines: 8
./
├── giphynavigator/
@ -705,17 +788,16 @@ Create ``tests.py`` module in the ``giphynavigator`` package:
│ ├── application.py
│ ├── containers.py
│ ├── giphy.py
│ ├── handlers.py
│ ├── services.py
│ └── tests.py
│ ├── tests.py
│ └── views.py
├── venv/
├── config.yml
└── requirements.txt
and put next into it:
.. code-block:: python
:emphasize-lines: 32,59,73
:emphasize-lines: 30,57,71
"""Tests module."""
@ -729,9 +811,7 @@ and put next into it:
@pytest.fixture
def app():
app = create_app()
yield app
app.container.unwire()
return create_app()
@pytest.fixture
@ -742,29 +822,29 @@ and put next into it:
async def test_index(client, app):
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = {
"data": [
{"url": "https://giphy.com/gif1.gif"},
{"url": "https://giphy.com/gif2.gif"},
'data': [
{'url': 'https://giphy.com/gif1.gif'},
{'url': 'https://giphy.com/gif2.gif'},
],
}
with app.container.giphy_client.override(giphy_client_mock):
response = await client.get(
"/",
'/',
params={
"query": "test",
"limit": 10,
'query': 'test',
'limit': 10,
},
)
assert response.status == 200
data = await response.json()
assert data == {
"query": "test",
"limit": 10,
"gifs": [
{"url": "https://giphy.com/gif1.gif"},
{"url": "https://giphy.com/gif2.gif"},
'query': 'test',
'limit': 10,
'gifs': [
{'url': 'https://giphy.com/gif1.gif'},
{'url': 'https://giphy.com/gif2.gif'},
],
}
@ -772,30 +852,30 @@ and put next into it:
async def test_index_no_data(client, app):
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = {
"data": [],
'data': [],
}
with app.container.giphy_client.override(giphy_client_mock):
response = await client.get("/")
response = await client.get('/')
assert response.status == 200
data = await response.json()
assert data["gifs"] == []
assert data['gifs'] == []
async def test_index_default_params(client, app):
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = {
"data": [],
'data': [],
}
with app.container.giphy_client.override(giphy_client_mock):
response = await client.get("/")
response = await client.get('/')
assert response.status == 200
data = await response.json()
assert data["query"] == app.container.config.default.query()
assert data["limit"] == app.container.config.default.limit()
assert data['query'] == app.container.config.search.default_query()
assert data['limit'] == app.container.config.search.default_limit()
Now let's run it and check the coverage:
@ -805,26 +885,27 @@ Now let's run it and check the coverage:
You should see:
.. code-block::
.. code-block:: bash
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: asyncio-0.16.0, anyio-3.3.4, aiohttp-0.3.0, cov-3.0.0
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
plugins: cov-2.10.0, aiohttp-0.3.0, asyncio-0.14.0
collected 3 items
giphynavigator/tests.py ... [100%]
---------- coverage: platform darwin, python 3.10.0-final-0 ----------
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
Name Stmts Miss Cover
---------------------------------------------------
giphynavigator/__init__.py 0 0 100%
giphynavigator/application.py 13 2 85%
giphynavigator/containers.py 7 0 100%
giphynavigator/__main__.py 5 5 0%
giphynavigator/application.py 10 0 100%
giphynavigator/containers.py 10 0 100%
giphynavigator/giphy.py 14 9 36%
giphynavigator/handlers.py 10 0 100%
giphynavigator/services.py 9 1 89%
giphynavigator/tests.py 37 0 100%
giphynavigator/tests.py 35 0 100%
giphynavigator/views.py 7 0 100%
---------------------------------------------------
TOTAL 90 12 87%
TOTAL 90 15 83%
.. note::
@ -839,19 +920,45 @@ In this tutorial we've built an ``aiohttp`` REST API application following the d
injection principle.
We've used the ``Dependency Injector`` as a dependency injection framework.
:ref:`containers` and :ref:`providers` helped to specify how to assemble search service and
giphy client.
The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff
when you need to understand or change your application structure. It's easy with the container,
cause you have everything defined explicitly in one place:
:ref:`configuration-provider` helped to deal with reading YAML file and environment variable.
.. code-block:: python
We used :ref:`wiring` feature to inject the dependencies into the ``index()`` handler.
:ref:`provider-overriding` feature helped in testing.
"""Application containers module."""
We kept all the dependencies injected explicitly. This will help when you need to add or
change something in future.
from dependency_injector import containers, providers
from dependency_injector.ext import aiohttp
from aiohttp import web
You can find complete project on the
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
from . import giphy, services, views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = aiohttp.Application(web.Application)
config = providers.Configuration()
giphy_client = providers.Factory(
giphy.GiphyClient,
api_key=config.giphy.api_key,
timeout=config.giphy.request_timeout,
)
search_service = providers.Factory(
services.SearchService,
giphy_client=giphy_client,
)
index_view = aiohttp.View(
views.index,
search_service=search_service,
default_query=config.search.default_query,
default_limit=config.search.default_limit,
)
What's next?
@ -859,6 +966,4 @@ What's next?
- Know more about the :ref:`providers`
- Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus::

View File

@ -18,7 +18,7 @@ In this tutorial we will use:
- Python 3
- Docker
- Docker Compose
- Docker-compose
Start from the scratch or jump to the section:
@ -27,7 +27,7 @@ Start from the scratch or jump to the section:
:backlinks: none
You can find complete project on the
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/asyncio-daemon>`_.
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/monitoring-daemon-asyncio>`_.
What are we going to build?
---------------------------
@ -42,32 +42,33 @@ response it will log:
- The amount of bytes in the response
- The time took to complete the response
.. image:: asyncio-images/diagram.png
.. image:: asyncio_images/diagram.png
Prerequisites
-------------
We will use `docker compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
We will use `Docker <https://www.docker.com/>`_ and
`docker-compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
.. code-block:: bash
docker --version
docker compose version
docker-compose --version
The output should look something like:
.. code-block:: bash
Docker version 27.3.1, build ce12230
Docker Compose version v2.29.7
Docker version 19.03.12, build 48a66213fe
docker-compose version 1.26.2, build eefe0d31
.. note::
If you don't have ``Docker`` or ``docker compose`` you need to install them before proceeding.
If you don't have ``Docker`` or ``docker-compose`` you need to install them before proceeding.
Follow these installation guides:
- `Install Docker <https://docs.docker.com/get-docker/>`_
- `Install docker compose <https://docs.docker.com/compose/install/>`_
- `Install docker-compose <https://docs.docker.com/compose/install/>`_
The prerequisites are satisfied. Let's get started with the project layout.
@ -78,8 +79,8 @@ Create the project root folder and set it as a working directory:
.. code-block:: bash
mkdir asyncio-daemon-tutorial
cd asyncio-daemon-tutorial
mkdir monitoring-daemon-tutorial
cd monitoring-daemon-tutorial
Now we need to create the initial project structure. Create the files and folders following next
layout. All files should be empty for now. We will fill them later.
@ -128,13 +129,13 @@ Put next lines into the ``requirements.txt`` file:
pytest-cov
Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and
specify how to run it. We will use ``python:3.13-bookworm`` as a base image.
specify how to run it. We will use ``python:3.8-buster`` as a base image.
Put next lines into the ``Dockerfile`` file:
.. code-block:: bash
FROM python:3.13-bookworm
FROM python:3.8-buster
ENV PYTHONUNBUFFERED=1
@ -154,6 +155,8 @@ Put next lines into the ``docker-compose.yml`` file:
.. code-block:: yaml
version: "3.7"
services:
monitor:
@ -168,7 +171,7 @@ Run in the terminal:
.. code-block:: bash
docker compose build
docker-compose build
The build process may take a couple of minutes. You should see something like this in the end:
@ -181,16 +184,16 @@ After the build is done run the container:
.. code-block:: bash
docker compose up
docker-compose up
The output should look like:
.. code-block:: bash
Creating network "asyncio-daemon-tutorial_default" with the default driver
Creating asyncio-daemon-tutorial_monitor_1 ... done
Attaching to asyncio-daemon-tutorial_monitor_1
asyncio-daemon-tutorial_monitor_1 exited with code 0
Creating network "monitoring-daemon-tutorial_default" with the default driver
Creating monitoring-daemon-tutorial_monitor_1 ... done
Attaching to monitoring-daemon-tutorial_monitor_1
monitoring-daemon-tutorial_monitor_1 exited with code 0
The environment is ready. The application does not do any work and just exits with a code ``0``.
@ -201,17 +204,17 @@ Logging and configuration
In this section we will configure the logging and configuration file parsing.
Let's start with the the main part of our application the container. Container will keep all of
Let's start with the the main part of our application - the container. Container will keep all of
the application components and their dependencies.
First two components that we're going to add are the configuration provider and the resource provider
for configuring the logging.
First two components that we're going to add are the config object and the provider for
configuring the logging.
Put next lines into the ``containers.py`` file:
.. code-block:: python
"""Containers module."""
"""Application containers module."""
import logging
import sys
@ -219,18 +222,28 @@ Put next lines into the ``containers.py`` file:
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
config = providers.Configuration(yaml_files=["config.yml"])
config = providers.Configuration()
logging = providers.Resource(
configure_logging = providers.Callable(
logging.basicConfig,
stream=sys.stdout,
level=config.log.level,
format=config.log.format,
)
The configuration file will keep the logging settings. Put next lines into the ``config.yml`` file:
.. note::
We have used the configuration value before it was defined. That's the principle how the
``Configuration`` provider works.
Use first, define later.
The configuration file will keep the logging settings.
Put next lines into the ``config.yml`` file:
.. code-block:: yaml
@ -238,35 +251,37 @@ The configuration file will keep the logging settings. Put next lines into the `
level: "INFO"
format: "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s"
Now let's create the function that will run our daemon. It's traditionally called ``main()``.
The ``main()`` function will start the dispatcher, but we will keep it empty for now.
We will create the container instance before calling ``main()`` in ``if __name__ == "__main__"``.
Container instance will parse ``config.yml`` and then we will call the logging configuration provider.
Now let's create the function that will run our daemon. It's traditionally called
``main()``. The ``main()`` function will create the container. Then it will use the container
to parse the ``config.yml`` file and call the logging configuration provider.
Put next lines into the ``__main__.py`` file:
.. code-block:: python
"""Main module."""
"""Main module."""
from .containers import Container
from .containers import ApplicationContainer
def main() -> None:
...
def main() -> None:
"""Run the application."""
container = ApplicationContainer()
container.config.from_yaml('config.yml')
container.configure_logging()
if __name__ == "__main__":
container = Container()
container.init_resources()
main()
if __name__ == '__main__':
main()
.. note::
Container is the first object in the application.
Logging and configuration parsing part is done. In next section we will create the monitoring
The container is used to create all other objects.
Logging and configuration parsing part is done. In the next section we will create the monitoring
checks dispatcher.
Dispatcher
@ -278,7 +293,7 @@ The dispatcher will control a list of the monitoring tasks. It will execute each
to the configured schedule. The ``Monitor`` class is the base class for all the monitors. You can
create different monitors by subclassing it and implementing the ``check()`` method.
.. image:: asyncio-images/classes-01.png
.. image:: asyncio_images/class_1.png
Let's create dispatcher and the monitor base classes.
@ -321,7 +336,7 @@ and next into the ``dispatcher.py``:
.. code-block:: python
"""Dispatcher module."""
""""Dispatcher module."""
import asyncio
import logging
@ -344,7 +359,7 @@ and next into the ``dispatcher.py``:
asyncio.run(self.start())
async def start(self) -> None:
self._logger.info("Starting up")
self._logger.info('Starting up')
for monitor in self._monitors:
self._monitor_tasks.append(
@ -364,11 +379,10 @@ and next into the ``dispatcher.py``:
self._stopping = True
self._logger.info("Shutting down")
self._logger.info('Shutting down')
for task, monitor in zip(self._monitor_tasks, self._monitors):
task.cancel()
self._monitor_tasks.clear()
self._logger.info("Shutdown finished successfully")
self._logger.info('Shutdown finished successfully')
@staticmethod
async def _run_monitor(monitor: Monitor) -> None:
@ -384,7 +398,7 @@ and next into the ``dispatcher.py``:
except asyncio.CancelledError:
break
except Exception:
monitor.logger.exception("Error executing monitor check")
monitor.logger.exception('Error executing monitor check')
await asyncio.sleep(_until_next(last=time_start))
@ -393,9 +407,9 @@ Now we need to add the dispatcher to the container.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 8,22-27
:emphasize-lines: 8,23-28
"""Containers module."""
"""Application containers module."""
import logging
import sys
@ -405,11 +419,12 @@ Edit ``containers.py``:
from . import dispatcher
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
config = providers.Configuration(yaml_files=["config.yml"])
config = providers.Configuration()
logging = providers.Resource(
configure_logging = providers.Callable(
logging.basicConfig,
stream=sys.stdout,
level=config.log.level,
@ -423,33 +438,35 @@ Edit ``containers.py``:
),
)
At the last we will inject dispatcher into the ``main()`` function
and call the ``run()`` method. We will use :ref:`wiring` feature.
.. note::
Every component should be added to the container.
At the last we will add the dispatcher in the ``main()`` function. We will retrieve the
dispatcher instance from the container and call the ``run()`` method.
Edit ``__main__.py``:
.. code-block:: python
:emphasize-lines: 3-5,9-11,17
:emphasize-lines: 13-14
"""Main module."""
from dependency_injector.wiring import Provide, inject
from .dispatcher import Dispatcher
from .containers import Container
from .containers import ApplicationContainer
@inject
def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None:
def main() -> None:
"""Run the application."""
container = ApplicationContainer()
container.config.from_yaml('config.yml')
container.configure_logging()
dispatcher = container.dispatcher()
dispatcher.run()
if __name__ == "__main__":
container = Container()
container.init_resources()
container.wire(modules=[__name__])
if __name__ == '__main__':
main()
Finally let's start the daemon to check that all works.
@ -458,28 +475,28 @@ Run in the terminal:
.. code-block:: bash
docker compose up
docker-compose up
The output should look like:
.. code-block:: bash
Starting asyncio-daemon-tutorial_monitor_1 ... done
Attaching to asyncio-daemon-tutorial_monitor_1
Starting monitoring-daemon-tutorial_monitor_1 ... done
Attaching to monitoring-daemon-tutorial_monitor_1
monitor_1 | [2020-08-08 16:12:35,772] [INFO] [Dispatcher]: Starting up
monitor_1 | [2020-08-08 16:12:35,774] [INFO] [Dispatcher]: Shutting down
monitor_1 | [2020-08-08 16:12:35,774] [INFO] [Dispatcher]: Shutdown finished successfully
asyncio-daemon-tutorial_monitor_1 exited with code 0
monitoring-daemon-tutorial_monitor_1 exited with code 0
Everything works properly. Dispatcher starts up and exits because there are no monitoring tasks.
By the end of this section we have the application skeleton ready. In next section will will
By the end of this section we have the application skeleton ready. In the next section will will
add first monitoring task.
Example.com monitor
-------------------
In this section we will add a monitoring task that will check the availability of the
In this section we will add the monitoring task that will check the availability of the
`http://example.com <http://example.com>`_.
We will start from the extending of our class model with a new type of the monitoring check, the
@ -489,9 +506,9 @@ The ``HttpMonitor`` is a subclass of the ``Monitor``. We will implement the ``ch
will send the HTTP request to the specified URL. The http request sending will be delegated to
the ``HttpClient``.
.. image:: asyncio-images/classes-02.png
.. image:: asyncio_images/class_2.png
First we need to create the ``HttpClient``.
First, we need to create the ``HttpClient``.
Create ``http.py`` in the ``monitoringdaemon`` package:
@ -532,9 +549,9 @@ Now we need to add the ``HttpClient`` to the container.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 8,22
:emphasize-lines: 8, 23
"""Containers module."""
"""Application containers module."""
import logging
import sys
@ -544,11 +561,12 @@ Edit ``containers.py``:
from . import http, dispatcher
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
config = providers.Configuration(yaml_files=["config.yml"])
config = providers.Configuration()
logging = providers.Resource(
configure_logging = providers.Callable(
logging.basicConfig,
stream=sys.stdout,
level=config.log.level,
@ -569,7 +587,7 @@ Now we're ready to add the ``HttpMonitor``. We will add it to the ``monitors`` m
Edit ``monitors.py``:
.. code-block:: python
:emphasize-lines: 4-7,20-56
:emphasize-lines: 4-5,7,20-56
"""Monitors module."""
@ -598,10 +616,10 @@ Edit ``monitors.py``:
options: Dict[str, Any],
) -> None:
self._client = http_client
self._method = options.pop("method")
self._url = options.pop("url")
self._timeout = options.pop("timeout")
super().__init__(check_every=options.pop("check_every"))
self._method = options.pop('method')
self._url = options.pop('url')
self._timeout = options.pop('timeout')
super().__init__(check_every=options.pop('check_every'))
async def check(self) -> None:
time_start = time.time()
@ -616,11 +634,11 @@ Edit ``monitors.py``:
time_took = time_end - time_start
self.logger.info(
"Check\n"
" %s %s\n"
" response code: %s\n"
" content length: %s\n"
" request took: %s seconds",
'Check\n'
' %s %s\n'
' response code: %s\n'
' content length: %s\n'
' request took: %s seconds\n',
self._method,
self._url,
response.status,
@ -637,9 +655,9 @@ We make two changes in the container:
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 8,24-28,33
:emphasize-lines: 8,25-29,34
"""Containers module."""
"""Application containers module."""
import logging
import sys
@ -649,11 +667,12 @@ Edit ``containers.py``:
from . import http, monitors, dispatcher
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
config = providers.Configuration(yaml_files=["config.yml"])
config = providers.Configuration()
logging = providers.Resource(
configure_logging = providers.Callable(
logging.basicConfig,
stream=sys.stdout,
level=config.log.level,
@ -702,20 +721,21 @@ Run in the terminal:
.. code-block:: bash
docker compose up
docker-compose up
You should see:
.. code-block:: bash
Starting asyncio-daemon-tutorial_monitor_1 ... done
Attaching to asyncio-daemon-tutorial_monitor_1
Starting monitoring-daemon-tutorial_monitor_1 ... done
Attaching to monitoring-daemon-tutorial_monitor_1
monitor_1 | [2020-08-08 17:06:41,965] [INFO] [Dispatcher]: Starting up
monitor_1 | [2020-08-08 17:06:42,033] [INFO] [HttpMonitor]: Check
monitor_1 | GET http://example.com
monitor_1 | response code: 200
monitor_1 | content length: 648
monitor_1 | request took: 0.067 seconds
monitor_1 |
monitor_1 | [2020-08-08 17:06:47,040] [INFO] [HttpMonitor]: Check
monitor_1 | GET http://example.com
monitor_1 | response code: 200
@ -724,21 +744,21 @@ You should see:
Our daemon can monitor `http://example.com <http://example.com>`_ availability.
Let's add a monitor for the `https://httpbin.org <https://httpbin.org>`_.
Let's add the monitor for the `http://httpbin.org <http://httpbin.org>`_.
Httpbin.org monitor
-------------------
Adding of a monitor for the `https://httpbin.org <https://httpbin.org>`_ will be much
easier because we have all the components ready. We just need to create a new provider
in the container and update the configuration.
Adding of the monitor for the `httpbin.org`_ will be much easier because we have all the
components ready. We just need to create a new provider in the container and update the
configuration.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 30-34,40
:emphasize-lines: 31-35,41
"""Containers module."""
"""Application containers module."""
import logging
import sys
@ -748,11 +768,12 @@ Edit ``containers.py``:
from . import http, monitors, dispatcher
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
config = providers.Configuration(yaml_files=["config.yml"])
config = providers.Configuration()
logging = providers.Resource(
configure_logging = providers.Callable(
logging.basicConfig,
stream=sys.stdout,
level=config.log.level,
@ -810,30 +831,33 @@ Run in the terminal:
.. code-block:: bash
docker compose up
docker-compose up
You should see:
.. code-block:: bash
Starting asyncio-daemon-tutorial_monitor_1 ... done
Attaching to asyncio-daemon-tutorial_monitor_1
Starting monitoring-daemon-tutorial_monitor_1 ... done
Attaching to monitoring-daemon-tutorial_monitor_1
monitor_1 | [2020-08-08 18:09:08,540] [INFO] [Dispatcher]: Starting up
monitor_1 | [2020-08-08 18:09:08,618] [INFO] [HttpMonitor]: Check
monitor_1 | GET http://example.com
monitor_1 | response code: 200
monitor_1 | content length: 648
monitor_1 | request took: 0.077 seconds
monitor_1 |
monitor_1 | [2020-08-08 18:09:08,722] [INFO] [HttpMonitor]: Check
monitor_1 | GET https://httpbin.org/get
monitor_1 | response code: 200
monitor_1 | content length: 310
monitor_1 | request took: 0.18 seconds
monitor_1 |
monitor_1 | [2020-08-08 18:09:13,619] [INFO] [HttpMonitor]: Check
monitor_1 | GET http://example.com
monitor_1 | response code: 200
monitor_1 | content length: 648
monitor_1 | request took: 0.066 seconds
monitor_1 |
monitor_1 | [2020-08-08 18:09:13,681] [INFO] [HttpMonitor]: Check
monitor_1 | GET https://httpbin.org/get
monitor_1 | response code: 200
@ -843,12 +867,12 @@ You should see:
The functional part is done. Daemon monitors `http://example.com <http://example.com>`_ and
`https://httpbin.org <https://httpbin.org>`_.
In next section we will add some tests.
In the next section we will add some tests.
Tests
-----
In this section we will add some tests.
It would be nice to add some tests. Let's do it.
We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
`coverage <https://coverage.readthedocs.io/>`_.
@ -875,7 +899,7 @@ Create ``tests.py`` in the ``monitoringdaemon`` package:
and put next into it:
.. code-block:: python
:emphasize-lines: 54,70-73
:emphasize-lines: 54,70-71
"""Tests module."""
@ -885,7 +909,7 @@ and put next into it:
import pytest
from .containers import Container
from .containers import ApplicationContainer
@dataclasses.dataclass
@ -896,33 +920,33 @@ and put next into it:
@pytest.fixture
def container():
return Container(
config={
"log": {
"level": "INFO",
"formant": "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s",
container = ApplicationContainer()
container.config.from_dict({
'log': {
'level': 'INFO',
'formant': '[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s',
},
'monitors': {
'example': {
'method': 'GET',
'url': 'http://fake-example.com',
'timeout': 1,
'check_every': 1,
},
"monitors": {
"example": {
"method": "GET",
"url": "http://fake-example.com",
"timeout": 1,
"check_every": 1,
},
"httpbin": {
"method": "GET",
"url": "https://fake-httpbin.org/get",
"timeout": 1,
"check_every": 1,
},
'httpbin': {
'method': 'GET',
'url': 'https://fake-httpbin.org/get',
'timeout': 1,
'check_every': 1,
},
}
)
},
})
return container
@pytest.mark.asyncio
async def test_example_monitor(container, caplog):
caplog.set_level("INFO")
caplog.set_level('INFO')
http_client_mock = mock.AsyncMock()
http_client_mock.request.return_value = RequestStub(
@ -934,22 +958,21 @@ and put next into it:
example_monitor = container.example_monitor()
await example_monitor.check()
assert "http://fake-example.com" in caplog.text
assert "response code: 200" in caplog.text
assert "content length: 635" in caplog.text
assert 'http://fake-example.com' in caplog.text
assert 'response code: 200' in caplog.text
assert 'content length: 635' in caplog.text
@pytest.mark.asyncio
async def test_dispatcher(container, caplog, event_loop):
caplog.set_level("INFO")
caplog.set_level('INFO')
example_monitor_mock = mock.AsyncMock()
httpbin_monitor_mock = mock.AsyncMock()
with container.override_providers(
example_monitor=example_monitor_mock,
httpbin_monitor=httpbin_monitor_mock,
):
with container.example_monitor.override(example_monitor_mock), \
container.httpbin_monitor.override(httpbin_monitor_mock):
dispatcher = container.dispatcher()
event_loop.create_task(dispatcher.start())
await asyncio.sleep(0.1)
@ -962,32 +985,31 @@ Run in the terminal:
.. code-block:: bash
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
You should see:
.. code-block:: bash
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
platform linux -- Python 3.8.3, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
rootdir: /code
plugins: cov-6.0.0, asyncio-0.24.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
plugins: asyncio-0.14.0, cov-2.10.0
collected 2 items
monitoringdaemon/tests.py .. [100%]
---------- coverage: platform linux, python 3.10.0-final-0 -----------
----------- coverage: platform linux, python 3.8.3-final-0 -----------
Name Stmts Miss Cover
----------------------------------------------------
monitoringdaemon/__init__.py 0 0 100%
monitoringdaemon/__main__.py 11 11 0%
monitoringdaemon/__main__.py 9 9 0%
monitoringdaemon/containers.py 11 0 100%
monitoringdaemon/dispatcher.py 45 5 89%
monitoringdaemon/dispatcher.py 43 5 88%
monitoringdaemon/http.py 6 3 50%
monitoringdaemon/monitors.py 23 1 96%
monitoringdaemon/tests.py 35 0 100%
monitoringdaemon/tests.py 37 0 100%
----------------------------------------------------
TOTAL 131 20 85%
TOTAL 129 18 86%
.. note::
@ -1006,19 +1028,55 @@ In this tutorial we've built an ``asyncio`` monitoring daemon following the dep
injection principle.
We've used the ``Dependency Injector`` as a dependency injection framework.
With a help of :ref:`containers` and :ref:`providers` we have defined how to assemble application components.
The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff
when you need to understand or change your application structure. It's easy with the container,
cause you have everything defined explicitly in one place:
``List`` provider helped to inject a list of monitors into dispatcher.
:ref:`configuration-provider` helped to deal with reading YAML file.
.. code-block:: python
We used :ref:`wiring` feature to inject dispatcher into the ``main()`` function.
:ref:`provider-overriding` feature helped in testing.
"""Application containers module."""
We kept all the dependencies injected explicitly. This will help when you need to add or
change something in future.
import logging
import sys
You can find complete project on the
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/asyncio-daemon>`_.
from dependency_injector import containers, providers
from . import http, monitors, dispatcher
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
config = providers.Configuration()
configure_logging = providers.Callable(
logging.basicConfig,
stream=sys.stdout,
level=config.log.level,
format=config.log.format,
)
http_client = providers.Factory(http.HttpClient)
example_monitor = providers.Factory(
monitors.HttpMonitor,
http_client=http_client,
options=config.monitors.example,
)
httpbin_monitor = providers.Factory(
monitors.HttpMonitor,
http_client=http_client,
options=config.monitors.httpbin,
)
dispatcher = providers.Factory(
dispatcher.Dispatcher,
monitors=providers.List(
example_monitor,
httpbin_monitor,
),
)
What's next?
@ -1026,6 +1084,4 @@ What's next?
- Know more about the :ref:`providers`
- Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus::

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -46,11 +46,11 @@ How does Movie Lister work?
Movie Lister is a naive example from Martin Fowler's article about the dependency injection and
inversion of control:
https://www.martinfowler.com/articles/injection.html
http://www.martinfowler.com/articles/injection.html
Here is a class diagram of the Movie Lister application:
.. image:: cli-images/classes-01.png
.. image:: cli-images/classes_01.png
The responsibilities are split next way:
@ -63,18 +63,18 @@ Prepare the environment
Let's create the environment for the project.
First we need to create a project folder:
First we need to create a project folder and the virtual environment:
.. code-block:: bash
mkdir movie-lister-tutorial
cd movie-lister-tutorial
python3 -m venv venv
Now let's create and activate virtual environment:
Now let's activate the virtual environment:
.. code-block:: bash
python3 -m venv venv
. venv/bin/activate
Project layout
@ -84,7 +84,7 @@ Create next structure in the project root directory. All files are empty. That's
Initial project layout:
.. code-block:: text
.. code-block:: bash
./
├── movies/
@ -109,7 +109,7 @@ Now it's time to install the project requirements. We will use next packages:
Put next lines into the ``requirements.txt`` file:
.. code-block:: text
.. code-block:: bash
dependency-injector
pyyaml
@ -134,7 +134,7 @@ We will create a script that creates database files.
First add the folder ``data/`` in the root of the project and then add the file
``fixtures.py`` inside of it:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 2-3
./
@ -160,19 +160,19 @@ Second put next in the ``fixtures.py``:
SAMPLE_DATA = [
("The Hunger Games: Mockingjay - Part 2", 2015, "Francis Lawrence"),
("Rogue One: A Star Wars Story", 2016, "Gareth Edwards"),
("The Jungle Book", 2016, "Jon Favreau"),
('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'),
('Rogue One: A Star Wars Story', 2016, 'Gareth Edwards'),
('The Jungle Book', 2016, 'Jon Favreau'),
]
FILE = pathlib.Path(__file__)
DIR = FILE.parent
CSV_FILE = DIR / "movies.csv"
SQLITE_FILE = DIR / "movies.db"
CSV_FILE = DIR / 'movies.csv'
SQLITE_FILE = DIR / 'movies.db'
def create_csv(movies_data, path):
with open(path, "w") as opened_file:
with open(path, 'w') as opened_file:
writer = csv.writer(opened_file)
for row in movies_data:
writer.writerow(row)
@ -181,20 +181,20 @@ Second put next in the ``fixtures.py``:
def create_sqlite(movies_data, path):
with sqlite3.connect(path) as db:
db.execute(
"CREATE TABLE IF NOT EXISTS movies "
"(title text, year int, director text)"
'CREATE TABLE IF NOT EXISTS movies '
'(title text, year int, director text)'
)
db.execute("DELETE FROM movies")
db.executemany("INSERT INTO movies VALUES (?,?,?)", movies_data)
db.execute('DELETE FROM movies')
db.executemany('INSERT INTO movies VALUES (?,?,?)', movies_data)
def main():
create_csv(SAMPLE_DATA, CSV_FILE)
create_sqlite(SAMPLE_DATA, SQLITE_FILE)
print("OK")
print('OK')
if __name__ == "__main__":
if __name__ == '__main__':
main()
Now run in the terminal:
@ -205,13 +205,13 @@ Now run in the terminal:
You should see:
.. code-block:: text
.. code-block:: bash
OK
Check that files ``movies.csv`` and ``movies.db`` have appeared in the ``data/`` folder:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 4-5
./
@ -245,13 +245,13 @@ Edit ``containers.py``:
from dependency_injector import containers
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
...
Container is empty for now. We will add the providers in the following sections.
Let's also create the ``main()`` function. Its responsibility is to run our application. For now
it will just do nothing.
it will just create the container.
Edit ``__main__.py``:
@ -259,18 +259,22 @@ Edit ``__main__.py``:
"""Main module."""
from .containers import Container
from .containers import ApplicationContainer
def main() -> None:
...
def main():
container = ApplicationContainer()
if __name__ == "__main__":
container = Container()
if __name__ == '__main__':
main()
.. note::
Container is the first object in the application.
The container is used to create all other objects.
Csv finder
----------
@ -285,11 +289,11 @@ We will add:
After each step we will add the provider to the container.
.. image:: cli-images/classes-02.png
.. image:: cli-images/classes_02.png
Create the ``entities.py`` in the ``movies`` package:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 10
./
@ -321,7 +325,7 @@ and put next into it:
self.director = str(director)
def __repr__(self):
return "{0}(title={1}, year={2}, director={3})".format(
return '{0}(title={1}, year={2}, director={3})'.format(
self.__class__.__name__,
repr(self.title),
repr(self.year),
@ -334,7 +338,7 @@ Now we need to add the ``Movie`` factory to the container. We need to add import
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 3,5,10
:emphasize-lines: 3,5,9
"""Containers module."""
@ -342,8 +346,7 @@ Edit ``containers.py``:
from . import entities
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
movie = providers.Factory(entities.Movie)
@ -356,7 +359,7 @@ Let's move on to the finders.
Create the ``finders.py`` in the ``movies`` package:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 11
./
@ -417,7 +420,7 @@ Now let's add the csv finder into the container.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 5,10,14-19
:emphasize-lines: 5,9,13-18
"""Containers module."""
@ -425,10 +428,9 @@ Edit ``containers.py``:
from . import finders, entities
class ApplicationContainer(containers.DeclarativeContainer):
class Container(containers.DeclarativeContainer):
config = providers.Configuration(yaml_files=["config.yml"])
config = providers.Configuration()
movie = providers.Factory(entities.Movie)
@ -445,9 +447,15 @@ This is also called the delegation of the provider. If we just pass the movie fa
as the dependency, it will be called when csv finder is created and the ``Movie`` instance will
be injected. With the ``.provider`` attribute the provider itself will be injected.
The csv finder also has a few dependencies on the configuration options. We added a configuration
provider to provide these dependencies and specified the location of the configuration file.
The configuration provider will parse the configuration file when we create a container instance.
The csv finder also has a few dependencies on the configuration options. We added configuration
provider to provide these dependencies.
.. note::
We have used the configuration value before it was defined. That's the principle how the
Configuration provider works.
Use first, define later.
Not let's define the configuration values.
@ -461,11 +469,32 @@ Edit ``config.yml``:
path: "data/movies.csv"
delimiter: ","
The configuration file is ready. Move on to the lister.
The configuration file is ready. Now let's update the ``main()`` function to specify its location.
Edit ``__main__.py``:
.. code-block:: python
:emphasize-lines: 9
"""Main module."""
from .containers import ApplicationContainer
def main():
container = ApplicationContainer()
container.config.from_yaml('config.yml')
if __name__ == '__main__':
main()
Move on to the lister.
Create the ``listers.py`` in the ``movies`` package:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 12
./
@ -513,7 +542,7 @@ and put next into it:
and edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 5,21-24
:emphasize-lines: 5,20-23
"""Containers module."""
@ -521,10 +550,9 @@ and edit ``containers.py``:
from . import finders, listers, entities
class ApplicationContainer(containers.DeclarativeContainer):
class Container(containers.DeclarativeContainer):
config = providers.Configuration(yaml_files=["config.yml"])
config = providers.Configuration()
movie = providers.Factory(entities.Movie)
@ -542,65 +570,36 @@ and edit ``containers.py``:
All the components are created and added to the container.
Let's inject the ``lister`` into the ``main()`` function.
Finally let's update the ``main()`` function.
Edit ``__main__.py``:
.. code-block:: python
:emphasize-lines: 3-5,9-10,16
:emphasize-lines: 11-20
"""Main module."""
from dependency_injector.wiring import Provide, inject
from .listers import MovieLister
from .containers import Container
from .containers import ApplicationContainer
@inject
def main(lister: MovieLister = Provide[Container.lister]) -> None:
...
def main():
container = ApplicationContainer()
container.config.from_yaml('config.yml')
lister = container.lister()
print(
'Francis Lawrence movies:',
lister.movies_directed_by('Francis Lawrence'),
)
print(
'2016 movies:',
lister.movies_released_in(2016),
)
if __name__ == "__main__":
container = Container()
container.wire(modules=[__name__])
main()
Now when we call ``main()`` the container will assemble and inject the movie lister.
Let's add some payload to ``main()`` function. It will list movies directed by
Francis Lawrence and movies released in 2016.
Edit ``__main__.py``:
.. code-block:: python
:emphasize-lines: 11-17
"""Main module."""
from dependency_injector.wiring import Provide, inject
from .listers import MovieLister
from .containers import Container
@inject
def main(lister: MovieLister = Provide[Container.lister]) -> None:
print("Francis Lawrence movies:")
for movie in lister.movies_directed_by("Francis Lawrence"):
print("\t-", movie)
print("2016 movies:")
for movie in lister.movies_released_in(2016):
print("\t-", movie)
if __name__ == "__main__":
container = Container()
container.wire(modules=[__name__])
if __name__ == '__main__':
main()
All set. Now we run the application.
@ -613,15 +612,12 @@ Run in the terminal:
You should see:
.. code-block:: text
.. code-block:: bash
Francis Lawrence movies:
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
2016 movies:
- Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards')
- Movie(title='The Jungle Book', year=2016, director='Jon Favreau')
Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]
Our application can work with the movies database in the csv format. We also want to support
Our application can work with the movies database in the csv format. We also need to support
the sqlite format. We will deal with it in the next section.
Sqlite finder
@ -684,7 +680,7 @@ Edit ``finders.py``:
def find_all(self) -> List[Movie]:
with self._database as db:
rows = db.execute("SELECT title, year, director FROM movies")
rows = db.execute('SELECT title, year, director FROM movies')
return [self._movie_factory(*row) for row in rows]
Now we need to add the sqlite finder to the container and update lister's dependency to use it.
@ -692,7 +688,7 @@ Now we need to add the sqlite finder to the container and update lister's depend
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 21-25,29
:emphasize-lines: 20-24,28
"""Containers module."""
@ -700,10 +696,9 @@ Edit ``containers.py``:
from . import finders, listers, entities
class ApplicationContainer(containers.DeclarativeContainer):
class Container(containers.DeclarativeContainer):
config = providers.Configuration(yaml_files=["config.yml"])
config = providers.Configuration()
movie = providers.Factory(entities.Movie)
@ -752,13 +747,10 @@ Run in the terminal:
You should see:
.. code-block:: text
.. code-block:: bash
Francis Lawrence movies:
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
2016 movies:
- Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards')
- Movie(title='The Jungle Book', year=2016, director='Jon Favreau')
Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]
Our application now supports both formats: csv files and sqlite databases. Every time when we
need to work with the different format we need to make a code change in the container. We will
@ -790,9 +782,9 @@ Edit ``containers.py``:
from . import finders, listers, entities
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
config = providers.Configuration(yaml_files=["config.yml"])
config = providers.Configuration()
movie = providers.Factory(entities.Movie)
@ -820,7 +812,7 @@ Edit ``containers.py``:
movie_finder=finder,
)
The switch is the ``config.finder.type`` option. When its value is ``csv``, the provider with the
The switch is the ``config.finder.type`` option. When its value is ``csv``, the provider under
``csv`` key is used. The same is for ``sqlite``.
Now we need to read the value of the ``config.finder.type`` option from the environment variable
@ -829,32 +821,32 @@ Now we need to read the value of the ``config.finder.type`` option from the envi
Edit ``__main__.py``:
.. code-block:: python
:emphasize-lines: 22
:emphasize-lines: 10
"""Main module."""
from dependency_injector.wiring import Provide, inject
from .listers import MovieLister
from .containers import Container
from .containers import ApplicationContainer
@inject
def main(lister: MovieLister = Provide[Container.lister]) -> None:
print("Francis Lawrence movies:")
for movie in lister.movies_directed_by("Francis Lawrence"):
print("\t-", movie)
def main():
container = ApplicationContainer()
print("2016 movies:")
for movie in lister.movies_released_in(2016):
print("\t-", movie)
container.config.from_yaml('config.yml')
container.config.finder.type.from_env('MOVIE_FINDER_TYPE')
lister = container.lister()
print(
'Francis Lawrence movies:',
lister.movies_directed_by('Francis Lawrence'),
)
print(
'2016 movies:',
lister.movies_released_in(2016),
)
if __name__ == "__main__":
container = Container()
container.config.finder.type.from_env("MOVIE_FINDER_TYPE")
container.wire(modules=[sys.modules[__name__]])
if __name__ == '__main__':
main()
Done.
@ -866,15 +858,12 @@ Run in the terminal line by line:
MOVIE_FINDER_TYPE=csv python -m movies
MOVIE_FINDER_TYPE=sqlite python -m movies
The output should be similar for each command:
The output should be something like this for each command:
.. code-block:: text
.. code-block:: bash
Francis Lawrence movies:
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
2016 movies:
- Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards')
- Movie(title='The Jungle Book', year=2016, director='Jon Favreau')
Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]
In the next section we will add some tests.
@ -888,7 +877,7 @@ We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
Create ``tests.py`` in the ``movies`` package:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 13
./
@ -911,7 +900,7 @@ Create ``tests.py`` in the ``movies`` package:
and put next into it:
.. code-block:: python
:emphasize-lines: 41,50
:emphasize-lines: 35,50
"""Tests module."""
@ -919,55 +908,55 @@ and put next into it:
import pytest
from .containers import Container
from .containers import ApplicationContainer
@pytest.fixture
def container():
container = Container(
config={
"finder": {
"type": "csv",
"csv": {
"path": "/fake-movies.csv",
"delimiter": ",",
},
"sqlite": {
"path": "/fake-movies.db",
},
container = ApplicationContainer()
container.config.from_dict({
'finder': {
'type': 'csv',
'csv': {
'path': '/fake-movies.csv',
'delimiter': ',',
},
'sqlite': {
'path': '/fake-movies.db',
},
},
)
})
return container
@pytest.fixture
def finder_mock(container):
def test_movies_directed_by(container):
finder_mock = mock.Mock()
finder_mock.find_all.return_value = [
container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"),
container.movie('The 33', 2015, 'Patricia Riggen'),
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
]
return finder_mock
def test_movies_directed_by(container, finder_mock):
with container.finder.override(finder_mock):
lister = container.lister()
movies = lister.movies_directed_by("Jon Favreau")
movies = lister.movies_directed_by('Jon Favreau')
assert len(movies) == 1
assert movies[0].title == "The Jungle Book"
assert movies[0].title == 'The Jungle Book'
def test_movies_released_in(container, finder_mock):
def test_movies_released_in(container):
finder_mock = mock.Mock()
finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'),
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
]
with container.finder.override(finder_mock):
lister = container.lister()
movies = lister.movies_released_in(2015)
assert len(movies) == 1
assert movies[0].title == "The 33"
assert movies[0].title == 'The 33'
Run in the terminal:
@ -977,26 +966,26 @@ Run in the terminal:
You should see:
.. code-block:: text
.. code-block:: bash
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-3.0.0
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
plugins: cov-2.10.0
collected 2 items
movies/tests.py .. [100%]
---------- coverage: platform darwin, python 3.10 -----------
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
Name Stmts Miss Cover
------------------------------------------
movies/__init__.py 0 0 100%
movies/__main__.py 16 16 0%
movies/__main__.py 10 10 0%
movies/containers.py 9 0 100%
movies/entities.py 7 1 86%
movies/finders.py 26 13 50%
movies/listers.py 8 0 100%
movies/tests.py 24 0 100%
------------------------------------------
TOTAL 90 30 67%
TOTAL 84 24 71%
.. note::
@ -1013,19 +1002,48 @@ Conclusion
In this tutorial we've built a CLI application following the dependency injection principle.
We've used the ``Dependency Injector`` as a dependency injection framework.
With a help of :ref:`containers` and :ref:`providers` we have defined how to assemble application components.
The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff
when you need to understand or change your application structure. It's easy with the container,
cause you have everything defined explicitly in one place:
``Selector`` provider served as a switch for selecting the database format based on a configuration.
:ref:`configuration-provider` helped to deal with reading a YAML file and environment variables.
.. code-block:: python
We used :ref:`wiring` feature to inject the dependencies into the ``main()`` function.
:ref:`provider-overriding` feature helped in testing.
"""Containers module."""
We kept all the dependencies injected explicitly. This will help when you need to add or
change something in future.
from dependency_injector import containers, providers
You can find complete project on the
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/movie-lister>`_.
from . import finders, listers, entities
class ApplicationContainer(containers.DeclarativeContainer):
config = providers.Configuration()
movie = providers.Factory(entities.Movie)
csv_finder = providers.Singleton(
finders.CsvMovieFinder,
movie_factory=movie.provider,
path=config.finder.csv.path,
delimiter=config.finder.csv.delimiter,
)
sqlite_finder = providers.Singleton(
finders.SqliteMovieFinder,
movie_factory=movie.provider,
path=config.finder.sqlite.path,
)
finder = providers.Selector(
config.finder.type,
csv=csv_finder,
sqlite=sqlite_finder,
)
lister = providers.Factory(
listers.MovieLister,
movie_finder=finder,
)
What's next?
@ -1033,6 +1051,4 @@ What's next?
- Know more about the :ref:`providers`
- Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 KiB

View File

@ -21,7 +21,7 @@ Start from the scratch or jump to the section:
:backlinks: none
You can find complete project on the
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/ghnav-flask>`_.
What are we going to build?
---------------------------
@ -43,25 +43,25 @@ How does Github Navigator work?
- User can click on the repository, the repository owner or the last commit to open its web page
on the Github.
.. image:: flask-images/screen-02.png
.. image:: flask_images/screen_02.png
Prepare the environment
-----------------------
Let's create the environment for the project.
First we need to create a project folder:
First we need to create a project folder and the virtual environment:
.. code-block:: bash
mkdir ghnav-flask-tutorial
cd ghnav-flask-tutorial
python3 -m venv venv
Now let's create and activate virtual environment:
Now let's activate the virtual environment:
.. code-block:: bash
python3 -m venv venv
. venv/bin/activate
Project layout
@ -110,13 +110,13 @@ You should see something like:
.. code-block:: bash
(venv) $ python -c "import dependency_injector; print(dependency_injector.__version__)"
4.37.0
3.22.0
(venv) $ python -c "import flask; print(flask.__version__)"
2.0.2
1.1.2
*Versions can be different. That's fine.*
Hello World!
Hello world!
------------
Let's create minimal application.
@ -129,29 +129,38 @@ Put next into the ``views.py``:
def index():
return "Hello, World!"
return 'Hello, World!'
Ok, we have the view.
Now let's create a container. Container will keep all of the application components and their dependencies.
Now let's create the main part of our application - the container. Container will keep all of the
application components and their dependencies. First two providers we need to add are
the ``Flask`` application provider and the view provider.
Edit ``containers.py``:
Put next into the ``containers.py``:
.. code-block:: python
"""Containers module."""
"""Application containers module."""
from dependency_injector import containers
from dependency_injector.ext import flask
from flask import Flask
from . import views
class Container(containers.DeclarativeContainer):
...
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
Container is empty for now. We will add the providers in the following sections.
app = flask.Application(Flask, __name__)
Finally we need to create Flask application factory. It will create and configure container
and Flask application. It is traditionally called ``create_app()``.
We will assign ``index`` view to handle user requests to the root ``/`` of our web application.
index_view = flask.View(views.index)
Finally we need to create the Flask application factory. It is traditionally called
``create_app()``. It will create the container. Then it will use the container to create
the Flask application. Last step is to configure the routing - we will assign ``index_view`` from
the container to handle user requests to the root ``/`` of our web application.
Put next into the ``application.py``:
@ -159,21 +168,26 @@ Put next into the ``application.py``:
"""Application module."""
from flask import Flask
from .containers import Container
from . import views
from .containers import ApplicationContainer
def create_app() -> Flask:
container = Container()
def create_app():
"""Create and return Flask application."""
container = ApplicationContainer()
app = Flask(__name__)
app = container.app()
app.container = container
app.add_url_rule("/", "index", views.index)
app.add_url_rule('/', view_func=container.index_view.as_view())
return app
.. note::
Container is the first object in the application.
The container is used to create all other objects.
Ok. Now we're ready to say "Hello, World!".
Do next in the terminal:
@ -223,34 +237,58 @@ and run in the terminal:
.. code-block:: bash
pip install -r requirements.txt
pip install --upgrade -r requirements.txt
Now we need to add ``bootstrap-flask`` extension to the container.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 6,16
"""Application containers module."""
from dependency_injector import containers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from . import views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
index_view = flask.View(views.index)
Let's initialize ``bootstrap-flask`` extension. We will need to modify ``create_app()``.
Edit ``application.py``:
.. code-block:: python
:emphasize-lines: 4,17-18
:emphasize-lines: 13-14
"""Application module."""
from flask import Flask
from flask_bootstrap import Bootstrap
from .containers import Container
from . import views
from .containers import ApplicationContainer
def create_app() -> Flask:
container = Container()
def create_app():
"""Create and return Flask application."""
container = ApplicationContainer()
app = Flask(__name__)
app = container.app()
app.container = container
app.add_url_rule("/", "index", views.index)
bootstrap = Bootstrap()
bootstrap = container.bootstrap()
bootstrap.init_app(app)
app.add_url_rule('/', view_func=container.index_view.as_view())
return app
Now we need to add the templates. For doing this we will need to add the folder ``templates/`` to
@ -280,7 +318,7 @@ Now let's fill in the layout.
Put next into the ``base.html``:
.. code-block:: jinja
.. code-block:: html
<!doctype html>
<html lang="en">
@ -313,7 +351,7 @@ And put something to the index page.
Put next into the ``index.html``:
.. code-block:: jinja
.. code-block:: html
{% extends "base.html" %}
@ -398,13 +436,13 @@ Edit ``views.py``:
def index():
query = request.args.get("query", "Dependency Injector")
limit = request.args.get("limit", 10, int)
query = request.args.get('query', 'Dependency Injector')
limit = request.args.get('limit', 10, int)
repositories = []
return render_template(
"index.html",
'index.html',
query=query,
limit=limit,
repositories=repositories,
@ -416,7 +454,7 @@ Make sure the app is running or use ``flask run`` and open ``http://127.0.0.1:50
You should see:
.. image:: flask-images/screen-01.png
.. image:: flask_images/screen_01.png
Connect to the GitHub
---------------------
@ -439,30 +477,41 @@ and run in the terminal:
.. code-block:: bash
pip install -r requirements.txt
pip install --upgrade -r requirements.txt
Now we need to add Github API client the container. We will need to add two more providers from
the ``dependency_injector.providers`` module:
- ``Factory`` provider. It will create a ``Github`` client.
- ``Configuration`` provider. It will provide an API token and a request timeout for the ``Github`` client.
We will specify the location of the configuration file. The configuration provider will parse
the configuration file when we create a container instance.
- ``Factory`` provider that will create ``Github`` client.
- ``Configuration`` provider that will be used for providing the API token and the request timeout
for the ``Github`` client.
Let's do it.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 3-4,9,11-15
:emphasize-lines: 3,7,19,21-25
"""Containers module."""
"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github
from . import views
class Container(containers.DeclarativeContainer):
config = providers.Configuration(yaml_files=["config.yml"])
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
config = providers.Configuration()
github_client = providers.Factory(
Github,
@ -470,14 +519,20 @@ Edit ``containers.py``:
timeout=config.github.request_timeout,
)
index_view = flask.View(views.index)
.. note::
Don't forget to remove the Ellipsis ``...`` from the container. We don't need it anymore
since we container is not empty.
We have used the configuration value before it was defined. That's the principle how
``Configuration`` provider works.
Now let's add the configuration file. We will use YAML. Create an empty file ``config.yml``
in the root of the project:
Use first, define later.
Now let's add the configuration file.
We will use YAML.
Create an empty file ``config.yml`` in the root root of the project:
.. code-block:: bash
:emphasize-lines: 11
@ -520,36 +575,39 @@ and install it:
.. code-block:: bash
pip install -r requirements.txt
pip install --upgrade -r requirements.txt
We will use the ``GITHUB_TOKEN`` environment variable to provide the API token. Let's edit
``create_app()`` to fetch the token value from it.
We will use environment variable ``GITHUB_TOKEN`` to provide the API token.
Now we need to edit ``create_app()`` to make two things when application starts:
- Load the configuration file the ``config.yml``.
- Load the API token from the ``GITHUB_TOKEN`` environment variable.
Edit ``application.py``:
.. code-block:: python
:emphasize-lines: 12
:emphasize-lines: 9-10
"""Application module."""
from flask import Flask
from flask_bootstrap import Bootstrap
from .containers import Container
from . import views
from .containers import ApplicationContainer
def create_app() -> Flask:
container = Container()
container.config.github.auth_token.from_env("GITHUB_TOKEN")
def create_app():
"""Create and return Flask application."""
container = ApplicationContainer()
container.config.from_yaml('config.yml')
container.config.github.auth_token.from_env('GITHUB_TOKEN')
app = Flask(__name__)
app = container.app()
app.container = container
app.add_url_rule("/", "index", views.index)
bootstrap = Bootstrap()
bootstrap = container.bootstrap()
bootstrap.init_app(app)
app.add_url_rule('/', view_func=container.index_view.as_view())
return app
Now we need create an API token.
@ -578,7 +636,7 @@ Github API client setup is done.
Search service
--------------
Now it's time to add ``SearchService``. It will:
Now it's time to add the ``SearchService``. It will:
- Perform the search.
- Fetch commit extra data for each result.
@ -626,7 +684,7 @@ and put next into it:
"""Search for repositories and return formatted data."""
repositories = self._github_client.search_repositories(
query=query,
**{"in": "name"},
**{'in': 'name'},
)
return [
self._format_repo(repository)
@ -636,22 +694,22 @@ and put next into it:
def _format_repo(self, repository: Repository):
commits = repository.get_commits()
return {
"url": repository.html_url,
"name": repository.name,
"owner": {
"login": repository.owner.login,
"url": repository.owner.html_url,
"avatar_url": repository.owner.avatar_url,
'url': repository.html_url,
'name': repository.name,
'owner': {
'login': repository.owner.login,
'url': repository.owner.html_url,
'avatar_url': repository.owner.avatar_url,
},
"latest_commit": self._format_commit(commits[0]) if commits else {},
'latest_commit': self._format_commit(commits[0]) if commits else {},
}
def _format_commit(self, commit: Commit):
return {
"sha": commit.sha,
"url": commit.html_url,
"message": commit.commit.message,
"author_name": commit.commit.author.name,
'sha': commit.sha,
'url': commit.html_url,
'message': commit.commit.message,
'author_name': commit.commit.author.name,
}
Now let's add ``SearchService`` to the container.
@ -659,19 +717,27 @@ Now let's add ``SearchService`` to the container.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 6,19-22
:emphasize-lines: 9,27-30
"""Containers module."""
"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github
from . import services
from . import services, views
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
config = providers.Configuration(yaml_files=["config.yml"])
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
config = providers.Configuration()
github_client = providers.Factory(
Github,
@ -684,63 +750,64 @@ Edit ``containers.py``:
github_client=github_client,
)
Inject search service into view
-------------------------------
index_view = flask.View(views.index)
Now we are ready to make the search work.
Make the search work
--------------------
Let's inject ``SearchService`` into the ``index`` view. We will use :ref:`Wiring` feature.
Now we are ready to make the search work. Let's use the ``SearchService`` in the ``index`` view.
Edit ``views.py``:
.. code-block:: python
:emphasize-lines: 4,6-7,10-11,15
:emphasize-lines: 5,8,12
"""Views module."""
from flask import request, render_template
from dependency_injector.wiring import inject, Provide
from .services import SearchService
from .containers import Container
@inject
def index(search_service: SearchService = Provide[Container.search_service]):
query = request.args.get("query", "Dependency Injector")
limit = request.args.get("limit", 10, int)
def index(search_service: SearchService):
query = request.args.get('query', 'Dependency Injector')
limit = request.args.get('limit', 10, int)
repositories = search_service.search_repositories(query, limit)
return render_template(
"index.html",
'index.html',
query=query,
limit=limit,
repositories=repositories,
)
To make the injection work we need to wire the container with the ``views`` module.
Let's configure the container to automatically make wiring with the ``views`` module when we
create a container instance.
Now let's inject the ``SearchService`` dependency into the ``index`` view.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 11
:emphasize-lines: 32-35
"""Containers module."""
"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github
from . import services
from . import services, views
class Container(containers.DeclarativeContainer):
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
wiring_config = containers.WiringConfiguration(modules=[".views"])
app = flask.Application(Flask, __name__)
config = providers.Configuration(yaml_files=["config.yml"])
bootstrap = flask.Extension(Bootstrap)
config = providers.Configuration()
github_client = providers.Factory(
Github,
@ -753,11 +820,16 @@ Edit ``containers.py``:
github_client=github_client,
)
index_view = flask.View(
views.index,
search_service=search_service,
)
Make sure the app is running or use ``flask run`` and open ``http://127.0.0.1:5000/``.
You should see:
.. image:: flask-images/screen-02.png
.. image:: flask_images/screen_02.png
Make some refactoring
---------------------
@ -772,35 +844,79 @@ Let's make some refactoring. We will move these values to the config.
Edit ``views.py``:
.. code-block:: python
:emphasize-lines: 11-17
:emphasize-lines: 8-14
"""Views module."""
from flask import request, render_template
from dependency_injector.wiring import inject, Provide
from .services import SearchService
from .containers import Container
@inject
def index(
search_service: SearchService = Provide[Container.search_service],
default_query: str = Provide[Container.config.default.query],
default_limit: int = Provide[Container.config.default.limit.as_int()],
search_service: SearchService,
default_query: str,
default_limit: int,
):
query = request.args.get("query", default_query)
limit = request.args.get("limit", default_limit, int)
query = request.args.get('query', default_query)
limit = request.args.get('limit', default_limit, int)
repositories = search_service.search_repositories(query, limit)
return render_template(
"index.html",
'index.html',
query=query,
limit=limit,
repositories=repositories,
)
Now we need to inject these values. Let's update the container.
Edit ``containers.py``:
.. code-block:: python
:emphasize-lines: 35-36
"""Application containers module."""
from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github
from . import services, views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
config = providers.Configuration()
github_client = providers.Factory(
Github,
login_or_token=config.github.auth_token,
timeout=config.github.request_timeout,
)
search_service = providers.Factory(
services.SearchService,
github_client=github_client,
)
index_view = flask.View(
views.index,
search_service=search_service,
default_query=config.search.default_query,
default_limit=config.search.default_limit,
)
Finally let's update the config.
Edit ``config.yml``:
.. code-block:: yaml
@ -808,18 +924,20 @@ Edit ``config.yml``:
github:
request_timeout: 10
default:
query: "Dependency Injector"
limit: 10
search:
default_query: "Dependency Injector"
default_limit: 10
That's it. The refactoring is done. We've made it cleaner.
That's it.
The refactoring is done. We've made it cleaner.
Tests
-----
In this section we will add some tests.
It would be nice to add some tests. Let's do this.
We will use `pytest <https://docs.pytest.org/en/stable/>`_ with its Flask extension and
We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
`coverage <https://coverage.readthedocs.io/>`_.
Edit ``requirements.txt``:
@ -835,7 +953,7 @@ Edit ``requirements.txt``:
pytest-flask
pytest-cov
And install added packages:
And let's install it:
.. code-block:: bash
@ -864,7 +982,7 @@ Create empty file ``tests.py`` in the ``githubnavigator`` package:
and put next into it:
.. code-block:: python
:emphasize-lines: 44,67
:emphasize-lines: 42,65
"""Tests module."""
@ -879,53 +997,51 @@ and put next into it:
@pytest.fixture
def app():
app = create_app()
yield app
app.container.unwire()
return create_app()
def test_index(client, app):
github_client_mock = mock.Mock(spec=Github)
github_client_mock.search_repositories.return_value = [
mock.Mock(
html_url="repo1-url",
name="repo1-name",
html_url='repo1-url',
name='repo1-name',
owner=mock.Mock(
login="owner1-login",
html_url="owner1-url",
avatar_url="owner1-avatar-url",
login='owner1-login',
html_url='owner1-url',
avatar_url='owner1-avatar-url',
),
get_commits=mock.Mock(return_value=[mock.Mock()]),
),
mock.Mock(
html_url="repo2-url",
name="repo2-name",
html_url='repo2-url',
name='repo2-name',
owner=mock.Mock(
login="owner2-login",
html_url="owner2-url",
avatar_url="owner2-avatar-url",
login='owner2-login',
html_url='owner2-url',
avatar_url='owner2-avatar-url',
),
get_commits=mock.Mock(return_value=[mock.Mock()]),
),
]
with app.container.github_client.override(github_client_mock):
response = client.get(url_for("index"))
response = client.get(url_for('index'))
assert response.status_code == 200
assert b"Results found: 2" in response.data
assert b'Results found: 2' in response.data
assert b"repo1-url" in response.data
assert b"repo1-name" in response.data
assert b"owner1-login" in response.data
assert b"owner1-url" in response.data
assert b"owner1-avatar-url" in response.data
assert b'repo1-url' in response.data
assert b'repo1-name' in response.data
assert b'owner1-login' in response.data
assert b'owner1-url' in response.data
assert b'owner1-avatar-url' in response.data
assert b"repo2-url" in response.data
assert b"repo2-name" in response.data
assert b"owner2-login" in response.data
assert b"owner2-url" in response.data
assert b"owner2-avatar-url" in response.data
assert b'repo2-url' in response.data
assert b'repo2-name' in response.data
assert b'owner2-login' in response.data
assert b'owner2-url' in response.data
assert b'owner2-avatar-url' in response.data
def test_index_no_results(client, app):
@ -933,10 +1049,10 @@ and put next into it:
github_client_mock.search_repositories.return_value = []
with app.container.github_client.override(github_client_mock):
response = client.get(url_for("index"))
response = client.get(url_for('index'))
assert response.status_code == 200
assert b"Results found: 0" in response.data
assert b'Results found: 0' in response.data
Now let's run it and check the coverage:
@ -948,23 +1064,23 @@ You should see:
.. code-block:: bash
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-3.0.0, flask-1.2.0
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
plugins: flask-1.0.0, cov-2.10.0
collected 2 items
githubnavigator/tests.py .. [100%]
---------- coverage: platform darwin, python 3.10.0-final-0 ----------
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
Name Stmts Miss Cover
----------------------------------------------------
githubnavigator/__init__.py 0 0 100%
githubnavigator/application.py 13 0 100%
githubnavigator/containers.py 8 0 100%
githubnavigator/application.py 11 0 100%
githubnavigator/containers.py 13 0 100%
githubnavigator/services.py 14 0 100%
githubnavigator/tests.py 34 0 100%
githubnavigator/views.py 10 0 100%
githubnavigator/tests.py 32 0 100%
githubnavigator/views.py 7 0 100%
----------------------------------------------------
TOTAL 79 0 100%
TOTAL 77 0 100%
.. note::
@ -975,22 +1091,53 @@ You should see:
Conclusion
----------
We are done.
In this tutorial we've built a ``Flask`` application following the dependency injection principle.
We've used the ``Dependency Injector`` as a dependency injection framework.
:ref:`containers` and :ref:`providers` helped to specify how to assemble search service and
integrate it with a 3rd-party library.
The main part of this application is the container. It keeps all the application components and
their dependencies defined explicitly in one place:
:ref:`configuration-provider` helped to deal with reading YAML file and environment variable.
.. code-block:: python
We used :ref:`wiring` feature to inject the dependencies into the ``index()`` view.
:ref:`provider-overriding` feature helped in testing.
"""Application containers module."""
We kept all the dependencies injected explicitly. This will help when you need to add or
change something in future.
from dependency_injector import containers, providers
from dependency_injector.ext import flask
from flask import Flask
from flask_bootstrap import Bootstrap
from github import Github
You can find complete project on the
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
from . import services, views
class ApplicationContainer(containers.DeclarativeContainer):
"""Application container."""
app = flask.Application(Flask, __name__)
bootstrap = flask.Extension(Bootstrap)
config = providers.Configuration()
github_client = providers.Factory(
Github,
login_or_token=config.github.auth_token,
timeout=config.github.request_timeout,
)
search_service = providers.Factory(
services.SearchService,
github_client=github_client,
)
index_view = flask.View(
views.index,
search_service=search_service,
default_query=config.search.default_query,
default_limit=config.search.default_limit,
)
What's next?
@ -998,6 +1145,5 @@ What's next?
- Know more about the :ref:`providers`
- Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus::

View File

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

Before

Width:  |  Height:  |  Size: 647 KiB

After

Width:  |  Height:  |  Size: 647 KiB

View File

@ -1,698 +0,0 @@
.. _wiring:
Wiring
======
Wiring feature provides a way to inject container providers into the functions and methods.
To use wiring you need:
- **Place @inject decorator**. Decorator ``@inject`` injects the dependencies.
- **Place markers**. Wiring marker specifies what dependency to inject,
e.g. ``Provide[Container.bar]``. This helps container to find the injections.
- **Wire the container with the markers in the code**. Call ``container.wire()``
specifying modules and packages you would like to wire it with.
- **Use functions and classes as you normally do**. Framework will provide specified injections.
.. literalinclude:: ../examples/wiring/example.py
:language: python
:lines: 3-
.. contents::
:local:
:backlinks: none
Decorator @inject
-----------------
Decorator ``@inject`` injects the dependencies. Use it to decorate all functions and methods
with the injections.
.. code-block:: python
from dependency_injector.wiring import inject, Provide
@inject
def foo(bar: Bar = Provide[Container.bar]):
...
Decorator ``@inject`` must be specified as a very first decorator of a function to ensure that
the wiring works appropriately. This will also contribute to the performance of the wiring process.
.. code-block:: python
from dependency_injector.wiring import inject, Provide
@decorator_etc
@decorator_2
@decorator_1
@inject
def foo(bar: Bar = Provide[Container.bar]):
...
Specifying the ``@inject`` as a first decorator is also crucial for FastAPI, other frameworks
using decorators similarly, for closures, and for any types of custom decorators with the injections.
FastAPI example:
.. code-block:: python
app = FastAPI()
@app.api_route("/")
@inject
async def index(service: Annotated[Service, Depends(Provide[Container.service])]):
value = await service.process()
return {"result": value}
Decorators example:
.. code-block:: python
def decorator1(func):
@functools.wraps(func)
@inject
def wrapper(value1: int = Provide[Container.config.value1]):
result = func()
return result + value1
return wrapper
def decorator2(func):
@functools.wraps(func)
@inject
def wrapper(value2: int = Provide[Container.config.value2]):
result = func()
return result + value2
return wrapper
@decorator1
@decorator2
def sample():
...
.. seealso::
`Issue #404 <https://github.com/ets-labs/python-dependency-injector/issues/404#issuecomment-785216978>`_
explains ``@inject`` decorator in a few more details.
Markers
-------
Wiring feature uses markers to make injections. Injection marker is specified as a default value of
a function or method argument:
.. code-block:: python
from dependency_injector.wiring import inject, Provide
@inject
def foo(bar: Bar = Provide[Container.bar]):
...
Specifying an annotation is optional.
To inject the provider itself use ``Provide[foo.provider]``:
.. code-block:: python
from dependency_injector.providers import Factory
from dependency_injector.wiring import inject, Provide
@inject
def foo(bar_provider: Factory[Bar] = Provide[Container.bar.provider]):
bar = bar_provider(argument="baz")
...
You can also use ``Provider[foo]`` for injecting the provider itself:
.. code-block:: python
from dependency_injector.providers import Factory
from dependency_injector.wiring import inject, Provider
@inject
def foo(bar_provider: Factory[Bar] = Provider[Container.bar]):
bar = bar_provider(argument="baz")
...
You can use configuration, provided instance and sub-container providers as you normally do.
.. code-block:: python
@inject
def foo(token: str = Provide[Container.config.api_token]):
...
@inject
def foo(timeout: int = Provide[Container.config.timeout.as_(int)]):
...
@inject
def foo(baz: Baz = Provide[Container.bar.provided.baz]):
...
@inject
def foo(bar: Bar = Provide[Container.subcontainer.bar]):
...
You can compound wiring and ``Resource`` provider to implement per-function execution scope.
See :ref:`Resources, wiring and per-function execution scope <resource-provider-wiring-closing>` for details.
Also you can use ``Provide`` marker to inject a container.
.. literalinclude:: ../examples/wiring/example_container.py
:language: python
:emphasize-lines: 14-17
:lines: 3-
String identifiers
------------------
You can use wiring with string identifiers. String identifier should match provider name in the container:
.. literalinclude:: ../examples/wiring/example_string_id.py
:language: python
:emphasize-lines: 15
:lines: 3-
With string identifiers you don't need to use a container to specify an injection.
To specify an injection from a nested container use point ``.`` as a separator:
.. code-block:: python
@inject
def foo(service: UserService = Provide["services.user"]) -> None:
...
You can also use injection modifiers:
.. code-block:: python
from dependency_injector.wiring import (
inject,
Provide,
as_int,
as_float,
as_,
required,
invariant,
provided,
)
@inject
def foo(value: int = Provide["config.option", as_int()]) -> None:
...
@inject
def foo(value: float = Provide["config.option", as_float()]) -> None:
...
@inject
def foo(value: Decimal = Provide["config.option", as_(Decimal)]) -> None:
...
@inject
def foo(value: str = Provide["config.option", required()]) -> None:
...
@inject
def foo(value: int = Provide["config.option", required().as_int()]) -> None:
...
@inject
def foo(value: int = Provide["config.option", invariant("config.switch")]) -> None:
...
@inject
def foo(value: int = Provide["service", provided().foo["bar"].call()]) -> None:
...
To inject a container use special identifier ``<container>``:
.. code-block:: python
@inject
def foo(container: Container = Provide["<container>"]) -> None:
...
Making injections into modules and class attributes
---------------------------------------------------
You can use wiring to make injections into modules and class attributes. Both the classic marker
syntax and the ``Annotated`` form are supported.
Classic marker syntax:
.. code-block:: python
service: Service = Provide[Container.service]
class Main:
service: Service = Provide[Container.service]
Full example of the classic marker syntax:
.. literalinclude:: ../examples/wiring/example_attribute.py
:language: python
:lines: 3-
:emphasize-lines: 14,19
Annotated form (Python 3.9+):
.. code-block:: python
from typing import Annotated
service: Annotated[Service, Provide[Container.service]]
class Main:
service: Annotated[Service, Provide[Container.service]]
Full example of the annotated form:
.. literalinclude:: ../examples/wiring/example_attribute_annotated.py
:language: python
:lines: 3-
:emphasize-lines: 16,21
You could also use string identifiers to avoid a dependency on a container:
.. code-block:: python
:emphasize-lines: 1,6
service: Service = Provide["service"]
class Main:
service: Service = Provide["service"]
Wiring with modules and packages
--------------------------------
To wire a container with the modules you need to call ``container.wire()`` method:
.. code-block:: python
container.wire(
modules=[
"yourapp.module1",
"yourapp.module2",
],
)
Method ``container.wire()`` can resolve relative imports:
.. code-block:: python
# In module "yourapp.main":
container.wire(
modules=[
".module1", # Resolved to: "yourapp.module1"
".module2", # Resolved to: "yourapp.module2"
],
)
You can also manually specify a base package for resolving relative imports with
the ``from_package`` argument:
.. code-block:: python
# In module "yourapp.main":
container.wire(
modules=[
".module1", # Resolved to: "anotherapp.module1"
".module2", # Resolved to: "anotherapp.module2"
],
from_package="anotherapp",
)
Argument ``modules`` can also take already imported modules:
.. code-block:: python
from yourapp import module1, module2
container = Container()
container.wire(modules=[module1, module2])
You can wire container with a package. Container walks recursively over the package modules:
.. code-block:: python
container.wire(
packages=[
"yourapp.package1",
"yourapp.package2",
],
)
Arguments ``modules`` and ``packages`` can be used together.
When wiring is done functions and methods with the markers are patched to provide injections when called.
.. code-block:: python
@inject
def foo(bar: Bar = Provide[Container.bar]):
...
container = Container()
container.wire(modules=[__name__])
foo() # <--- Argument "bar" is injected
Injections are done as keyword arguments.
.. code-block:: python
foo() # Equivalent to:
foo(bar=container.bar())
Context keyword arguments have a priority over injections.
.. code-block:: python
foo(bar=Bar()) # Bar() is injected
To unpatch previously patched functions and methods call ``container.unwire()`` method.
.. code-block:: python
container.unwire()
You can use that in testing to re-create and re-wire a container before each test.
.. code-block:: python
import unittest
class SomeTest(unittest.TestCase):
def setUp(self):
self.container = Container()
self.container.wire(modules=["yourapp.module1", "yourapp.module2"])
self.addCleanup(self.container.unwire)
.. code-block:: python
import pytest
@pytest.fixture
def container():
container = Container()
container.wire(modules=["yourapp.module1", "yourapp.module2"])
yield container
container.unwire()
.. note::
Wiring can take time if you have a large codebase. Consider to persist a container instance and
avoid re-wiring between tests.
.. note::
Python has a limitation on patching individually imported functions. To protect from errors
prefer importing modules to importing individual functions or make sure imports happen
after the wiring:
.. code-block:: python
# Potential error:
from .module import fn
fn()
Instead use next:
.. code-block:: python
# Always works:
from . import module
module.fn()
Wiring configuration
--------------------
You can specify wiring configuration in the container. When wiring configuration is defined,
container will call method ``.wire()`` automatically when you create an instance:
.. code-block:: python
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(
modules=[
"yourapp.module1",
"yourapp.module2",
],
packages=[
"yourapp.package1",
"yourapp.package2",
],
)
...
if __name__ == "__main__":
container = Container() # container.wire() is called automatically
...
You can also use relative imports. Container will resolve them corresponding
to the module of the container class:
.. code-block:: python
# In module "yourapp.container":
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(
modules=[
".module1", # Resolved to: "yourapp.module1"
".module2", # Resolved to: "yourapp.module2"
],
)
)
# In module "yourapp.foo.bar.main":
if __name__ == "__main__":
container = Container() # wire to "yourapp.module1" and "yourapp.module2"
...
To use wiring configuration and call method ``.wire()`` manually, set flag ``auto_wire=False``:
.. code-block:: python
:emphasize-lines: 5
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(
modules=["yourapp.module1"],
auto_wire=False,
)
if __name__ == "__main__":
container = Container() # container.wire() is NOT called automatically
container.wire() # wire to "yourapp.module1"
...
.. _async-injections-wiring:
Asynchronous injections
-----------------------
Wiring feature supports asynchronous injections:
.. code-block:: python
class Container(containers.DeclarativeContainer):
db = providers.Resource(init_async_db_client)
cache = providers.Resource(init_async_cache_client)
@inject
async def main(
db: Database = Provide[Container.db],
cache: Cache = Provide[Container.cache],
):
...
When you call asynchronous function wiring prepares injections asynchronously.
Here is what it does for previous example:
.. code-block:: python
db, cache = await asyncio.gather(
container.db(),
container.cache(),
)
await main(db=db, cache=cache)
You can also use ``Closing`` marker with the asynchronous ``Resource`` providers:
.. code-block:: python
@inject
async def main(
db: Database = Closing[Provide[Container.db]],
cache: Cache = Closing[Provide[Container.cache]],
):
...
Wiring does closing asynchronously:
.. code-block:: python
db, cache = await asyncio.gather(
container.db(),
container.cache(),
)
await main(db=db, cache=cache)
await asyncio.gather(
container.db.shutdown(),
container.cache.shutdown(),
)
See :ref:`Resources, wiring and per-function execution scope <resource-provider-wiring-closing>` for
details on ``Closing`` marker.
.. note::
Wiring does not not convert asynchronous injections to synchronous.
It handles asynchronous injections only for ``async def`` functions. Asynchronous injections into
synchronous ``def`` function still work, but you need to take care of awaitables by your own.
See also:
- Provider :ref:`async-injections`
- Resource provider :ref:`resource-async-initializers`
- :ref:`fastapi-redis-example`
Wiring of dynamically imported modules
--------------------------------------
You can install an import hook that automatically wires containers to the imported modules.
This is useful when you import modules dynamically.
.. code-block:: python
import importlib
from dependency_injector.wiring import register_loader_containers
from .containers import Container
if __name__ == "__main__":
container = Container()
register_loader_containers(container) # <--- installs import hook
module = importlib.import_module("package.module")
module.foo()
You can register multiple containers in the import hook. For doing this call register function
with multiple containers ``register_loader_containers(container1, container2, ...)``
or with a single container ``register_loader_containers(container)`` multiple times.
To unregister a container use ``unregister_loader_containers(container)``.
Wiring module will uninstall the import hook when unregister last container.
Few notes on performance
------------------------
``.wire()`` utilize caching to speed up the wiring process. At the end it clears the cache to avoid memory leaks.
But this may not always be desirable, when you want to keep the cache for the next wiring
(e.g. due to usage of multiple containers or during unit tests).
To keep the cache after wiring, you can set flag ``keep_cache=True`` (works with ``WiringConfiguration`` too):
.. code-block:: python
container1.wire(
modules=["yourapp.module1", "yourapp.module2"],
keep_cache=True,
)
container2.wire(
modules=["yourapp.module2", "yourapp.module3"],
keep_cache=True,
)
...
and then clear it manually when you need it:
.. code-block:: python
from dependency_injector.wiring import clear_cache
clear_cache()
Integration with other frameworks
---------------------------------
Wiring feature helps to integrate with other frameworks like Django, Flask, etc.
With wiring you do not need to change the traditional application structure of your framework.
1. Create a container and put framework-independent components as providers.
2. Place wiring markers in the functions and methods where you want the providers
to be injected (Flask or Django views, Aiohttp or Sanic handlers, etc).
3. Wire the container with the application modules.
4. Run the application.
.. literalinclude:: ../examples/wiring/flask_example.py
:language: python
:lines: 3-
Take a look at other application examples:
- :ref:`application-single-container`
- :ref:`application-multiple-containers`
- :ref:`decoupled-packages`
- :ref:`boto3-example`
- :ref:`django-example`
- :ref:`flask-example`
- :ref:`flask-blueprints-example`
- :ref:`aiohttp-example`
- :ref:`sanic-example`
- :ref:`fastapi-example`
- :ref:`fastapi-redis-example`
- :ref:`fastapi-sqlalchemy-example`
- :ref:`fastdepends-example`
.. disqus::

View File

@ -1,15 +0,0 @@
"""Container dependencies check example."""
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
service1 = providers.Dependency()
service2 = providers.Dependency()
if __name__ == "__main__":
container = Container()
container.check_dependencies() # <-- raises error:
# Container has undefined dependencies: "Container.service1", "Container.service2"

View File

@ -10,7 +10,7 @@ class Container(containers.DeclarativeContainer):
factory2 = providers.Factory(object)
if __name__ == "__main__":
if __name__ == '__main__':
container = Container()
object1 = container.factory1()
@ -18,6 +18,6 @@ if __name__ == "__main__":
print(container.providers)
# {
# "factory1": <dependency_injector.providers.Factory(...),
# "factory2": <dependency_injector.providers.Factory(...),
# 'factory1': <dependency_injector.providers.Factory(...),
# 'factory2': <dependency_injector.providers.Factory(...),
# }

View File

@ -1,31 +0,0 @@
"""Declarative container provider copying with ``@copy()`` decorator."""
import sqlite3
from unittest import mock
from dependency_injector import containers, providers
class Service:
def __init__(self, db):
self.db = db
class SourceContainer(containers.DeclarativeContainer):
database = providers.Singleton(sqlite3.connect, ":memory:")
service = providers.Factory(Service, db=database)
# Copy ``SourceContainer`` providers into ``DestinationContainer``:
@containers.copy(SourceContainer)
class DestinationContainer(SourceContainer):
database = providers.Singleton(mock.Mock)
if __name__ == "__main__":
container = DestinationContainer()
service = container.service()
assert isinstance(service.db, mock.Mock)

View File

@ -1,33 +0,0 @@
"""Declarative container provider copying with ``@copy()`` decorator."""
from dependency_injector import containers, providers
class Service:
def __init__(self, dependency: str):
self.dependency = dependency
class Base(containers.DeclarativeContainer):
dependency = providers.Dependency(instance_of=str, default="Default value")
service = providers.Factory(Service, dependency=dependency)
@containers.copy(Base)
class Derived1(Base):
dependency = providers.Dependency(instance_of=str, default="Derived 1")
# @containers.copy(Base) # <-- No @copy decorator
class Derived2(Base):
dependency = providers.Dependency(instance_of=str, default="Derived 2")
if __name__ == "__main__":
container1 = Derived1()
service1 = container1.service()
print(service1.dependency) # Derived 1
container2 = Derived2()
service2 = container2.service()
print(service2.dependency) # Default value

View File

@ -14,21 +14,21 @@ class ContainerB(ContainerA):
assert ContainerA.providers == {
"provider1": ContainerA.provider1,
'provider1': ContainerA.provider1,
}
assert ContainerB.providers == {
"provider1": ContainerA.provider1,
"provider2": ContainerB.provider2,
'provider1': ContainerA.provider1,
'provider2': ContainerB.provider2,
}
assert ContainerA.cls_providers == {
"provider1": ContainerA.provider1,
'provider1': ContainerA.provider1,
}
assert ContainerB.cls_providers == {
"provider2": ContainerB.provider2,
'provider2': ContainerB.provider2,
}
assert ContainerA.inherited_providers == {}
assert ContainerB.inherited_providers == {
"provider1": ContainerA.provider1,
'provider1': ContainerA.provider1,
}

View File

@ -18,7 +18,7 @@ class AuthService:
class Container(containers.DeclarativeContainer):
database = providers.Singleton(sqlite3.connect, ":memory:")
database = providers.Singleton(sqlite3.connect, ':memory:')
user_service = providers.Factory(
UserService,
@ -32,7 +32,7 @@ class Container(containers.DeclarativeContainer):
)
if __name__ == "__main__":
if __name__ == '__main__':
container = Container()
user_service = container.user_service()

View File

@ -1,25 +0,0 @@
"""Declarative container provider overriding with ``@override()`` decorator."""
import sqlite3
from unittest import mock
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
database = providers.Singleton(sqlite3.connect, ":memory:")
# Overriding ``Container`` with ``OverridingContainer``:
@containers.override(Container)
class OverridingContainer(containers.DeclarativeContainer):
database = providers.Singleton(mock.Mock)
if __name__ == "__main__":
container = Container()
database = container.database()
assert isinstance(database, mock.Mock)

View File

@ -8,10 +8,10 @@ from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
database = providers.Singleton(sqlite3.connect, ":memory:")
database = providers.Singleton(sqlite3.connect, ':memory:')
if __name__ == "__main__":
if __name__ == '__main__':
container = Container(database=mock.Mock(sqlite3.Connection))
database = container.database()

View File

@ -3,7 +3,7 @@
from dependency_injector import containers, providers
if __name__ == "__main__":
if __name__ == '__main__':
container = containers.DynamicContainer()
container.factory1 = providers.Factory(object)
container.factory2 = providers.Factory(object)
@ -13,6 +13,6 @@ if __name__ == "__main__":
print(container.providers)
# {
# "factory1": <dependency_injector.providers.Factory(...),
# "factory2": <dependency_injector.providers.Factory(...),
# 'factory1': <dependency_injector.providers.Factory(...),
# 'factory2': <dependency_injector.providers.Factory(...),
# }

View File

@ -13,20 +13,20 @@ class AuthService:
def populate_container(container, providers_config):
for provider_name, provider_info in providers_config.items():
provided_cls = globals().get(provider_info["class"])
provider_cls = getattr(providers, provider_info["provider_class"])
provided_cls = globals().get(provider_info['class'])
provider_cls = getattr(providers, provider_info['provider_class'])
setattr(container, provider_name, provider_cls(provided_cls))
if __name__ == "__main__":
if __name__ == '__main__':
services_config = {
"user": {
"class": "UserService",
"provider_class": "Factory",
'user': {
'class': 'UserService',
'provider_class': 'Factory',
},
"auth": {
"class": "AuthService",
"provider_class": "Factory",
'auth': {
'class': 'AuthService',
'provider_class': 'Factory',
},
}
services = containers.DynamicContainer()

View File

@ -1,36 +0,0 @@
"""Container injecting ``self`` example."""
from dependency_injector import containers, providers
class Service:
def __init__(self, name: str):
self.name = name
class ServiceDispatcher:
def __init__(self, container: containers.Container):
self.container = container
def get_services(self):
for provider in self.container.traverse(types=[providers.Factory]):
yield provider()
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
service1 = providers.Factory(Service, name="Service 1")
service2 = providers.Factory(Service, name="Service 2")
service3 = providers.Factory(Service, name="Service 3")
dispatcher = providers.Singleton(ServiceDispatcher, __self__)
if __name__ == "__main__":
container = Container()
dispatcher = container.dispatcher()
for service in dispatcher.get_services():
print(service.name)

Some files were not shown because too many files have changed in this diff Show More