Compare commits

..

No commits in common. "master" and "3.36.0" have entirely different histories.

686 changed files with 118901 additions and 34487 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/ parts/
sdist/ sdist/
var/ var/
wheelhouse/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
*.egg *.egg
@ -37,7 +36,6 @@ reports/
.cache .cache
nosetests.xml nosetests.xml
coverage.xml coverage.xml
.hypothesis/
# Translations # Translations
*.mo *.mo
@ -56,7 +54,7 @@ target/
.idea/ .idea/
# Virtualenv # Virtualenv
venv*/ venv/
# SQLite # SQLite
*.db *.db
@ -64,13 +62,10 @@ venv*/
# Vim Rope # Vim Rope
.ropeproject/ .ropeproject/
# Cython artifacts # C extensions
src/**/*.c src/dependency_injector/*.h
src/**/*.h src/dependency_injector/*.so
src/**/*.so src/dependency_injector/containers/*.h
src/**/*.html src/dependency_injector/containers/*.so
src/dependency_injector/providers/*.h
# Workspace for samples src/dependency_injector/providers/*.so
.workspace/
.vscode/

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) + Bruno P. Kinoshita (kinow)
+ RobinsonMa (RobinsonMa) + RobinsonMa (RobinsonMa)
+ Rüdiger Busche (JarnoRFB) + 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. All rights reserved.
Redistribution and use in source and binary forms, with or without 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 recursive-include tests *.py
include README.rst include README.rst
include CONTRIBUTORS.rst include CONTRIBUTORS.rst
include LICENSE.rst include LICENSE.rst
include requirements.txt
include setup.py include setup.py
include tox.ini include tox.ini
include py.typed

View File

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

View File

@ -35,10 +35,14 @@
:target: https://pypi.org/project/dependency-injector/ :target: https://pypi.org/project/dependency-injector/
:alt: Wheel :alt: Wheel
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master .. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
:target: https://github.com/ets-labs/python-dependency-injector/actions :target: https://travis-ci.org/ets-labs/python-dependency-injector
:alt: Build Status :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 .. 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 :target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
:alt: Coverage Status :alt: Coverage Status
@ -48,39 +52,97 @@ What is ``Dependency Injector``?
``Dependency Injector`` is a dependency injection framework for Python. ``Dependency Injector`` is a dependency injection framework for Python.
It helps implement the dependency injection principle. It helps you in implementing the dependency injection principle.
Key features of the ``Dependency Injector``: What is dependency injection?
-----------------------------
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``, Dependency injection is a principle that helps to decrease coupling and increase cohesion. Your
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers code becomes more flexible, clear and it is easier to test it.
that help assemble your objects.
See `Providers <https://python-dependency-injector.ets-labs.org/providers/index.html>`_. How to implement dependency injection?
- **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>`_. Objects do not create each other anymore. They provide a way to inject the needed dependencies
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings, instead.
environment variables, and dictionaries.
See `Configuration provider <https://python-dependency-injector.ets-labs.org/providers/configuration.html>`_. Before:
- **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. .. code-block:: python
See `Resource provider <https://python-dependency-injector.ets-labs.org/providers/resource.html>`_.
- **Containers**. Provides declarative and dynamic containers. import os
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. class ApiClient:
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
- **Asynchronous**. Supports asynchronous injections. def __init__(self):
See `Asynchronous injections <https://python-dependency-injector.ets-labs.org/providers/async.html>`_. self.api_key = os.getenv('API_KEY')
- **Typing**. Provides typing stubs, ``mypy``-friendly. self.timeout = os.getenv('TIMEOUT')
See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
- **Performance**. Fast. Written in ``Cython``.
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported. class Service:
def __init__(self):
self.api_client = ApiClient()
if __name__ == '__main__':
service = Service()
After:
.. code-block:: python
import os
class ApiClient:
def __init__(self, api_key: str, timeout: int):
self.api_key = api_key
self.timeout = timeout
class Service:
def __init__(self, api_client: ApiClient):
self.api_client = api_client
if __name__ == '__main__':
service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))
Flexibility comes with a price: now you need to assemble your objects like this
``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.
What does Dependency Injector do?
---------------------------------
``Dependency Injector`` helps you assemble the objects.
It provides you the container and the providers that help you describe 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 .. code-block:: python
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class ApiClient:
def __init__(self, api_key: str, timeout: int):
self.api_key = api_key
self.timeout = timeout
class Service:
def __init__(self, api_client: ApiClient):
self.api_client = api_client
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -90,7 +152,7 @@ Key features of the ``Dependency Injector``:
api_client = providers.Singleton( api_client = providers.Singleton(
ApiClient, ApiClient,
api_key=config.api_key, api_key=config.api_key,
timeout=config.timeout, timeout=config.timeout.as_int(),
) )
service = providers.Factory( service = providers.Factory(
@ -99,40 +161,31 @@ Key features of the ``Dependency Injector``:
) )
@inject if __name__ == '__main__':
def main(service: Service = Provide[Container.service]) -> None:
...
if __name__ == "__main__":
container = Container() container = Container()
container.config.api_key.from_env("API_KEY", required=True) container.config.api_key.from_env('API_KEY')
container.config.timeout.from_env("TIMEOUT", as_=int, default=5) container.config.timeout.from_env('TIMEOUT')
container.wire(modules=[__name__])
main() # <-- dependency is injected automatically service = container.service()
with container.api_client.override(mock.Mock()): Retrieving of the ``Service`` instance now is done like this ``container.service()``.
main() # <-- overridden dependency is injected automatically
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically. Also ``Dependency Injector`` provides a bonus in overriding any of the providers with the
``.override()`` method:
When you do testing, you call the ``container.api_client.override()`` method to replace the real API .. code-block:: python
client with a mock. When you call ``main()``, the mock is injected.
You can override any provider with another provider. from unittest import mock
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. with container.api_client.override(mock.Mock()):
This makes it easier to understand and change how an application works. service = container.service()
assert isinstance(service.api_client, mock.Mock)
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg It helps in a testing. Also you can use it for configuring project for the different environments:
:target: https://github.com/ets-labs/python-dependency-injector replace an API client with a stub on the dev or stage.
Visit the docs to know more about the `More examples <https://github.com/ets-labs/python-dependency-injector/tree/master/examples>`_
`Dependency injection and inversion of control in Python <https://python-dependency-injector.ets-labs.org/introduction/di_in_python.html>`_.
Installation Installation
------------ ------------
@ -144,67 +197,74 @@ The package is available on the `PyPi`_::
Documentation 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>`_
Tutorials Tutorials
--------- ---------
Choose one of the following: Choose one of the following:
- `Flask web application tutorial <https://python-dependency-injector.ets-labs.org/tutorials/flask.html>`_ - `Flask web application tutorial <http://python-dependency-injector.ets-labs.org/tutorials/flask.html>`_
- `Aiohttp REST API tutorial <https://python-dependency-injector.ets-labs.org/tutorials/aiohttp.html>`_ - `Aiohttp REST API tutorial <http://python-dependency-injector.ets-labs.org/tutorials/aiohttp.html>`_
- `Asyncio monitoring daemon tutorial <https://python-dependency-injector.ets-labs.org/tutorials/asyncio-daemon.html>`_ - `Asyncio monitoring daemon tutorial <http://python-dependency-injector.ets-labs.org/tutorials/asyncio-daemon.html>`_
- `CLI application tutorial <https://python-dependency-injector.ets-labs.org/tutorials/cli.html>`_ - `CLI application tutorial <http://python-dependency-injector.ets-labs.org/tutorials/cli.html>`_
Concept 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 does 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`` makes a simple contract with you:
``Dependency Injector`` is a simple tool for the powerful concept.
- 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 Frequently asked questions
-------------------------- --------------------------
What is dependency injection? What is the dependency injection?
- dependency injection is a principle that decreases coupling and increases cohesion - dependency injection is a principle that decreases coupling and increases cohesion
Why should I do the dependency injection? 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 start writing the code following the dependency injection principle
- you register all of your application components and their dependencies in the container - 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? 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 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? Have a question?
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_ - 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:
:special-members:
.. disqus::

View File

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

View File

@ -3,7 +3,7 @@ dependency_injector.containers
.. automodule:: dependency_injector.containers .. automodule:: dependency_injector.containers
:members: :members:
:inherited-members: :special-members:
:show-inheritance:
.. disqus:: .. disqus::

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

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

View File

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

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 :language: python
:lines: 3- :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 .. literalinclude:: ../../examples/containers/declarative_override_providers.py
:language: python :language: python
:lines: 3- :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:: .. disqus::

View File

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

View File

@ -21,20 +21,4 @@ The container also has:
:py:class:`DynamicContainer` has the same functionality. :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:: .. 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

@ -1,68 +0,0 @@
Chained Factories pattern
=========================
This example demonstrates "Chained Factories" pattern.
The idea of the pattern is in wrapping ``Factory`` into another ``Factory`` that adds
additional arguments.
.. code-block:: python
base_factory = providers.Factory(
SomeClass,
base_argument=1,
)
concrete_factory = providers.Factory(
base_factory,
extra_argument=2,
)
if __name__ == "__main__":
instance = concrete_factory()
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
Sample code
-----------
Listing of the pattern example:
.. literalinclude:: ../../examples/miniapps/factory-patterns/chained_factories.py
:language: python
Arguments priority
------------------
Passing of the arguments works the same way like for any other :ref:`factory-provider`.
.. code-block:: python
# 1. Keyword arguments of upper level factory are added to lower level factory
chained_dict_factory = providers.Factory(
providers.Factory(dict, 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}
# 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}
Credits
-------
The "Chained Factories" pattern was suggested by the ``Dependency Injector`` users.
.. disqus::

View File

@ -1,74 +0,0 @@
Factory of Factories pattern
============================
This example demonstrates "Factory of Factories" pattern.
The idea of the pattern is in creating a ``Factory`` that creates another ``Factory`` and adds
additional arguments.
.. code-block:: python
base_factory = providers.Factory(
providers.Factory
SomeClass,
base_argument=1,
)
concrete_factory = providers.Factory(
OtherClass,
instance=base_factory(extra_argument=1),
)
if __name__ == "__main__":
instance = concrete_factory()
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
Sample code
-----------
Listing of the pattern example:
.. literalinclude:: ../../examples/miniapps/factory-patterns/factory_of_factories.py
:language: python
Arguments priority
------------------
Passing of the arguments works the same way like for any other :ref:`factory-provider`.
.. code-block:: python
# 1. Keyword arguments of upper level factory are added to lower level factory
factory_of_dict_factories = providers.Factory(
providers.Factory,
dict,
arg1=1,
)
dict_factory = factory_of_dict_factories(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(
providers.Factory,
dict,
arg1=1,
)
dict_factory = factory_of_dict_factories(arg1=2)
print(dict_factory()) # prints: {"arg1": 2}
# 3. Keyword arguments provided from context have the most priority
factory_of_dict_factories = providers.Factory(
providers.Factory,
dict,
arg1=1,
)
dict_factory = factory_of_dict_factories(arg1=2)
print(dict_factory(arg1=3)) # prints: {"arg1": 3}
Credits
-------
The "Factory of Factories" pattern was suggested by the ``Dependency Injector`` users.
.. disqus::

View File

@ -1,17 +0,0 @@
Other examples
==============
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework
:description: This sections contains assorted Dependency Injector examples.
This sections contains assorted ``Dependency Injector`` examples.
.. toctree::
:maxdepth: 2
use-cases
password-hashing
chained-factories
factory-of-factories

View File

@ -1,30 +0,0 @@
Password hashing example
========================
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework,Callable
:description: This example demonstrates a usage of the Callable provider.
This example demonstrates an injection of the ``Callable`` provider.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/password-hashing>`_.
Sample code
-----------
Listing of the pattern example:
.. literalinclude:: ../../examples/miniapps/password-hashing/example.py
:language: python
Run the example
---------------
Instructions for running:
.. code-block:: bash
python example.py
.. disqus::

View File

@ -1,74 +0,0 @@
Use cases example
=================
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework,DependenciesContainer
:description: This example demonstrates a usage of the DependenciesContainer provider.
This example demonstrates a usage of the ``DependenciesContainer`` provider.
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
Application structure
---------------------
Example application has next structure:
.. code-block:: bash
./
└── example/
├── __init__.py
├── __main__.py
├── adapters.py
├── containers.py
└── usecases.py
Containers
----------
Listing of the ``example/containers.py``:
.. literalinclude:: ../../examples/miniapps/use-cases/example/containers.py
:language: python
Main module
-----------
Listing of the ``example/__main__.py``:
.. literalinclude:: ../../examples/miniapps/use-cases/example/__main__.py
:language: python
Run the application
-------------------
Instructions for running in the "test" mode:
.. code-block:: bash
python run.py test example@example.com
Instructions for running in the "prod" mode:
.. code-block:: bash
python run.py prod example@example.com
Adapters and use cases
----------------------
Listing of the ``example/adapters.py``:
.. literalinclude:: ../../examples/miniapps/use-cases/example/adapters.py
:language: python
Listing of the ``example/usecases.py``:
.. literalinclude:: ../../examples/miniapps/use-cases/example/usecases.py
:language: python
.. disqus::

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

@ -1,89 +0,0 @@
.. _application-multiple-containers:
Application example (multiple containers)
=========================================
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework,AWS,boto3,client
:description: This example shows how you can create an application using multiple declarative
containers. We build an example Python micro application following the dependency
injection principle. It consists from several services with a domain logic that
have dependencies on database & AWS S3.
This example shows how you can create an application using multiple declarative containers. Using
multiple declarative containers is a good choice for a large application. For
building a moderate or a small size application refer to :ref:`application-single-container`.
We build an example micro application following the dependency injection principle. It consists
of several services with a domain logic. The services have dependencies on database & AWS S3.
.. image:: images/application.png
:width: 100%
:align: center
Start from the scratch or jump to the section:
.. contents::
:local:
:backlinks: none
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>`_.
Application structure
---------------------
Application consists of an ``example`` package, a configuration file and a ``requirements.txt``
file.
.. code-block:: bash
./
├── example/
│ ├── __init__.py
│ ├── __main__.py
│ ├── containers.py
│ └── services.py
├── config.yml
└── requirements.txt
Containers
----------
Listing of the ``example/containers.py``:
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/containers.py
:language: python
Main module
-----------
Listing of the ``example/__main__.py``:
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/__main__.py
:language: python
Services
--------
Listing of the ``example/services.py``:
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/services.py
:language: python
Configuration
-------------
Listing of the ``config.yml``:
.. literalinclude:: ../../examples/miniapps/application-multiple-containers/config.yml
:language: yaml
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

@ -1,95 +0,0 @@
.. _application-single-container:
Application example (single container)
======================================
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework,AWS,boto3,client
:description: This example shows how you can create an application using a single declarative
container. We build an example Python micro application following the dependency
injection principle. It consists from several services with a domain logic that
have dependencies on database & AWS S3.
This example shows how you can create an application using a single declarative container. Using
a single declarative container is a good choice for small or moderate size application. For
building a large application refer to :ref:`application-multiple-containers`.
We build an example micro application following the dependency injection principle. It consists
of several services with a domain logic. The services have dependencies on database & AWS S3.
.. image:: images/application.png
:width: 100%
:align: center
Start from the scratch or jump to the section:
.. contents::
:local:
:backlinks: none
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>`_.
Application structure
---------------------
Application consists of an ``example`` package, several configuration files and a
``requirements.txt`` file.
.. code-block:: bash
./
├── example/
│ ├── __init__.py
│ ├── __main__.py
│ ├── containers.py
│ └── services.py
├── config.ini
├── logging.ini
└── requirements.txt
Container
---------
Listing of the ``example/containers.py``:
.. literalinclude:: ../../examples/miniapps/application-single-container/example/containers.py
:language: python
Main module
-----------
Listing of the ``example/__main__.py``:
.. literalinclude:: ../../examples/miniapps/application-single-container/example/__main__.py
:language: python
Services
--------
Listing of the ``example/services.py``:
.. literalinclude:: ../../examples/miniapps/application-single-container/example/services.py
:language: python
Configuration
-------------
Listing of the ``config.ini``:
.. literalinclude:: ../../examples/miniapps/application-single-container/config.ini
:language: ini
Listing of the ``logging.ini``:
.. literalinclude:: ../../examples/miniapps/application-single-container/logging.ini
:language: ini
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

@ -0,0 +1,74 @@
Bundles mini application example
--------------------------------
.. currentmodule:: dependency_injector.containers
"Bundles" is an example mini application that is intended to demonstrate the
power of dependency injection for creation of re-usable application components
("bundles") with 100% transparency of their dependencies.
Example application
~~~~~~~~~~~~~~~~~~~
"Bundles" mini application has next structure:
.. code-block:: bash
bundles/
bundles/ <-- Bundles package
photos/ <-- Photos bundle
__init__.py <-- Photos bundle dependency injection container
entities.py
repositories.py
users/ <-- Users bundle
__init__.py <-- Users bundle dependency injection container
entities.py
repositories.py
run.py <-- Entrypoint
IoC containers
~~~~~~~~~~~~~~
Next two listings show :py:class:`DeclarativeContainer`'s for "users" and
"photos" bundles.
Listing of ``bundles/users/__init__.py``:
.. literalinclude:: ../../examples/miniapps/bundles/bundles/users/__init__.py
:language: python
.. note::
- ``Users`` container has dependency on database.
Listing of ``bundles/photos/__init__.py``:
.. literalinclude:: ../../examples/miniapps/bundles/bundles/photos/__init__.py
:language: python
.. note::
- ``Photos`` container has dependencies on database and file storage.
Run application
~~~~~~~~~~~~~~~
Finally, both "bundles" are initialized by providing needed dependencies.
Initialization of dependencies happens right in the runtime, not earlier.
Generally, it means, that any part of any bundle could be overridden on the
fly.
Listing of ``run.py``:
.. literalinclude:: ../../examples/miniapps/bundles/run.py
:language: python
Links
~~~~~
+ `Dependency Injector <https://github.com/ets-labs/python-dependency-injector/>`_
+ `Full example sources <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/bundles>`_
.. disqus::

View File

@ -0,0 +1,21 @@
Chained Factories pattern
=========================
This example demonstrate implementation of "Chained Factories" pattern.
Main idea of this pattern is about wrapping :py:class:`Factory` into
another :py:class:`Factory` that mix additional arguments or keyword
arguments to a wrapped one.
Listing of ``data.py``, demonstrates sample classes structure:
.. literalinclude:: ../../examples/miniapps/factory_patterns/data.py
:language: python
Listing of ``chained_factories.py``, demonstrates "Chained Factories"
pattern and provide some explanation:
.. literalinclude:: ../../examples/miniapps/factory_patterns/chained_factories.py
:language: python
.. disqus::

View File

@ -1,134 +0,0 @@
.. _decoupled-packages:
Decoupled packages example (multiple containers)
================================================
.. meta::
:keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application,
Framework,AWS,boto3,client
:description: This example shows how to use Dependency Injector to create Python decoupled packages.
To achieve a decoupling each package has a container with the components. When
a component needs a dependency from the outside of the package scope we use the
Dependency provider. The package container has no knowledge on where the
dependencies come from. It states a need that the dependencies must be provided.
This helps to decouple a package from the 3rd party dependencies and other
packages.
This example shows how to use ``Dependency Injector`` to create decoupled packages.
To achieve a decoupling each package has a container with the components. When a component needs a
dependency from the outside of the package scope we use the ``Dependency`` provider. The package
container has no knowledge on where the dependencies come from. It states a need that the
dependencies must be provided. This helps to decouple a package from the 3rd party dependencies
and other packages.
To wire the packages we use an application container. Application container has all 3rd party
dependencies and package containers. It wires the packages and dependencies to create a
complete application.
We build an example micro application that consists of 3 packages:
- ``user`` - a package with user domain logic, depends on a database
- ``photo`` - a package with photo domain logic, depends on a database and AWS S3
- ``analytics`` - a package with analytics domain logic, depends on the ``user`` and ``photo``
package components
.. image:: images/decoupled-packages.png
:width: 100%
:align: center
Start from the scratch or jump to the section:
.. contents::
:local:
:backlinks: none
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>`_.
Application structure
---------------------
Application consists of an ``example`` package, a configuration file and a ``requirements.txt``
file.
.. code-block:: bash
./
├── example/
│ ├── analytics/
│ │ ├── __init__.py
│ │ ├── containers.py
│ │ └── services.py
│ ├── photo/
│ │ ├── __init__.py
│ │ ├── containers.py
│ │ ├── entities.py
│ │ └── repositories.py
│ ├── user/
│ │ ├── __init__.py
│ │ ├── containers.py
│ │ ├── entities.py
│ │ └── repositories.py
│ ├── __init__.py
│ ├── __main__.py
│ └── containers.py
├── config.ini
└── requirements.txt
Package containers
------------------
Listing of the ``example/user/containers.py``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/user/containers.py
:language: python
Listing of the ``example/photo/containers.py``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/photo/containers.py
:language: python
Listing of the ``example/analytics/containers.py``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/analytics/containers.py
:language: python
Application container
---------------------
Application container consists of all packages and 3rd party dependencies. Its role is to wire
everything together in a complete application.
Listing of the ``example/containers.py``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/containers.py
:language: python
.. note::
Package ``analytics`` has dependencies on the repositories from the ``user`` and
``photo`` packages. This is an example of how you can pass the dependencies from one package
to another.
Main module
-----------
Listing of the ``example/__main__.py``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/__main__.py
:language: python
Configuration
-------------
Listing of the ``config.ini``:
.. literalinclude:: ../../examples/miniapps/decoupled-packages/config.ini
:language: ini
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

@ -0,0 +1,20 @@
Factory of Factories pattern
============================
This example demonstrate implementation of "Factory of Factories" pattern.
Main idea of this pattern is about creation of a :py:class:`Factory` that
creates another :py:class:`Factory` and mix additional arguments to it.
Listing of ``data.py``, demonstrates sample classes structure:
.. literalinclude:: ../../examples/miniapps/factory_patterns/data.py
:language: python
Listing of ``factory_of_factories.py``, demonstrates "Chained Factories"
pattern and provide some explanation:
.. literalinclude:: ../../examples/miniapps/factory_patterns/factory_of_factories.py
:language: python
.. 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: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

View File

@ -2,26 +2,23 @@ Examples
======== ========
.. meta:: .. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Example :keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: Python dependency injection examples. :description: Current section of documentation is designed to provide
several example mini applications that are built on the top
of inversion of control principle and powered by
"Dependency Injector" framework.
Explore the examples to see the ``Dependency Injector`` in action. Current section of documentation is designed to provide several example mini
applications that are built according to the inversion of control principle
and powered by *Dependency Injector* framework.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
application-single-container services_miniapp_v1
application-multiple-containers services_miniapp_v2
decoupled-packages bundles_miniapp
boto3 use_cases_miniapp
django password_hashing_miniapp
flask chained_factories
flask-blueprints factory_of_factories
aiohttp
sanic
fastapi
fastapi-redis
fastapi-sqlalchemy
fastdepends
.. disqus::

View File

@ -0,0 +1,18 @@
Dependency injection and password hashing in Python
===================================================
Small example that demonstrates using of dependency injection for user
password hashing.
Instructions for running:
.. code-block:: bash
python example.py
Listing of ``example.py``:
.. literalinclude:: ../../examples/miniapps/password_hashing/example.py
:language: python
.. 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::

View File

@ -0,0 +1,73 @@
Services mini application example (v1 - multiple containers)
------------------------------------------------------------
.. meta::
:description: "Services miniapp" is an example mini application that
consists from several services that have dependencies on
some standard and 3rd-party libraries for logging,
interaction with database and remote service via API.
"Services miniapp" example demonstrates usage of
Dependency Injector for creating several inversion of control /
dependency injection containers.
"Services miniapp" is an example mini application that consists from several
services that have dependencies on some standard and 3rd-party libraries for
logging, interaction with database and remote service calls via API.
"Services miniapp" example demonstrates usage of
:doc:`Dependency Injector <../index>` for creating several IoC containers.
Instructions for running:
.. code-block:: bash
python run.py 1 secret photo.jpg
Example application
~~~~~~~~~~~~~~~~~~~
Classes diagram:
.. image:: /images/miniapps/services/classes.png
:width: 100%
:align: center
Example application structure:
.. code-block:: bash
/example
/__init__.py
/main.py
/services.py
Listing of ``example/services.py``:
.. literalinclude:: ../../examples/miniapps/services_v1/example/services.py
:language: python
Listing of ``example/main.py``:
.. literalinclude:: ../../examples/miniapps/services_v1/example/main.py
:language: python
IoC containers
~~~~~~~~~~~~~~
Listing of ``containers.py``:
.. literalinclude:: ../../examples/miniapps/services_v1/containers.py
:language: python
Run application
~~~~~~~~~~~~~~~
Listing of ``run.py``:
.. literalinclude:: ../../examples/miniapps/services_v1/run.py
:language: python
.. disqus::

View File

@ -0,0 +1,73 @@
Services mini application example (v2 - single container)
---------------------------------------------------------
.. meta::
:description: "Services miniapp" is an example mini application that
consists from several services that have dependencies on
some standard and 3rd-party libraries for logging,
interaction with database and remote service via API.
"Services miniapp" example demonstrates usage of
Dependency Injector for creating inversion of control /
dependency injection container.
"Services miniapp" is an example mini application that consists from several
services that have dependencies on some standard and 3rd-party libraries for
logging, interaction with database and remote service calls via API.
"Services miniapp" example demonstrates usage of
:doc:`Dependency Injector <../index>` for creating IoC container.
Instructions for running:
.. code-block:: bash
python run.py 1 secret photo.jpg
Example application
~~~~~~~~~~~~~~~~~~~
Classes diagram:
.. image:: /images/miniapps/services/classes.png
:width: 100%
:align: center
Example application structure:
.. code-block:: bash
/example
/__init__.py
/main.py
/services.py
Listing of ``example/services.py``:
.. literalinclude:: ../../examples/miniapps/services_v2/example/services.py
:language: python
Listing of ``example/main.py``:
.. literalinclude:: ../../examples/miniapps/services_v2/example/main.py
:language: python
IoC container
~~~~~~~~~~~~~
Listing of ``container.py``:
.. literalinclude:: ../../examples/miniapps/services_v2/container.py
:language: python
Run application
~~~~~~~~~~~~~~~
Listing of ``run.py``:
.. literalinclude:: ../../examples/miniapps/services_v2/run.py
:language: python
.. disqus::

View File

@ -0,0 +1,55 @@
Use cases mini application example
----------------------------------
.. currentmodule:: dependency_injector.providers
"Use cases" miniapp demonstrate usage of :py:class:`DependenciesContainer`
provider.
Example application
~~~~~~~~~~~~~~~~~~~
"Use cases" mini application has next structure:
.. code-block:: bash
use_cases/
example/ <-- Example package
__init__.py
adapters.py
use_cases.py
containers.py <-- Dependency injection containers
run.py <-- Entrypoint
IoC containers
~~~~~~~~~~~~~~
Listing of ``use_cases/containers.py``:
.. literalinclude:: ../../examples/miniapps/use_cases/containers.py
:language: python
Run application
~~~~~~~~~~~~~~~
Listing of ``run.py``:
.. literalinclude:: ../../examples/miniapps/use_cases/run.py
:language: python
Instructions for running:
.. code-block:: bash
python run.py prod example@example.com # Running in "production" environment
python run.py test example@example.com # Running in "testing" environment
Links
~~~~~
+ `Dependency Injector <https://github.com/ets-labs/python-dependency-injector/>`_
+ `Full example sources <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/use_cases>`_
.. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/images/internals.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -9,11 +9,11 @@ Dependency Injector --- Dependency injection framework for Python
:description: Dependency Injector is a dependency injection framework :description: Dependency Injector is a dependency injection framework
for Python. It helps to maintain you application structure. for Python. It helps to maintain you application structure.
It was designed to be unified, developer-friendly It was designed to be unified, developer-friendly
tool that helps to implement dependency injection design tool that helps to implement dependency injection design
pattern in formal, pretty, Pythonic way. Dependency Injector pattern in formal, pretty, Pythonic way. Dependency Injector
provides implementations of such popular design patterns provides implementations of such popular design patterns
like IoC container, Factory and Singleton. Dependency like IoC container, Factory and Singleton. Dependency
Injector providers are implemented as C extension types Injector providers are implemented as C extension types
using Cython. using Cython.
.. _index: .. _index:
@ -34,15 +34,15 @@ Dependency Injector --- Dependency injection framework for Python
:target: https://pypi.org/project/dependency-injector/ :target: https://pypi.org/project/dependency-injector/
:alt: Supported Python implementations :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 :target: https://pepy.tech/project/dependency-injector
:alt: Downloads :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 :target: https://pepy.tech/project/dependency-injector
:alt: Downloads :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 :target: https://pepy.tech/project/dependency-injector
:alt: Downloads :alt: Downloads
@ -50,84 +50,47 @@ Dependency Injector --- Dependency injection framework for Python
:target: https://pypi.org/project/dependency-injector/ :target: https://pypi.org/project/dependency-injector/
:alt: Wheel :alt: Wheel
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master .. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
:target: https://github.com/ets-labs/python-dependency-injector/actions :target: https://travis-ci.org/ets-labs/python-dependency-injector
:alt: Build Status :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 .. 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 :target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
:alt: Coverage Status :alt: Coverage Status
``Dependency Injector`` is a dependency injection framework for Python. ``Dependency Injector`` is a dependency injection framework for Python.
It helps implementing the dependency injection principle. It stands on two principles:
Key features of the ``Dependency Injector``: - Explicit is better than implicit (PEP20).
- Do no magic to your code.
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``, How does it different from the other frameworks?
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
that help assemble 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
: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`.
- **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.
.. code-block:: python - **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.
from dependency_injector import containers, providers ``Dependency Injector`` makes a simple contract with you:
from dependency_injector.wiring import Provide, inject
- You tell the framework how to assemble your objects
- The framework does it for you
class Container(containers.DeclarativeContainer): The power of the ``Dependency Injector`` is in its simplicity and straightforwardness. It is a simple tool for the powerful concept.
config = providers.Configuration() 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.
api_client = providers.Singleton( .. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-map.svg
ApiClient,
api_key=config.api_key,
timeout=config.timeout,
)
service = providers.Factory(
Service,
api_client=api_client,
)
@inject
def main(service: Service = Provide[Container.service]) -> None:
...
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__])
main() # <-- dependency is injected automatically
with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically
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.
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
:target: https://github.com/ets-labs/python-dependency-injector :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``. Explore the documentation to know more about the ``Dependency Injector``.
.. _contents: .. _contents:
@ -135,16 +98,15 @@ Explore the documentation to know more about the ``Dependency Injector``.
Contents Contents
-------- --------
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
introduction/index introduction/index
examples/index main/installation
tutorials/index tutorials/index
providers/index providers/index
containers/index containers/index
wiring examples/index
examples-other/index api/index
api/index main/feedback
main/feedback main/changelog
main/changelog

View File

@ -1,315 +1,153 @@
Dependency injection and inversion of control in Python Dependency injection and inversion of control in Python
======================================================= -------------------------------------------------------
.. meta:: .. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Example :keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: This page describes a usage of the dependency injection and inversion of control :description: This article describes benefits of dependency injection and
in Python. It contains Python examples that show how to implement dependency inversion of control for Python applications. Also it
injection. It demonstrates a usage of the dependency injection framework contains some Python examples that show how dependency
Dependency Injector, its container, Factory, Singleton and Configuration injection and inversion could be implemented. In addition, it
providers. The example show how to use Dependency Injector providers overriding demonstrates usage of dependency injection framework,
feature for testing or configuring project in different environments and explains IoC container and such popular design pattern as Factory.
why it's better than monkey-patching.
History
~~~~~~~
Originally, dependency injection pattern got popular in languages with static
typing, like Java. Dependency injection framework can
significantly improve flexibility of the language with static typing. Also,
implementation of dependency injection framework for language with static
typing is not something that one can do shortly, it could be quite complex
thing to be done well.
While Python is very flexible interpreted language with dynamic typing, there
is a meaning that dependency injection doesn't work for it as well, as it does
for Java. Also there is a meaning that dependency injection framework is
something that Python developer would not ever need, cause dependency injection
could be implemented easily using language fundamentals.
Discussion
~~~~~~~~~~
It is true.
Partly.
Dependency injection, as a software design pattern, has number of
advantages that are common for each language (including Python):
+ Dependency Injection decreases coupling between a class and its dependency.
+ Because dependency injection doesn't require any change in code behavior it
can be applied to legacy code as a refactoring. The result is clients that
are more independent and that are easier to unit test in isolation using
stubs or mock objects that simulate other objects not under test. This ease
of testing is often the first benefit noticed when using dependency
injection.
+ Dependency injection can be used to externalize a system's configuration
details into configuration files allowing the system to be reconfigured
without recompilation (rebuilding). Separate configurations can be written
for different situations that require different implementations of
components. This includes, but is not limited to, testing.
+ Reduction of boilerplate code in the application objects since all work to
initialize or set up dependencies is handled by a provider component.
+ Dependency injection allows a client to remove all knowledge of a concrete
implementation that it needs to use. This helps isolate the client from the
impact of design changes and defects. It promotes reusability, testability
and maintainability.
+ Dependency injection allows a client the flexibility of being configurable.
Only the client's behavior is fixed. The client may act on anything that
supports the intrinsic interface the client expects.
.. note::
While improved testability is one the first benefits of using dependency
injection, it could be easily overwhelmed by monkey-patching technique,
that works absolutely great in Python (you can monkey-patch anything,
anytime). At the same time, monkey-patching has nothing similar with
other advantages defined above. Also monkey-patching technique is
something that could be considered like too dirty to be used in production.
The complexity of dependency injection pattern implementation in Python is
definitely quite lower than in other languages (even with dynamic typing).
.. note::
Low complexity of dependency injection pattern implementation in Python
still means that some code should be written, reviewed, tested and
supported.
Talking about inversion of control, it is a software design principle that
also works for each programming language, not depending on its typing type.
Inversion of control is used to increase modularity of the program and make
it extensible.
Main design purposes of using inversion of control are:
+ To decouple the execution of a task from implementation.
+ To focus a module on the task it is designed for.
+ To free modules from assumptions about how other systems do what they do and
instead rely on contracts.
+ To prevent side effects when replacing a module.
Example
~~~~~~~
Let's go through next example:
.. image:: /images/miniapps/engines_cars/diagram.png
:width: 100%
:align: center
Listing of ``example.engines`` module:
.. literalinclude:: ../../examples/miniapps/engines_cars/example/engines.py
:language: python
Listing of ``example.cars`` module:
.. literalinclude:: ../../examples/miniapps/engines_cars/example/cars.py
:language: python
Originally dependency injection pattern got popular in languages with static typing like Java. Next example demonstrates creation of several cars with different engines:
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
to be done well. And will take time.
Python is an interpreted language with dynamic typing. There is an opinion that dependency .. literalinclude:: ../../examples/miniapps/engines_cars/example_di.py
injection doesn't work for it as well as it does for Java. A lot of the flexibility is already :language: python
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 While previous example demonstrates advantages of dependency injection, there
easily using language fundamentals. is a disadvantage demonstration as well - creation of car requires additional
code for specification of dependencies. Nevertheless, this disadvantage could
be easily avoided by using a dependency injection framework for creation of
inversion of control container (IoC container).
This page describes the advantages of applying dependency injection in Python. It Example of creation of several inversion of control containers (IoC containers)
contains Python examples that show how to implement dependency injection. It demonstrates the usage using :doc:`Dependency Injector <../index>`:
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.
What is dependency injection? .. literalinclude:: ../../examples/miniapps/engines_cars/example_ioc_containers.py
----------------------------- :language: python
Let's see what the dependency injection is.
Dependency injection is a principle that helps to decrease coupling and increase cohesion.
.. image:: images/coupling-cohesion.png
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
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.
Cohesion often correlates with coupling. Higher cohesion usually leads to lower coupling and vice versa.
Low coupling brings flexibility. Your code becomes easier to change and test.
How to implement the dependency injection?
Objects do not create each other anymore. They provide a way to inject the dependencies instead.
Before:
.. code-block:: python
import os
class ApiClient:
def __init__(self) -> None:
self.api_key = os.getenv("API_KEY") # <-- dependency
self.timeout = int(os.getenv("TIMEOUT")) # <-- dependency
class Service:
def __init__(self) -> None:
self.api_client = ApiClient() # <-- dependency
def main() -> None:
service = Service() # <-- dependency
...
if __name__ == "__main__":
main()
After:
.. code-block:: python
import os
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
class Service:
def __init__(self, api_client: ApiClient) -> None:
self.api_client = api_client # <-- 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")),
),
),
)
``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.
``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:
.. code-block:: python
main(
service=Service(
api_client=ApiClient(
api_key=os.getenv("API_KEY"),
timeout=int(os.getenv("TIMEOUT")),
),
),
)
The assembly code might get duplicated and it'll become harder to change the application structure.
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.
``Dependency Injector`` helps to assemble and inject the dependencies.
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.
.. code-block:: python
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout,
)
service = providers.Factory(
Service,
api_client=api_client,
)
@inject
def main(service: Service = Provide[Container.service]) -> None:
...
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__])
main() # <-- dependency is injected automatically
with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically.
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.
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.
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.
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.
With 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.
Conclusion
----------
Dependency injection provides you with three 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
the fly.
- **Testability**. Testing is easier 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
change it.
Is it worth applying 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.
Is it worth using a framework for applying 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
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
An advice 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".
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
implementation details. Experience comes with practice and time.
What's next? What's next?
------------ ~~~~~~~~~~~~
Choose one of the following as a next step: Choose one of the following as a next step:
- Look at the application examples: + Pass one of the dependency injection tutorials:
- :ref:`application-single-container` + :ref:`flask-tutorial`
- :ref:`application-multiple-containers` + :ref:`aiohttp-tutorial`
- :ref:`decoupled-packages` + :ref:`asyncio-daemon-tutorial`
- :ref:`boto3-example` + :ref:`cli-tutorial`
- :ref:`django-example` + Know more about the :ref:`providers`
- :ref:`flask-example` + Go to the :ref:`contents`
- :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`
- :ref:`asyncio-daemon-tutorial`
- :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 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://en.wikipedia.org/wiki/Dependency_injection
+ https://martinfowler.com/articles/injection.html + https://martinfowler.com/articles/injection.html
+ https://github.com/ets-labs/python-dependency-injector + https://github.com/ets-labs/python-dependency-injector
+ https://pypi.org/project/dependency-injector/ + https://pypi.org/project/dependency-injector/
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -3,16 +3,18 @@ Introduction
.. meta:: .. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control :keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: Current section of the documentation is provides an :description: Current section of documentation is designed to give some
overview of the dependency injection, inversion of overview about dependency injection pattern, inversion of
control and Dependency Injector framework. control principle and "Dependency Injector" framework.
The current section of the documentation provides an overview of the Current section of documentation is designed to give some overview about
dependency injection, inversion of control, and the ``Dependency Injector`` framework. dependency injection pattern, inversion of control principle and
*Dependency Injector* framework.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
what_is_di
di_in_python di_in_python
key_features key_features
installation structure

View File

@ -1,42 +0,0 @@
Installation
============
``Dependency Injector`` is available on `PyPI <https://pypi.org/project/dependency-injector/>`_.
To install the latest version you can use ``pip``:
.. 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>`_.
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.
To verify the installed version:
.. code-block:: bash
>>> import dependency_injector
>>> dependency_injector.__version__
'4.39.0'
.. note::
When adding ``Dependency Injector`` to ``pyproject.toml`` or ``requirements.txt``
don't forget to pin the version to the current major:
.. 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

@ -1,43 +1,68 @@
.. _key-features:
Key features Key features
------------ ------------
.. meta:: .. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control :keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: This article describes key features of the Dependency Injector :description: This article describes key features of "Dependency Injector"
framework. framework. It also provides some cases and recommendations
about usage of "Dependency Injector" framework.
Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``, ``Dependency Injector`` is a dependency injection framework for Python.
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers It was designed to be a unified and developer-friendly tool that helps
that help assemble your objects. See :ref:`providers`. implement a dependency injection design pattern in a formal, pretty, and
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing Pythonic way.
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`.
- **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.
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle: It stands on two principles:
.. code-block:: text - Explicit is better than implicit (PEP20).
- Do no magic to your code.
Explicit is better than implicit How does 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`` makes a simple contract with you:
``Dependency Injector`` is a simple tool for the powerful concept.
- You tell the framework how to build you code
- 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.
The key features of the ``Dependency Injector`` framework are:
+ Easy, smart, and Pythonic style.
+ Does NOT pollute client code.
+ Obvious and clear structure.
+ Extensibility and flexibility.
+ High performance.
+ Memory efficiency.
+ Thread safety.
+ Documented.
+ Semantically versioned.
+ Distributed as pre-compiled wheels.
``Dependency Injector`` containers and providers are implemented as C extension
types using ``Cython``.
``Dependency Injector`` framework can be used in the different application types:
+ Web applications based on the ``Flask``, ``Django`` or any other web framework.
+ Asynchronous applications ``asyncio``, ``aiohttp``, ``Tornado``, or ``Twisted``.
+ Standalone frameworks and libraries.
+ GUI applications.
``Dependency Injector`` framework can be integrated on the different project
stages:
+ It can be used in the beginning of the development of a new application.
+ It can be integrated into application that is on its active development stage.
+ It can be used for refactoring of legacy application.
Components of ``Dependency Injector`` framework could be used:
+ In composition with each other.
+ Independently from each other.
.. disqus:: .. disqus::

View File

@ -0,0 +1,50 @@
Structure of Dependency Injector
--------------------------------
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: This article describes "Dependency Injector" framework
components and their interaction between each other.
Providers and containers are the former components of
the framework.
Current section describes *Dependency Injector* main entities and their
interaction between each other.
.. image:: /images/internals.png
:width: 100%
:align: center
There are 2 main entities: providers & containers.
Providers
~~~~~~~~~
Providers are strategies of accessing objects. For example,
:py:class:`dependency_injector.providers.Factory` creates new instance
of provided class every time it is called.
:py:class:`dependency_injector.providers.Singleton` creates provided
instance once and returns it on every next call. Base class is -
:py:class:`dependency_injector.providers.Provider`.
Providers could be:
+ Injected into each other.
+ Overridden by each other.
+ Extended.
Containers
~~~~~~~~~~
Containers are collections of providers. They are used for grouping
of providers by some principles. Base class is -
:py:class:`dependency_injector.containers.DeclarativeContainer`.
Containers could be:
+ Overridden by each other.
+ Copied from each other.
+ Extended.
.. disqus::

View File

@ -0,0 +1,126 @@
What is dependency injection and inversion of control?
------------------------------------------------------
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control
:description: This article provides definition of dependency injection,
inversion of control and dependency inversion. It contains
example code in Python that is refactored to be following
inversion of control principle.
Definition
~~~~~~~~~~
Wikipedia provides quite good definitions of dependency injection pattern
and related principles:
.. glossary::
`Dependency injection`_
In software engineering, dependency injection is a software design
pattern that implements inversion of control for resolving
dependencies. A dependency is an object that can be used (a service).
An injection is the passing of a dependency to a dependent object (a
client) that would use it. The service is made part of the client's
state. Passing the service to the client, rather than allowing a
client to build or find the service, is the fundamental requirement of
the pattern.
Dependency injection allows a program design to follow the dependency
inversion principle. The client delegates to external code (the
injector) the responsibility of providing its dependencies. The client
is not allowed to call the injector code. It is the injecting code
that constructs the services and calls the client to inject them. This
means the client code does not need to know about the injecting code.
The client does not need to know how to construct the services. The
client does not need to know which actual services it is using. The
client only needs to know about the intrinsic interfaces of the
services because these define how the client may use the services.
This separates the responsibilities of use and construction.
`Inversion of control`_
In software engineering, inversion of control (IoC) describes a design
in which custom-written portions of a computer program receive the
flow of control from a generic, reusable library. A software
architecture with this design inverts control as compared to
traditional procedural programming: in traditional programming, the
custom code that expresses the purpose of the program calls into
reusable libraries to take care of generic tasks, but with inversion
of control, it is the reusable code that calls into the custom, or
task-specific, code.
Inversion of control is used to increase modularity of the program and
make it extensible, and has applications in object-oriented
programming and other programming paradigms. The term was popularized
by Robert C. Martin and Martin Fowler.
The term is related to, but different from, the dependency inversion
principle, which concerns itself with decoupling dependencies between
high-level and low-level layers through shared abstractions.
`Dependency inversion`_
In object-oriented programming, the dependency inversion principle
refers to a specific form of decoupling software modules. When
following this principle, the conventional dependency relationships
established from high-level, policy-setting modules to low-level,
dependency modules are reversed, thus rendering high-level modules
independent of the low-level module implementation details. The
principle states:
+ High-level modules should not depend on low-level modules.
Both should depend on abstractions.
+ Abstractions should not depend on details.
Details should depend on abstractions.
The principle inverts the way some people may think about
object-oriented design, dictating that both high- and low-level
objects must depend on the same abstraction.
Example
~~~~~~~
Let's go through the code of ``example.py``:
.. literalinclude:: ../../examples/di_demo/example.py
:language: python
At some point, things defined above mean, that the code from ``example.py``,
could look different, like in ``example_di.py``:
.. literalinclude:: ../../examples/di_demo/example_di.py
:language: python
Best explanation, ever
~~~~~~~~~~~~~~~~~~~~~~
Some times ago `user198313`_ posted awesome `question`_ about dependency
injection on `StackOverflow`_:
.. note::
How to explain dependency injection to a 5-year-old?
And `John Munsch`_ provided absolutely Great answer:
.. note::
When you go and get things out of the refrigerator for yourself, you can
cause problems. You might leave the door open, you might get something
Mommy or Daddy doesn't want you to have. You might even be looking for
something we don't even have or which has expired.
What you should be doing is stating a need, "I need something to drink
with lunch," and then we will make sure you have something when you sit
down to eat.
.. disqus::
.. _Dependency injection: http://en.wikipedia.org/wiki/Dependency_injection
.. _Inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control
.. _Dependency inversion: https://en.wikipedia.org/wiki/Dependency_inversion_principle
.. _StackOverflow: http://stackoverflow.com/
.. _question: http://stackoverflow.com/questions/1638919/how-to-explain-dependency-injection-to-a-5-year-old/1639186
.. _user198313: http://stackoverflow.com/users/198313/user198313
.. _John Munsch: http://stackoverflow.com/users/31899/john-munsch

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,12 @@
Feedback Feedback
======== ========
To post a question, bug report, a feature proposal or get some help open a Feel free to post questions, bugs, feature requests, proposals etc. on
`Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_ or leave a comment *Dependency Injector* GitHub Issues:
below.
https://github.com/ets-labs/python-dependency-injector/issues
Your feedback is quite important!
.. disqus:: .. disqus::

View File

@ -0,0 +1,41 @@
Installation
============
*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
.. 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.
Sources can be cloned from GitHub_:
.. 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__
'3.15.2'
.. _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
.. disqus::

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

@ -1,18 +1,12 @@
.. _configuration-provider:
Configuration provider Configuration provider
====================== ======================
.. meta:: .. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection, :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
Option,Ini,Json,Yaml,Pydantic,Dict,Environment Variable Interpolation, Option,Ini,Json,Yaml,Dict,Environment Variable,Load,Read,Get
Environment Variable Substitution,Environment Variable in Config,
Environment Variable in YAML file,Environment Variable in INI file,Default,Load,Read
:description: Configuration provides configuration options to the other providers. This page :description: Configuration provides configuration options to the other providers. This page
demonstrates how to use Configuration provider to inject the dependencies, load demonstrates how to use Configuration provider to inject the dependencies, load
a configuration from an ini or yaml file, a dictionary, an environment variable, a configuration from an ini or yaml file, dictionary or an environment variable.
or a pydantic settings object. This page also describes how to substitute (interpolate)
environment variables in YAML and INI configuration files.
.. currentmodule:: dependency_injector.providers .. currentmodule:: dependency_injector.providers
@ -20,15 +14,11 @@ Configuration provider
.. literalinclude:: ../../examples/providers/configuration/configuration.py .. literalinclude:: ../../examples/providers/configuration/configuration.py
:language: python :language: python
:emphasize-lines: 7,12-13 :emphasize-lines: 4,9-10
:lines: 3- :lines: 4-14
It implements the principle "use first, define later". It implements the principle "use first, define later".
.. contents::
:local:
:backlinks: none
Loading from an INI file Loading from an INI file
------------------------ ------------------------
@ -37,39 +27,17 @@ Loading from an INI file
.. literalinclude:: ../../examples/providers/configuration/configuration_ini.py .. literalinclude:: ../../examples/providers/configuration/configuration_ini.py
:language: python :language: python
:lines: 3- :lines: 3-5,6-
:emphasize-lines: 12 :emphasize-lines: 6
where ``examples/providers/configuration/config.ini`` is: where ``examples/providers/configuration/config.ini`` is:
.. literalinclude:: ../../examples/providers/configuration/config.ini .. literalinclude:: ../../examples/providers/configuration/config.ini
:language: ini :language: ini
Alternatively, you can provide a path to the INI file over the configuration provider argument. In that case, :py:meth:`Configuration.from_ini` method supports environment variables interpolation. Use
the container will call ``config.from_ini()`` automatically: ``${ENV_NAME}`` format in the configuration file to substitute value of the environment
variable ``ENV_NAME``.
.. 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`.
Loading from a YAML file Loading from a YAML file
------------------------ ------------------------
@ -79,48 +47,17 @@ Loading from a YAML file
.. literalinclude:: ../../examples/providers/configuration/configuration_yaml.py .. literalinclude:: ../../examples/providers/configuration/configuration_yaml.py
:language: python :language: python
:lines: 3- :lines: 3-5,6-
:emphasize-lines: 12 :emphasize-lines: 6
where ``examples/providers/configuration/config.yml`` is: where ``examples/providers/configuration/config.yml`` is:
.. literalinclude:: ../../examples/providers/configuration/config.yml .. literalinclude:: ../../examples/providers/configuration/config.yml
:language: ini :language: ini
Alternatively, you can provide a path to the YAML file over the configuration provider argument. In that case, :py:meth:`Configuration.from_yaml` method supports environment variables interpolation. Use
the container will call ``config.from_yaml()`` automatically: ``${ENV_NAME}`` format in the configuration file to substitute value of the environment
variable ``ENV_NAME``.
.. 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)
.. note:: .. note::
@ -136,102 +73,6 @@ To use another loader use ``loader`` argument:
*Don't forget to mirror the changes in the requirements file.* *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 Loading from a dictionary
------------------------- -------------------------
@ -240,8 +81,8 @@ Loading from a dictionary
.. literalinclude:: ../../examples/providers/configuration/configuration_dict.py .. literalinclude:: ../../examples/providers/configuration/configuration_dict.py
:language: python :language: python
:lines: 3- :lines: 3-5,6-
:emphasize-lines: 12-19 :emphasize-lines: 6-13
Loading from an environment variable Loading from an environment variable
------------------------------------ ------------------------------------
@ -251,37 +92,8 @@ Loading from an environment variable
.. literalinclude:: ../../examples/providers/configuration/configuration_env.py .. literalinclude:: ../../examples/providers/configuration/configuration_env.py
:language: python :language: python
:lines: 3- :lines: 5-7,13-21
:emphasize-lines: 18-20 :emphasize-lines: 6-8
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 Loading from the multiple sources
--------------------------------- ---------------------------------
@ -291,131 +103,14 @@ configuration is merged recursively over the existing configuration.
.. literalinclude:: ../../examples/providers/configuration/configuration_multiple.py .. literalinclude:: ../../examples/providers/configuration/configuration_multiple.py
:language: python :language: python
:lines: 3- :lines: 3-5,6-14
:emphasize-lines: 12-13 :emphasize-lines: 6-7
where ``examples/providers/configuration/config.local.yml`` is: where ``examples/providers/configuration/config.local.yml`` is:
.. literalinclude:: ../../examples/providers/configuration/config.local.yml .. literalinclude:: ../../examples/providers/configuration/config.local.yml
:language: ini :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 Specifying the value type
------------------------- -------------------------
@ -427,7 +122,7 @@ convert it into an ``int`` or a ``float``.
.. literalinclude:: ../../examples/providers/configuration/configuration_type.py .. literalinclude:: ../../examples/providers/configuration/configuration_type.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 19 :emphasize-lines: 17
``Configuration`` provider has next helper methods: ``Configuration`` provider has next helper methods:
@ -440,140 +135,10 @@ The last method ``.as_(callback, *args, **kwargs)`` helps to implement other con
.. literalinclude:: ../../examples/providers/configuration/configuration_type_custom.py .. literalinclude:: ../../examples/providers/configuration/configuration_type_custom.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 18 :emphasize-lines: 16
With the ``.as_(callback, *args, **kwargs)`` you can specify a function that will be called With the ``.as_(callback, *args, **kwargs)`` you can specify a function that will be called
before the injection. The value from the config will be passed as a first argument. The returned 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. 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
--------------------
You can inject invariant configuration options based on the value of the other configuration
option.
To use that you should provide the switch-value as an item of the configuration option that
contains sections ``config.options[config.switch]``:
- When the value of the ``config.switch`` is ``A``, the ``config.options.A`` is injected
- When the value of the ``config.switch`` is ``B``, the ``config.options.B`` is injected
.. literalinclude:: ../../examples/providers/configuration/configuration_itemselector.py
:language: python
:lines: 3-
:emphasize-lines: 15,30-31,38
.. disqus:: .. disqus::

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

View File

@ -1,38 +1,21 @@
.. _dependency-provider:
Dependency provider Dependency provider
=================== ===================
.. currentmodule:: dependency_injector.providers .. 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)``. The first argument of the ``Dependency`` provider specifies a type of the dependency. It is
Dependency provider will control that returned object is an instance of ``instance_of`` type. 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 .. literalinclude:: ../../examples/providers/dependency.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 26,35-36 :emphasize-lines: 26
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`.
.. disqus:: .. 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 :language: python
:lines: 3- :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 Passing arguments to the underlying providers
--------------------------------------------- ---------------------------------------------
@ -81,7 +73,7 @@ all the classes and use special double-underscore ``__`` syntax for passing the
.. literalinclude:: ../../examples/providers/factory_init_injections_underlying.py .. literalinclude:: ../../examples/providers/factory_init_injections_underlying.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 44,49 :emphasize-lines: 24-35,39,42,45
When you use ``__`` separator in the name of the keyword argument the ``Factory`` looks for When you use ``__`` separator in the name of the keyword argument the ``Factory`` looks for
the dependency with the same name as the left part of the ``__`` expression. the dependency with the same name as the left part of the ``__`` expression.
@ -106,49 +98,10 @@ attribute of the provider that you're going to inject.
.. literalinclude:: ../../examples/providers/factory_delegation.py .. literalinclude:: ../../examples/providers/factory_delegation.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 28 :emphasize-lines: 25
.. note:: Any provider has a ``.provider`` attribute. .. 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: .. _factory-specialize-provided-type:
Specializing the provided type Specializing the provided type
@ -182,22 +135,18 @@ provider with two peculiarities:
.. literalinclude:: ../../examples/providers/abstract_factory.py .. literalinclude:: ../../examples/providers/abstract_factory.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 34 :emphasize-lines: 32
.. _factory-aggregate-provider:
Factory aggregate 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:: The aggregated factories are associated with the string names. When you call the
:ref:`aggregate-provider` it's a successor of ``FactoryAggregate`` provider that can aggregate ``FactoryAggregate`` you have to provide one of the these names as a first argument.
any type of provider, not only ``Factory``. ``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``.
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.
.. image:: images/factory_aggregate.png .. image:: images/factory_aggregate.png
:width: 100% :width: 100%
@ -206,14 +155,14 @@ The aggregated factories are associated with the string keys. When you call the
.. literalinclude:: ../../examples/providers/factory_aggregate.py .. literalinclude:: ../../examples/providers/factory_aggregate.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 33-37,47 :emphasize-lines: 31-35,43
You can get a dictionary of the aggregated providers using ``.providers`` attribute. You can get a dictionary of the aggregated factories using the ``.factories`` attribute of the
To get a game provider dictionary from the previous example you can use ``FactoryAggregate``. To get a game factories dictionary from the previous example you can use
``game_factory.providers`` attribute. ``game_factory.factories`` attribute.
You can also access an aggregated factory as an attribute. To create the ``Chess`` object from the 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:: .. note::
You can not override the ``FactoryAggregate`` provider. You can not override the ``FactoryAggregate`` provider.
@ -221,22 +170,4 @@ previous example you can do ``chess = game_factory.chess("John", "Jane")``.
.. note:: .. note::
When you inject the ``FactoryAggregate`` provider it is passed "as is". 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:: .. disqus::

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -24,7 +24,7 @@ It causes the cascade effect that helps to assemble object graphs. See ``Factory
└──> provider6() └──> provider6()
Another providers feature is an overriding. You can override any provider with another provider. Another providers feature is an overriding. You can override any provider by another provider.
This helps in testing. This also helps in overriding API clients with stubs for the development This helps in testing. This also helps in overriding API clients with stubs for the development
or staging environment. See the example at :ref:`provider-overriding`. or staging environment. See the example at :ref:`provider-overriding`.
@ -43,15 +43,9 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
coroutine coroutine
object object
list list
dict
configuration configuration
resource
aggregate
selector selector
dependency dependency
overriding overriding
provided_instance provided_instance
inject_self
custom 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

@ -13,7 +13,7 @@ List provider
.. literalinclude:: ../../examples/providers/list.py .. literalinclude:: ../../examples/providers/list.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 21-24 :emphasize-lines: 19-22
``List`` provider handles positional arguments the same way as a :ref:`factory-provider`. ``List`` provider handles positional arguments the same way as a :ref:`factory-provider`.

View File

@ -10,7 +10,7 @@ Provider overriding
.. currentmodule:: dependency_injector.providers .. currentmodule:: dependency_injector.providers
You can override any provider with another provider. You can override any provider by another provider.
When provider is overridden it calls to the overriding provider instead of providing When provider is overridden it calls to the overriding provider instead of providing
the object by its own. the object by its own.

View File

@ -14,7 +14,7 @@ You can inject provided object attribute, item or result of its method call.
.. literalinclude:: ../../examples/providers/provided_instance.py .. literalinclude:: ../../examples/providers/provided_instance.py
:language: python :language: python
:emphasize-lines: 28-34 :emphasize-lines: 26-32
:lines: 3- :lines: 3-
To use the feature you should use the ``.provided`` attribute of the injected provider. This To use the feature you should use the ``.provided`` attribute of the injected provider. This
@ -32,7 +32,27 @@ You can do nested constructions:
.. literalinclude:: ../../examples/providers/provided_instance_complex.py .. literalinclude:: ../../examples/providers/provided_instance_complex.py
:language: python :language: python
:emphasize-lines: 26-32 :emphasize-lines: 24-30
:lines: 3- :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:: .. 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

@ -17,7 +17,7 @@ Selector provider
.. literalinclude:: ../../examples/providers/selector.py .. literalinclude:: ../../examples/providers/selector.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 16-20 :emphasize-lines: 14-18
The first argument of the ``Selector`` provider is called ``selector``. It can be an option of The first argument of the ``Selector`` provider is called ``selector``. It can be an option of
a ``Configuration`` provider or any other callable. The ``selector`` callable has to return a a ``Configuration`` provider or any other callable. The ``selector`` callable has to return a
@ -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 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. value is changed the ``Selector`` provider will delegate the work to another provider.
.. seealso::
:ref:`aggregate-provider` to inject a group of providers.
.. disqus:: .. disqus::

View File

@ -1,5 +1,3 @@
.. _singleton-provider:
Singleton provider Singleton provider
================== ==================
@ -20,12 +18,13 @@ returns it on the rest of the calls.
:language: python :language: python
:lines: 3- :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:: .. note::
``Singleton`` provider makes dependencies injection only when it creates an object. When an object ``Singleton`` provider does dependencies injection only when creates the object. When the object
is created and memorized ``Singleton`` provider just returns it without applying injections. 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 Specialization of the provided type and abstract singletons work the same like like for the
factories: factories:
@ -33,13 +32,6 @@ factories:
- :ref:`factory-specialize-provided-type` - :ref:`factory-specialize-provided-type`
- :ref:`abstract-factory` - :ref:`abstract-factory`
``Singleton`` provider scope is tied to the container. Two different containers will provider
two different singleton objects:
.. literalinclude:: ../../examples/providers/singleton_multiple_containers.py
:language: python
:lines: 3-
Resetting memorized object Resetting memorized object
-------------------------- --------------------------
@ -49,44 +41,12 @@ provider.
.. literalinclude:: ../../examples/providers/singleton_resetting.py .. literalinclude:: ../../examples/providers/singleton_resetting.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 18 :emphasize-lines: 14
.. note:: .. note::
Resetting of the memorized object clears the reference to it. Further object's lifecycle is Resetting of the memorized object clears the reference to it. Further object's lifecycle is
managed by the garbage collector. 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 Using singleton with multiple threads
------------------------------------- -------------------------------------
@ -104,7 +64,7 @@ There are two thread-safe singleton implementations out of the box:
.. literalinclude:: ../../examples/providers/singleton_thread_locals.py .. literalinclude:: ../../examples/providers/singleton_thread_locals.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 13,15 :emphasize-lines: 11,12
Implementing scopes Implementing scopes
------------------- -------------------

View File

@ -1,60 +0,0 @@
.. _provider-typing:
Typing and mypy
===============
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Providers,Typing,Mypy,
Pattern,Example
:description: Dependency Injector providers are mypy-friendly. Providers module goes with the
typing stubs to provide the typing information to ``mypy``, IDEs and editors.
Providers are ``mypy``-friendly.
Providers module goes with the typing stubs. It provides typing information to ``mypy`` and your
IDE.
.. code-block:: python
from dependency_injector import providers
class Animal:
...
class Cat(Animal)
...
provider = providers.Factory(Cat)
if __name__ == "__main__":
animal = provider() # mypy knows that animal is of type "Cat"
You can use ``Provider`` as a generic type. This helps when a provider is an argument of a
function or method.
.. code-block:: python
:emphasize-lines: 12
from dependency_injector import providers
class Animal:
...
class Cat(Animal)
...
provider: providers.Provider[Animal] = providers.Factory(Cat)
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 :backlinks: none
You can find complete project on the 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? What are we going to build?
--------------------------- ---------------------------
@ -88,18 +88,18 @@ Prepare the environment
Let's create the environment for the project. 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 .. code-block:: bash
mkdir giphynav-aiohttp-tutorial mkdir giphynav-aiohttp-tutorial
cd 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 .. code-block:: bash
python3 -m venv venv
. venv/bin/activate . venv/bin/activate
Environment is ready and now we're going to create the layout of the project. Environment is ready and now we're going to create the layout of the project.
@ -116,7 +116,7 @@ Initial project layout::
│ ├── __init__.py │ ├── __init__.py
│ ├── application.py │ ├── application.py
│ ├── containers.py │ ├── containers.py
│ └── handlers.py │ └── views.py
├── venv/ ├── venv/
└── requirements.txt └── 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 - ``dependency-injector`` - the dependency injection framework
- ``aiohttp`` - the web 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 - ``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-aiohttp`` - the helper library for the testing of the ``aiohttp`` application
- ``pytest-cov`` - the helper library for measuring the test coverage - ``pytest-cov`` - the helper library for measuring the test coverage
@ -137,6 +139,7 @@ Put next lines into the ``requirements.txt`` file:
dependency-injector dependency-injector
aiohttp aiohttp
aiohttp-devtools
pyyaml pyyaml
pytest-aiohttp pytest-aiohttp
pytest-cov pytest-cov
@ -161,51 +164,60 @@ The requirements are setup. Now we will build a minimal application.
Minimal application Minimal application
------------------- -------------------
In this section we will build a minimal application. It will have an endpoint that In this section we will build a minimal application. It will have an endpoint that we can call.
will answer our requests in json format. There will be no payload for now. The endpoint will answer in the right format and will have no data.
Edit ``handlers.py``: Edit ``views.py``:
.. code-block:: python .. code-block:: python
"""Handlers module.""" """Views module."""
from aiohttp import web from aiohttp import web
async def index(request: web.Request) -> web.Response: async def index(request: web.Request) -> web.Response:
query = request.query.get("query", "Dependency Injector") query = request.query.get('query', 'Dependency Injector')
limit = int(request.query.get("limit", 10)) limit = int(request.query.get('limit', 10))
gifs = [] gifs = []
return web.json_response( return web.json_response(
{ {
"query": query, 'query': query,
"limit": limit, 'limit': limit,
"gifs": gifs, '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 .. code-block:: python
"""Containers module.""" """Application containers module."""
from dependency_injector import containers 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 index_view = aiohttp.View(views.index)
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. 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``: Put next into the ``application.py``:
@ -215,24 +227,27 @@ Put next into the ``application.py``:
from aiohttp import web from aiohttp import web
from .containers import Container from .containers import ApplicationContainer
from . import handlers
def create_app() -> web.Application: def create_app():
container = Container() """Create and return aiohttp application."""
container = ApplicationContainer()
app = web.Application() app: web.Application = container.app()
app.container = container app.container = container
app.add_routes([ app.add_routes([
web.get("/", handlers.index), web.get('/', container.index_view.as_view()),
]) ])
return app return app
.. note::
if __name__ == "__main__": Container is the first object in the application.
app = create_app()
web.run_app(app) The container is used to create all other objects.
Now we're ready to run our application Now we're ready to run our application
@ -240,30 +255,30 @@ Do next in the terminal:
.. code-block:: bash .. code-block:: bash
python -m giphynavigator.application adev runserver giphynavigator/application.py --livereload
The output should be something like: The output should be something like:
.. code-block:: bash .. code-block:: bash
======== Running on http://0.0.0.0:8080 ======== [18:52:59] Starting aux server at http://localhost:8001 ◆
(Press CTRL+C to quit) [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 .. code-block:: bash
http http://0.0.0.0:8080/ http http://127.0.0.1:8000/
You should see: You should see:
.. code-block:: http .. code-block:: json
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Length: 844 Content-Length: 844
Content-Type: application/json; charset=utf-8 Content-Type: application/json; charset=utf-8
Date: Wed, 29 Jul 2020 21:01:50 GMT 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": [], "gifs": [],
@ -291,7 +306,7 @@ Create ``giphy.py`` module in the ``giphynavigator`` package:
│ ├── application.py │ ├── application.py
│ ├── containers.py │ ├── containers.py
│ ├── giphy.py │ ├── giphy.py
│ └── handlers.py │ └── views.py
├── venv/ ├── venv/
└── requirements.txt └── requirements.txt
@ -306,7 +321,7 @@ and put next into it:
class GiphyClient: class GiphyClient:
API_URL = "https://api.giphy.com/v1" API_URL = 'http://api.giphy.com/v1'
def __init__(self, api_key, timeout): def __init__(self, api_key, timeout):
self._api_key = api_key self._api_key = api_key
@ -314,11 +329,11 @@ and put next into it:
async def search(self, query, limit): async def search(self, query, limit):
"""Make search API call and return result.""" """Make search API call and return result."""
url = f"{self.API_URL}/gifs/search" url = f'{self.API_URL}/gifs/search'
params = { params = {
"q": query, 'q': query,
"api_key": self._api_key, 'api_key': self._api_key,
"limit": limit, 'limit': limit,
} }
async with ClientSession(timeout=self._timeout) as session: async with ClientSession(timeout=self._timeout) as session:
async with session.get(url, params=params) as response: 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 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: providers from the ``dependency_injector.providers`` module:
- ``Factory`` provider. It will create a ``GiphyClient`` client. - ``Factory`` provider that will create the ``GiphyClient`` client.
- ``Configuration`` provider. It will provide an API key and a request timeout for the ``GiphyClient`` - ``Configuration`` provider that will provide the API key and the request timeout.
client. We will specify the location of the configuration file. The configuration provider will parse
the configuration file when we create a container instance.
Edit ``containers.py``: Edit ``containers.py``:
.. code-block:: python .. 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 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_client = providers.Factory(
giphy.GiphyClient, giphy.GiphyClient,
@ -357,8 +375,20 @@ Edit ``containers.py``:
timeout=config.giphy.request_timeout, timeout=config.giphy.request_timeout,
) )
Now let's add the configuration file. We will use YAML. Create an empty file ``config.yml`` in index_view = aiohttp.View(views.index)
the root root of the project:
.. 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 .. code-block:: bash
:emphasize-lines: 9 :emphasize-lines: 9
@ -369,7 +399,7 @@ the root root of the project:
│ ├── application.py │ ├── application.py
│ ├── containers.py │ ├── containers.py
│ ├── giphy.py │ ├── giphy.py
│ └── handlers.py │ └── views.py
├── venv/ ├── venv/
├── config.yml ├── config.yml
└── requirements.txt └── requirements.txt
@ -381,39 +411,40 @@ and put next into it:
giphy: giphy:
request_timeout: 10 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 Now we need to edit ``create_app()`` to make two things when application starts:
``create_app()`` to fetch the key value from it.
- Load the configuration file the ``config.yml``.
- Load the API key from the ``GIPHY_API_KEY`` environment variable.
Edit ``application.py``: Edit ``application.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 11 :emphasize-lines: 11-12
"""Application module.""" """Application module."""
from aiohttp import web from aiohttp import web
from .containers import Container from .containers import ApplicationContainer
from . import handlers
def create_app() -> web.Application: def create_app():
container = Container() """Create and return aiohttp application."""
container.config.giphy.api_key.from_env("GIPHY_API_KEY") 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.container = container
app.add_routes([ app.add_routes([
web.get("/", handlers.index), web.get('/', container.index_view.as_view()),
]) ])
return app 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. Now we need to create an API key and set it to the environment variable.
As for now, dont worry, just take this one: 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: Create ``services.py`` module in the ``giphynavigator`` package:
.. code-block:: bash .. code-block:: bash
:emphasize-lines: 8 :emphasize-lines: 7
./ ./
├── giphynavigator/ ├── giphynavigator/
@ -450,10 +481,9 @@ Create ``services.py`` module in the ``giphynavigator`` package:
│ ├── application.py │ ├── application.py
│ ├── containers.py │ ├── containers.py
│ ├── giphy.py │ ├── giphy.py
│ ├── handlers.py │ ├── services.py
│ └── services.py │ └── views.py
├── venv/ ├── venv/
├── config.yml
└── requirements.txt └── requirements.txt
and put next into it: and put next into it:
@ -477,26 +507,31 @@ and put next into it:
result = await self._giphy_client.search(query, limit) 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 The ``SearchService`` has a dependency on the ``GiphyClient``. This dependency will be injected.
injected when we add ``SearchService`` to the container. Let's add ``SearchService`` to the container.
Edit ``containers.py``: Edit ``containers.py``:
.. code-block:: python .. 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 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_client = providers.Factory(
giphy.GiphyClient, giphy.GiphyClient,
@ -509,67 +544,67 @@ Edit ``containers.py``:
giphy_client=giphy_client, 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 Make the search work
-------------------- --------------------
Now we are ready to put the search into work. Let's inject ``SearchService`` into Now we are ready to make the search work. Let's use the ``SearchService`` in the ``index`` view.
the ``index`` handler. We will use :ref:`wiring` feature.
Edit ``handlers.py``: Edit ``views.py``:
.. code-block:: python .. 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 aiohttp import web
from dependency_injector.wiring import Provide, inject
from .services import SearchService from .services import SearchService
from .containers import Container
@inject
async def index( async def index(
request: web.Request, request: web.Request,
search_service: SearchService = Provide[Container.search_service], search_service: SearchService,
) -> web.Response: ) -> web.Response:
query = request.query.get("query", "Dependency Injector") query = request.query.get('query', 'Dependency Injector')
limit = int(request.query.get("limit", 10)) limit = int(request.query.get('limit', 10))
gifs = await search_service.search(query, limit) gifs = await search_service.search(query, limit)
return web.json_response( return web.json_response(
{ {
"query": query, 'query': query,
"limit": limit, 'limit': limit,
"gifs": gifs, 'gifs': gifs,
}, },
) )
To make the injection work we need to wire the container with the ``handlers`` module. Now let's inject the ``SearchService`` dependency into the ``index`` view.
Let's configure the container to automatically make wiring with the ``handlers`` module when we
create a container instance.
Edit ``containers.py``: Edit ``containers.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 10 :emphasize-lines: 28-31
"""Containers module.""" """Application containers module."""
from dependency_injector import containers, providers 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_client = providers.Factory(
giphy.GiphyClient, giphy.GiphyClient,
@ -582,47 +617,52 @@ Edit ``containers.py``:
giphy_client=giphy_client, 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 .. code-block:: bash
python -m giphynavigator.application adev runserver giphynavigator/application.py --livereload
and make a request to the API in the terminal: and make a request to the API in the terminal:
.. code-block:: bash .. 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: You should see:
.. code-block:: http .. code-block:: json
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Length: 492 Content-Length: 850
Content-Type: application/json; charset=utf-8 Content-Type: application/json; charset=utf-8
Date: Fri, 09 Oct 2020 01:35:48 GMT Date: Wed, 29 Jul 2020 22:22:55 GMT
Server: Python/3.10 aiohttp/3.6.2 Server: Python/3.8 aiohttp/3.6.2
{ {
"gifs": [ "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/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" "query": "wow,it works"
} }
@ -633,48 +673,86 @@ The search works!
Make some refactoring Make some refactoring
--------------------- ---------------------
Our ``index`` handler has two hardcoded config values: Our ``index`` view has two hardcoded config values:
- Default search query - Default search query
- Default results limit - Default results limit
Let's make some refactoring. We will move these values to the config. Let's make some refactoring. We will move these values to the config.
Edit ``handlers.py``: Edit ``views.py``:
.. code-block:: python .. code-block:: python
:emphasize-lines: 14-15,17-18 :emphasize-lines: 11-12,14-15
"""Handlers module.""" """Views module."""
from aiohttp import web from aiohttp import web
from dependency_injector.wiring import Provide, inject
from .services import SearchService from .services import SearchService
from .containers import Container
@inject
async def index( async def index(
request: web.Request, request: web.Request,
search_service: SearchService = Provide[Container.search_service], search_service: SearchService,
default_query: str = Provide[Container.config.default.query], default_query: str,
default_limit: int = Provide[Container.config.default.limit.as_int()], default_limit: int,
) -> web.Response: ) -> web.Response:
query = request.query.get("query", default_query) query = request.query.get('query', default_query)
limit = int(request.query.get("limit", default_limit)) limit = int(request.query.get('limit', default_limit))
gifs = await search_service.search(query, limit) gifs = await search_service.search(query, limit)
return web.json_response( return web.json_response(
{ {
"query": query, 'query': query,
"limit": limit, 'limit': limit,
"gifs": gifs, '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``: Edit ``config.yml``:
@ -683,21 +761,26 @@ Edit ``config.yml``:
giphy: giphy:
request_timeout: 10 request_timeout: 10
default: search:
query: "Dependency Injector" default_query: "Dependency Injector"
limit: 10 default_limit: 10
The refactoring is done. We've made it cleaner - hardcoded values are now moved to the config. 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 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: Create ``tests.py`` module in the ``giphynavigator`` package:
.. code-block:: bash .. code-block:: bash
:emphasize-lines: 9 :emphasize-lines: 8
./ ./
├── giphynavigator/ ├── giphynavigator/
@ -705,17 +788,16 @@ Create ``tests.py`` module in the ``giphynavigator`` package:
│ ├── application.py │ ├── application.py
│ ├── containers.py │ ├── containers.py
│ ├── giphy.py │ ├── giphy.py
│ ├── handlers.py
│ ├── services.py │ ├── services.py
│ └── tests.py │ ├── tests.py
│ └── views.py
├── venv/ ├── venv/
├── config.yml
└── requirements.txt └── requirements.txt
and put next into it: and put next into it:
.. code-block:: python .. code-block:: python
:emphasize-lines: 32,59,73 :emphasize-lines: 30,57,71
"""Tests module.""" """Tests module."""
@ -729,9 +811,7 @@ and put next into it:
@pytest.fixture @pytest.fixture
def app(): def app():
app = create_app() return create_app()
yield app
app.container.unwire()
@pytest.fixture @pytest.fixture
@ -742,29 +822,29 @@ and put next into it:
async def test_index(client, app): async def test_index(client, app):
giphy_client_mock = mock.AsyncMock(spec=GiphyClient) giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = { giphy_client_mock.search.return_value = {
"data": [ 'data': [
{"url": "https://giphy.com/gif1.gif"}, {'url': 'https://giphy.com/gif1.gif'},
{"url": "https://giphy.com/gif2.gif"}, {'url': 'https://giphy.com/gif2.gif'},
], ],
} }
with app.container.giphy_client.override(giphy_client_mock): with app.container.giphy_client.override(giphy_client_mock):
response = await client.get( response = await client.get(
"/", '/',
params={ params={
"query": "test", 'query': 'test',
"limit": 10, 'limit': 10,
}, },
) )
assert response.status == 200 assert response.status == 200
data = await response.json() data = await response.json()
assert data == { assert data == {
"query": "test", 'query': 'test',
"limit": 10, 'limit': 10,
"gifs": [ 'gifs': [
{"url": "https://giphy.com/gif1.gif"}, {'url': 'https://giphy.com/gif1.gif'},
{"url": "https://giphy.com/gif2.gif"}, {'url': 'https://giphy.com/gif2.gif'},
], ],
} }
@ -772,30 +852,30 @@ and put next into it:
async def test_index_no_data(client, app): async def test_index_no_data(client, app):
giphy_client_mock = mock.AsyncMock(spec=GiphyClient) giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = { giphy_client_mock.search.return_value = {
"data": [], 'data': [],
} }
with app.container.giphy_client.override(giphy_client_mock): with app.container.giphy_client.override(giphy_client_mock):
response = await client.get("/") response = await client.get('/')
assert response.status == 200 assert response.status == 200
data = await response.json() data = await response.json()
assert data["gifs"] == [] assert data['gifs'] == []
async def test_index_default_params(client, app): async def test_index_default_params(client, app):
giphy_client_mock = mock.AsyncMock(spec=GiphyClient) giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = { giphy_client_mock.search.return_value = {
"data": [], 'data': [],
} }
with app.container.giphy_client.override(giphy_client_mock): with app.container.giphy_client.override(giphy_client_mock):
response = await client.get("/") response = await client.get('/')
assert response.status == 200 assert response.status == 200
data = await response.json() data = await response.json()
assert data["query"] == app.container.config.default.query() assert data['query'] == app.container.config.search.default_query()
assert data["limit"] == app.container.config.default.limit() assert data['limit'] == app.container.config.search.default_limit()
Now let's run it and check the coverage: 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: 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 platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
plugins: asyncio-0.16.0, anyio-3.3.4, aiohttp-0.3.0, cov-3.0.0 plugins: cov-2.10.0, aiohttp-0.3.0, asyncio-0.14.0
collected 3 items collected 3 items
giphynavigator/tests.py ... [100%] 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 Name Stmts Miss Cover
--------------------------------------------------- ---------------------------------------------------
giphynavigator/__init__.py 0 0 100% giphynavigator/__init__.py 0 0 100%
giphynavigator/application.py 13 2 85% giphynavigator/__main__.py 5 5 0%
giphynavigator/containers.py 7 0 100% giphynavigator/application.py 10 0 100%
giphynavigator/containers.py 10 0 100%
giphynavigator/giphy.py 14 9 36% giphynavigator/giphy.py 14 9 36%
giphynavigator/handlers.py 10 0 100%
giphynavigator/services.py 9 1 89% 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:: .. note::
@ -839,19 +920,45 @@ In this tutorial we've built an ``aiohttp`` REST API application following the d
injection principle. injection principle.
We've used the ``Dependency Injector`` as a dependency injection framework. 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 The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff
giphy client. 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. """Application containers module."""
:ref:`provider-overriding` feature helped in testing.
We kept all the dependencies injected explicitly. This will help when you need to add or from dependency_injector import containers, providers
change something in future. from dependency_injector.ext import aiohttp
from aiohttp import web
You can find complete project on the from . import giphy, services, views
`Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
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? What's next?
@ -859,6 +966,4 @@ What's next?
- Know more about the :ref:`providers` - Know more about the :ref:`providers`
- Go to the :ref:`contents` - Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

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