Compare commits
No commits in common. "master" and "3.36.0" have entirely different histories.
7
.coveragerc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[run]
|
||||||
|
source = src/dependency_injector
|
||||||
|
omit = tests/unit
|
||||||
|
plugins = Cython.Coverage
|
||||||
|
|
||||||
|
[html]
|
||||||
|
directory=reports/unittests/
|
|
@ -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
|
|
|
@ -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``
|
|
|
@ -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``
|
|
|
@ -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"
|
|
|
@ -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
|
@ -1 +0,0 @@
|
||||||
github: rmk135
|
|
131
.github/workflows/publishing.yml
vendored
|
@ -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 }}
|
|
67
.github/workflows/tests-and-linters.yml
vendored
|
@ -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
|
@ -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
|
@ -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
|
@ -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=
|
|
@ -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)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
41
Makefile
|
@ -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)*
|
||||||
|
|
||||||
|
|
238
README.rst
|
@ -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>`_
|
||||||
|
|
9
docs/_static/custom.css
vendored
|
@ -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
|
@ -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);
|
||||||
|
});
|
2
docs/_static/logo.svg
vendored
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.6 KiB |
3
docs/_static/sphinx_rtd_theme-hotfix.css
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.rst-content .highlight>pre, .rst-content .linenodiv>pre {
|
||||||
|
line-height: normal;
|
||||||
|
}
|
1
docs/_static/sponsor.html
vendored
|
@ -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
|
@ -0,0 +1,9 @@
|
||||||
|
dependency_injector.ext.aiohttp
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: dependency_injector.ext.aiohttp
|
||||||
|
:members:
|
||||||
|
:special-members:
|
||||||
|
|
||||||
|
|
||||||
|
.. disqus::
|
|
@ -1,9 +0,0 @@
|
||||||
dependency_injector.ext.starlette
|
|
||||||
=================================
|
|
||||||
|
|
||||||
.. automodule:: dependency_injector.ext.starlette
|
|
||||||
:members:
|
|
||||||
:inherited-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
.. disqus::
|
|
|
@ -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
|
@ -0,0 +1,9 @@
|
||||||
|
dependency_injector.ext.flask
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. automodule:: dependency_injector.ext.flask
|
||||||
|
:members:
|
||||||
|
:special-members:
|
||||||
|
|
||||||
|
|
||||||
|
.. disqus::
|
|
@ -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
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
dependency_injector.wiring
|
|
||||||
=============================
|
|
||||||
|
|
||||||
.. automodule:: dependency_injector.wiring
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. disqus::
|
|
129
docs/conf.py
|
@ -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",
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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::
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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::
|
||||||
|
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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
|
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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::
|
|
74
docs/examples/bundles_miniapp.rst
Normal 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::
|
21
docs/examples/chained_factories.rst
Normal 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::
|
|
@ -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::
|
|
|
@ -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::
|
|
20
docs/examples/factory_of_factories.rst
Normal 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::
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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
|
|
|
@ -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::
|
|
|
@ -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::
|
|
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 182 KiB |
Before Width: | Height: | Size: 382 KiB |
|
@ -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::
|
|
||||||
|
|
18
docs/examples/password_hashing_miniapp.rst
Normal 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::
|
|
@ -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::
|
|
73
docs/examples/services_miniapp_v1.rst
Normal 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::
|
73
docs/examples/services_miniapp_v2.rst
Normal 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::
|
55
docs/examples/use_cases_miniapp.rst
Normal 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::
|
BIN
docs/favicon.ico
Before Width: | Height: | Size: 15 KiB |
BIN
docs/images/internals.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
docs/images/miniapps/engines_cars/diagram.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/images/miniapps/services/classes.png
Normal file
After Width: | Height: | Size: 49 KiB |
122
docs/index.rst
|
@ -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
|
|
||||||
|
|
|
@ -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::
|
||||||
|
|
Before Width: | Height: | Size: 8.2 KiB |
|
@ -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
|
||||||
|
|
|
@ -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::
|
|
|
@ -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::
|
||||||
|
|
50
docs/introduction/structure.rst
Normal 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::
|
126
docs/introduction/what_is_di.rst
Normal 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
|
|
@ -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::
|
||||||
|
|
41
docs/main/installation.rst
Normal 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::
|
|
@ -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::
|
|
|
@ -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::
|
|
|
@ -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::
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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::
|
||||||
|
|
|
@ -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::
|
|
|
@ -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::
|
||||||
|
|
Before Width: | Height: | Size: 12 KiB |
|
@ -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
|
|
||||||
|
|
|
@ -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::
|
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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::
|
||||||
|
|
|
@ -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::
|
|
|
@ -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::
|
||||||
|
|
|
@ -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
|
||||||
-------------------
|
-------------------
|
||||||
|
|
|
@ -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::
|
|
|
@ -1,7 +0,0 @@
|
||||||
.. list-table::
|
|
||||||
:class: no-border
|
|
||||||
:align: left
|
|
||||||
|
|
||||||
* - Sponsor the project on GitHub:
|
|
||||||
- .. raw:: html
|
|
||||||
:file: _static/sponsor.html
|
|
|
@ -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. Let’s 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, don’t worry, just take this one:
|
As for now, don’t 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::
|
||||||
|
|