mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-04-22 10:02:12 +03:00
Compare commits
196 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6e4794bab1 | ||
|
f3b3b1baa4 | ||
|
9b66d4bf16 | ||
|
7d4ebecd19 | ||
|
09efbffab1 | ||
|
8b625d81ad | ||
|
23acf01c15 | ||
|
0d6fdb5b78 | ||
|
2330122de6 | ||
|
29ae3e1337 | ||
|
50643e0dfb | ||
|
3893e1df81 | ||
|
0fd35baee6 | ||
|
3df95847d5 | ||
|
6d9d34c0f6 | ||
|
de50666a13 | ||
|
ccbd5bbb80 | ||
|
00326e9a22 | ||
|
46646b1acf | ||
|
9f38db6ef3 | ||
|
9f4e2839d2 | ||
|
41e18dfa90 | ||
|
f9db578c59 | ||
|
d82d9fb822 | ||
|
3ba4704bc1 | ||
|
aa56b70dc8 | ||
|
7f586246b4 | ||
|
87741edb53 | ||
|
be7abb3ec7 | ||
|
15400dea7d | ||
|
704e36a642 | ||
|
83d71acb70 | ||
|
c61fc16b8d | ||
|
cab75cb9c7 | ||
|
494c457643 | ||
|
abf2a2577c | ||
|
3777a947ea | ||
|
c92129dcb0 | ||
|
37486900cd | ||
|
9071583981 | ||
|
595daebd3a | ||
|
13a7ef609b | ||
|
7a88a8ee8d | ||
|
938091b6ea | ||
|
4bda5105c2 | ||
|
46034cbeb1 | ||
|
39ac098ca2 | ||
|
f54604fc14 | ||
|
2c998b8448 | ||
|
5697f1d5d8 | ||
|
086d82f13d | ||
|
3375436eb3 | ||
|
fec2b08210 | ||
|
8a44027f3d | ||
|
f56453f59f | ||
|
1b9e079524 | ||
|
b1a3a69428 | ||
|
a8b54423dc | ||
|
3e56fef461 | ||
|
5d1e5ee485 | ||
|
f7c6cb2647 | ||
|
a5166bf591 | ||
|
98d5867743 | ||
|
68da747ce0 | ||
|
cc2304e46e | ||
|
4bfdf89142 | ||
|
659d242503 | ||
|
6b13b6dbaf | ||
|
d3320f5f06 | ||
|
31c1f7c2d6 | ||
|
d0c8f328b3 | ||
|
3b76a0d091 | ||
|
a79ea1790c | ||
|
781d3b9c4c | ||
|
6f491a6cae | ||
|
88a2b96102 | ||
|
f0d9eda566 | ||
|
55f81bd754 | ||
|
a9cd0de886 | ||
|
aaff333f01 | ||
|
3858cef657 | ||
|
8cf86826eb | ||
|
6f859e4aa2 | ||
|
0668295543 | ||
|
142b91921a | ||
|
753e863d02 | ||
|
14b5ddae4f | ||
|
bf356ec565 | ||
|
9bc11a7828 | ||
|
a0bb7c4ede | ||
|
450407bf7a | ||
|
4666a15092 | ||
|
daca85d555 | ||
|
20bf3c0a01 | ||
|
4188f721d6 | ||
|
cfed30cf07 | ||
|
13cae77d57 | ||
|
8b0745d43e | ||
|
93c8cbc83b | ||
|
77b5cdebd3 | ||
|
f0c55cda22 | ||
|
f00fa16bd0 | ||
|
c2877777af | ||
|
8fe00bcff0 | ||
|
c26b260c73 | ||
|
ad0d430229 | ||
|
0235d68265 | ||
|
86df7f91f6 | ||
|
38ca1cdeed | ||
|
8dc3dd2f09 | ||
|
a38ca647c3 | ||
|
d4933baec1 | ||
|
742e73af1a | ||
|
cfadd8c3fa | ||
|
cc17052acc | ||
|
7238482402 | ||
|
541131e338 | ||
|
72d0e2610d | ||
|
0b3bcf334e | ||
|
99d858e2fb | ||
|
fe01ad41d9 | ||
|
6030950596 | ||
|
34902db86e | ||
|
b16b190ff7 | ||
|
b97862cb9f | ||
|
94aca21fb8 | ||
|
4cc4ca9188 | ||
|
284dee6e58 | ||
|
73a43e6191 | ||
|
08ea99759d | ||
|
f82a6b5445 | ||
|
1e198a3ebd | ||
|
4f977c7cf0 | ||
|
8ade2b7839 | ||
|
0b1e214135 | ||
|
98f036e14c | ||
|
023d766267 | ||
|
196d86f4b3 | ||
|
6b4c7e50b5 | ||
|
3c52756d3f | ||
|
274d1fe53b | ||
|
8bea62eeee | ||
|
b64c9b7a05 | ||
|
31bed0651f | ||
|
e670377bb3 | ||
|
a9173496b4 | ||
|
02b9793189 | ||
|
4a52595a9d | ||
|
c92a941fe5 | ||
|
7e794c41dd | ||
|
93dad6bbd0 | ||
|
320d837bea | ||
|
d827f93816 | ||
|
b3732281a1 | ||
|
7d160cb4a5 | ||
|
258c55dd22 | ||
|
0b5987bf84 | ||
|
cf039a0c2b | ||
|
980914c2f7 | ||
|
5c7bdf4fc6 | ||
|
4733aad44e | ||
|
d8aa70c70b | ||
|
8377f2a82d | ||
|
cc4235257c | ||
|
ff5b81fecb | ||
|
cef6d35cfd | ||
|
902913ccff | ||
|
14d8ed909b | ||
|
6af818102b | ||
|
e0825041b0 | ||
|
b4df3dd2c9 | ||
|
cf2861c4b4 | ||
|
49e2cc75c2 | ||
|
eda67e42d0 | ||
|
ea9aa2370e | ||
|
36bfd2ed58 | ||
|
83c2af0e7e | ||
|
1163ac59d4 | ||
|
4286013ca0 | ||
|
48df949cd5 | ||
|
9637d97d48 | ||
|
c4639e555e | ||
|
7b19fa0964 | ||
|
cde7dee4b3 | ||
|
5acde87a6e | ||
|
7bdcc33eda | ||
|
b4ddf61939 | ||
|
f376628dfa | ||
|
384117db9c | ||
|
06f9855140 | ||
|
547b7fd844 | ||
|
04117938d2 | ||
|
c23a48c28e | ||
|
98a4b06a12 | ||
|
c19969a6ed | ||
|
aa251a44ba |
|
@ -1,7 +0,0 @@
|
|||
[run]
|
||||
source = src/dependency_injector
|
||||
omit = tests/unit
|
||||
plugins = Cython.Coverage
|
||||
|
||||
[html]
|
||||
directory=reports/unittests/
|
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
github: rmk135
|
91
.github/workflows/publishing.yml
vendored
91
.github/workflows/publishing.yml
vendored
|
@ -1,6 +1,7 @@
|
|||
name: Publishing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
@ -9,28 +10,28 @@ jobs:
|
|||
|
||||
tests:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.13
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: 3.9
|
||||
TOXENV: 3.13
|
||||
|
||||
linters:
|
||||
name: Run linters
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
toxenv: [flake8, pydocstyle, mypy, pylint]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.13
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
|
@ -39,15 +40,18 @@ jobs:
|
|||
build-sdist:
|
||||
name: Build source tarball
|
||||
needs: [tests, linters]
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: python setup.py sdist
|
||||
- uses: actions/upload-artifact@v2
|
||||
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:
|
||||
|
@ -56,62 +60,47 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04, windows-latest, macos-latest]
|
||||
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2019, macos-14]
|
||||
env:
|
||||
CIBW_SKIP: cp27-win*
|
||||
CIBW_SKIP: cp27-*
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install cibuildwheel==1.8.0
|
||||
- run: cibuildwheel --output-dir wheelhouse
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
build-wheels-linux-aarch64:
|
||||
name: Build wheels (ubuntu-latest-aarch64)
|
||||
needs: [tests, linters]
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install cibuildwheel==1.8.0
|
||||
- run: cibuildwheel --archs aarch64 --output-dir wheelhouse
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v2.20.0
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cibw-wheels-x86-${{ matrix.os }}-${{ strategy.job-index }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
publish:
|
||||
name: Publish on PyPI
|
||||
needs: [build-sdist, build-wheels, build-wheels-linux-aarch64]
|
||||
runs-on: ubuntu-18.04
|
||||
needs: [build-sdist, build-wheels]
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: artifact
|
||||
pattern: cibw-*
|
||||
path: dist
|
||||
- uses: pypa/gh-action-pypi-publish@master
|
||||
merge-multiple: true
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
# For publishing to Test PyPI, uncomment next two lines:
|
||||
# password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||
# repository_url: https://test.pypi.org/legacy/
|
||||
|
||||
publish-docs:
|
||||
name: Publish docs
|
||||
needs: [publish]
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install -r requirements-doc.txt
|
||||
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: |
|
||||
|
|
57
.github/workflows/tests-and-linters.yml
vendored
57
.github/workflows/tests-and-linters.yml
vendored
|
@ -4,15 +4,15 @@ on: [push, pull_request, workflow_dispatch]
|
|||
|
||||
jobs:
|
||||
|
||||
test-on-different-versions:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-18.04
|
||||
tests-on-legacy-versions:
|
||||
name: Run tests on legacy versions
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, pypy2, pypy3]
|
||||
python-version: [3.7]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- run: pip install tox
|
||||
|
@ -20,21 +20,48 @@ jobs:
|
|||
env:
|
||||
TOXENV: ${{ matrix.python-version }}
|
||||
|
||||
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:
|
||||
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@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: pip install tox cython
|
||||
- run: make cythonize
|
||||
- run: tox
|
||||
python-version: 3.12
|
||||
- run: pip install tox 'cython>=3,<4'
|
||||
- run: tox -vv
|
||||
env:
|
||||
TOXENV: coveralls
|
||||
|
||||
|
@ -45,10 +72,10 @@ jobs:
|
|||
matrix:
|
||||
toxenv: [flake8, pydocstyle, mypy, pylint]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.13
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
|
|
17
.gitignore
vendored
17
.gitignore
vendored
|
@ -36,6 +36,7 @@ reports/
|
|||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
@ -54,7 +55,7 @@ target/
|
|||
.idea/
|
||||
|
||||
# Virtualenv
|
||||
venv/
|
||||
venv*/
|
||||
|
||||
# SQLite
|
||||
*.db
|
||||
|
@ -62,13 +63,13 @@ venv/
|
|||
# Vim Rope
|
||||
.ropeproject/
|
||||
|
||||
# C extensions
|
||||
src/dependency_injector/*.h
|
||||
src/dependency_injector/*.so
|
||||
src/dependency_injector/containers/*.h
|
||||
src/dependency_injector/containers/*.so
|
||||
src/dependency_injector/providers/*.h
|
||||
src/dependency_injector/providers/*.so
|
||||
# Cython artifacts
|
||||
src/**/*.c
|
||||
src/**/*.h
|
||||
src/**/*.so
|
||||
src/**/*.html
|
||||
|
||||
# Workspace for samples
|
||||
.workspace/
|
||||
|
||||
.vscode/
|
||||
|
|
49
.pylintrc
49
.pylintrc
|
@ -1,49 +0,0 @@
|
|||
[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
|
|
@ -18,3 +18,7 @@ Dependency Injector Contributors
|
|||
+ 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) 2021, ETS Labs
|
||||
Copyright (c) 2024, Roman Mogylatov
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
41
Makefile
41
Makefile
|
@ -1,14 +1,6 @@
|
|||
VERSION := $(shell python setup.py --version)
|
||||
|
||||
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
|
||||
|
||||
export COVERAGE_RCFILE := pyproject.toml
|
||||
|
||||
clean:
|
||||
# Clean sources
|
||||
|
@ -25,39 +17,28 @@ clean:
|
|||
find examples -name '*.py[co]' -delete
|
||||
find examples -name '__pycache__' -delete
|
||||
|
||||
cythonize:
|
||||
# Compile Cython to C
|
||||
cython -a $(CYTHON_DIRECTIVES) $(CYTHON_SRC)
|
||||
build: clean
|
||||
# Compile C extensions
|
||||
python setup.py build_ext --inplace
|
||||
# Move all Cython html reports
|
||||
mkdir -p reports/cython/
|
||||
find src -name '*.html' -exec mv {} reports/cython/ \;
|
||||
|
||||
build: clean cythonize
|
||||
# Compile C extensions
|
||||
python setup.py build_ext --inplace
|
||||
|
||||
docs-live:
|
||||
sphinx-autobuild docs docs/_build/html
|
||||
|
||||
install: uninstall clean cythonize
|
||||
install: uninstall clean build
|
||||
pip install -ve .
|
||||
|
||||
uninstall:
|
||||
- pip uninstall -y -q dependency-injector 2> /dev/null
|
||||
|
||||
test-py2: build
|
||||
test:
|
||||
# Unit tests with coverage report
|
||||
coverage erase
|
||||
coverage run --rcfile=./.coveragerc -m unittest discover -s tests/unit/ -p test_*_py2_py3.py
|
||||
coverage report --rcfile=./.coveragerc
|
||||
coverage html --rcfile=./.coveragerc
|
||||
|
||||
test: build
|
||||
# Unit tests with coverage report
|
||||
coverage erase
|
||||
coverage run --rcfile=./.coveragerc -m unittest discover -s tests/unit/ -p test_*py3*.py
|
||||
coverage report --rcfile=./.coveragerc
|
||||
coverage html --rcfile=./.coveragerc
|
||||
coverage run -m pytest -c tests/.configs/pytest.ini
|
||||
coverage report
|
||||
coverage html
|
||||
|
||||
check:
|
||||
flake8 src/dependency_injector/
|
||||
|
@ -68,9 +49,9 @@ check:
|
|||
|
||||
mypy tests/typing
|
||||
|
||||
test-publish: cythonize
|
||||
test-publish: build
|
||||
# Create distributions
|
||||
python setup.py sdist
|
||||
python -m build --sdist
|
||||
# Upload distributions to PyPI
|
||||
twine upload --repository testpypi dist/dependency-injector-$(VERSION)*
|
||||
|
||||
|
|
57
README.rst
57
README.rst
|
@ -35,7 +35,7 @@
|
|||
:target: https://pypi.org/project/dependency-injector/
|
||||
:alt: Wheel
|
||||
|
||||
.. image:: https://img.shields.io/github/workflow/status/ets-labs/python-dependency-injector/Tests%20and%20linters/master
|
||||
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master
|
||||
:target: https://github.com/ets-labs/python-dependency-injector/actions
|
||||
:alt: Build Status
|
||||
|
||||
|
@ -48,26 +48,26 @@ What is ``Dependency Injector``?
|
|||
|
||||
``Dependency Injector`` is a dependency injection framework for Python.
|
||||
|
||||
It helps implementing the dependency injection principle.
|
||||
It helps implement the dependency injection principle.
|
||||
|
||||
Key features of the ``Dependency Injector``:
|
||||
|
||||
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
|
||||
that help assembling your objects.
|
||||
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
|
||||
that help assemble your objects.
|
||||
See `Providers <https://python-dependency-injector.ets-labs.org/providers/index.html>`_.
|
||||
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
|
||||
and configuring dev / stage environment to replace API clients with stubs etc. See
|
||||
and configuring dev/stage environment to replace API clients with stubs etc. See
|
||||
`Provider overriding <https://python-dependency-injector.ets-labs.org/providers/overriding.html>`_.
|
||||
- **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, ``pydantic`` settings,
|
||||
- **Configuration**. Reads configuration from ``yaml``, ``ini``, and ``json`` files, ``pydantic`` settings,
|
||||
environment variables, and dictionaries.
|
||||
See `Configuration provider <https://python-dependency-injector.ets-labs.org/providers/configuration.html>`_.
|
||||
- **Containers**. Provides declarative and dynamic containers.
|
||||
See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_.
|
||||
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
|
||||
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
|
||||
See `Resource provider <https://python-dependency-injector.ets-labs.org/providers/resource.html>`_.
|
||||
- **Wiring**. Injects dependencies into functions and methods. Helps integrating with
|
||||
- **Containers**. Provides declarative and dynamic containers.
|
||||
See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_.
|
||||
- **Wiring**. Injects dependencies into functions and methods. Helps integrate with
|
||||
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc.
|
||||
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
|
||||
- **Asynchronous**. Supports asynchronous injections.
|
||||
|
@ -75,12 +75,12 @@ Key features of the ``Dependency Injector``:
|
|||
- **Typing**. Provides typing stubs, ``mypy``-friendly.
|
||||
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.
|
||||
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -90,7 +90,7 @@ Key features of the ``Dependency Injector``:
|
|||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
timeout=config.timeout,
|
||||
)
|
||||
|
||||
service = providers.Factory(
|
||||
|
@ -100,34 +100,33 @@ Key features of the ``Dependency Injector``:
|
|||
|
||||
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]):
|
||||
def main(service: Service = Provide[Container.service]) -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
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 ``main()`` function the ``Service`` dependency is assembled and injected automatically.
|
||||
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically.
|
||||
|
||||
When doing a testing you call the ``container.api_client.override()`` to replace the real API
|
||||
client with a mock. When you call ``main()`` the mock is injected.
|
||||
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 configuring project for the different environments: replace an API client
|
||||
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`` objects assembling is consolidated in the container.
|
||||
Dependency injections are defined explicitly.
|
||||
This makes easier to understand and change how application works.
|
||||
With the ``Dependency Injector``, object assembling is consolidated in a container. Dependency injections are defined explicitly.
|
||||
This makes it easier to understand and change how an application works.
|
||||
|
||||
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
|
||||
:target: https://github.com/ets-labs/python-dependency-injector
|
||||
|
@ -185,19 +184,19 @@ The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/d
|
|||
|
||||
You need to specify how to assemble and where to inject the dependencies explicitly.
|
||||
|
||||
The power of the framework is in a simplicity.
|
||||
The power of the framework is in its simplicity.
|
||||
``Dependency Injector`` is a simple tool for the powerful concept.
|
||||
|
||||
Frequently asked questions
|
||||
--------------------------
|
||||
|
||||
What is the dependency injection?
|
||||
What is dependency injection?
|
||||
- dependency injection is a principle that decreases coupling and increases cohesion
|
||||
|
||||
Why should I do the dependency injection?
|
||||
- your code becomes more flexible, testable and clear 😎
|
||||
- your code becomes more flexible, testable, and clear 😎
|
||||
|
||||
How do I start doing the dependency injection?
|
||||
How do I start applying the dependency injection?
|
||||
- you start writing the code following the dependency injection principle
|
||||
- you register all of your application components and their dependencies in the container
|
||||
- when you need a component, you specify where to inject it or get it from the container
|
||||
|
@ -205,7 +204,7 @@ How do I start doing the dependency injection?
|
|||
What price do I pay and what do I get?
|
||||
- you need to explicitly specify the dependencies
|
||||
- it will be extra work in the beginning
|
||||
- it will payoff as the project grows
|
||||
- it will payoff as project grows
|
||||
|
||||
Have a question?
|
||||
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_
|
||||
|
|
9
docs/_static/custom.css
vendored
Normal file
9
docs/_static/custom.css
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
.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
11
docs/_static/disqus.js
vendored
|
@ -1,11 +0,0 @@
|
|||
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
2
docs/_static/logo.svg
vendored
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.8 KiB |
1
docs/_static/sponsor.html
vendored
Normal file
1
docs/_static/sponsor.html
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<iframe src="https://github.com/sponsors/rmk135/button" title="Sponsor Dependency Injector" height="32" width="114" style="border: 0; border-radius: 6px;"></iframe>
|
128
docs/conf.py
128
docs/conf.py
|
@ -20,49 +20,49 @@ import alabaster
|
|||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
sys.path.insert(0, os.path.abspath(".."))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
#needs_sphinx = "1.0"
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# extensions coming with Sphinx (named "sphinx.ext.*") or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'alabaster',
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinxcontrib.disqus',
|
||||
"alabaster",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx_disqus.disqus",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
# source_suffix = [".rst", ".md"]
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
#source_encoding = "utf-8-sig"
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = u'Dependency Injector'
|
||||
copyright = u'2021, ETS Labs'
|
||||
author = u'ETS Labs'
|
||||
project = "Dependency Injector"
|
||||
copyright = "2024, Roman Mogylatov"
|
||||
author = "Roman Mogylatov"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# The version info for the project you"re documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
# Getting version:
|
||||
with open('../src/dependency_injector/__init__.py') as init_file:
|
||||
version = re.search('__version__ = \'(.*?)\'', init_file.read()).group(1)
|
||||
with open("../src/dependency_injector/__init__.py") as init_file:
|
||||
version = re.search("__version__ = \"(.*?)\"", init_file.read()).group(1)
|
||||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
@ -76,19 +76,19 @@ language = None
|
|||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
#today = ""
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
#today_fmt = "%B %d, %Y"
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ["_build"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
# If true, "()" will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
|
@ -100,7 +100,7 @@ exclude_patterns = ['_build']
|
|||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
@ -116,8 +116,8 @@ todo_include_todos = False
|
|||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
# html_theme = 'sphinx_rtd_theme'
|
||||
html_theme = 'alabaster'
|
||||
# html_theme = "sphinx_rtd_theme"
|
||||
html_theme = "alabaster"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
|
@ -141,21 +141,24 @@ html_theme_path = [alabaster.get_path()]
|
|||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
html_favicon = 'favicon.ico'
|
||||
html_favicon = "favicon.ico"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
html_static_path = ["_static"]
|
||||
html_css_files = [
|
||||
"custom.css",
|
||||
]
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# If not "", a "Last updated on:" timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
#html_last_updated_fmt = "%b %d, %Y"
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
|
@ -189,50 +192,50 @@ html_static_path = ['_static']
|
|||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
#html_use_opensearch = ""
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
||||
#html_search_language = 'en'
|
||||
# "da", "de", "en", "es", "fi", "fr", "hu", "it", "ja"
|
||||
# "nl", "no", "pt", "ro", "ru", "sv", "tr"
|
||||
#html_search_language = "en"
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# Now only 'ja' uses this config value
|
||||
#html_search_options = {'type': 'default'}
|
||||
# Now only "ja" uses this config value
|
||||
#html_search_options = {"type": "default"}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#html_search_scorer = 'scorer.js'
|
||||
#html_search_scorer = "scorer.js"
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'dependency_injectordoc'
|
||||
htmlhelp_basename = "dependency_injectordoc"
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ("letterpaper" or "a4paper").
|
||||
#"papersize": "letterpaper",
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ("10pt", "11pt" or "12pt").
|
||||
#"pointsize": "10pt",
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
#"preamble": "",
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
#"figure_align": "htbp",
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'dependency_injector.tex', u'Dependency Injector Documentation',
|
||||
u'ETS Labs', 'manual'),
|
||||
(master_doc, "dependency_injector.tex", u"Dependency Injector Documentation",
|
||||
u"Roman Mogylatov", "manual"),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
@ -261,7 +264,7 @@ latex_documents = [
|
|||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'Dependency Injector', u'Dependency Injector Documentation',
|
||||
(master_doc, "Dependency Injector", u"Dependency Injector Documentation",
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
@ -275,9 +278,9 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'Dependency Injector', u'Dependency Injector Documentation',
|
||||
author, 'Dependency Injector', 'Dependency injection microframework for Python',
|
||||
'Miscellaneous'),
|
||||
(master_doc, "Dependency Injector", u"Dependency Injector Documentation",
|
||||
author, "Dependency Injector", "Dependency injection microframework for Python",
|
||||
"Miscellaneous"),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
@ -286,24 +289,25 @@ texinfo_documents = [
|
|||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
# How to display URL addresses: "footnote", "no", or "inline".
|
||||
#texinfo_show_urls = "footnote"
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
# If true, do not generate a @detailmenu in the "Top" node"s menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
autodoc_member_order = 'bysource'
|
||||
autodoc_member_order = "bysource"
|
||||
|
||||
disqus_shortname = 'python-dependency-injector'
|
||||
disqus_shortname = "python-dependency-injector"
|
||||
|
||||
html_theme_options = {
|
||||
'github_user': 'ets-labs',
|
||||
'github_repo': 'python-dependency-injector',
|
||||
'github_type': 'star',
|
||||
'github_button': True,
|
||||
'github_banner': True,
|
||||
'logo': 'logo.svg',
|
||||
'description': 'Dependency injection framework for Python',
|
||||
'code_font_size': '10pt',
|
||||
'analytics_id': 'UA-67012059-1',
|
||||
"github_user": "ets-labs",
|
||||
"github_repo": "python-dependency-injector",
|
||||
"github_type": "star",
|
||||
"github_button": True,
|
||||
"github_banner": True,
|
||||
"logo": "logo.svg",
|
||||
"description": "Dependency injection framework for Python by Roman Mogylatov",
|
||||
"code_font_size": "10pt",
|
||||
"analytics_id": "UA-67012059-1",
|
||||
"donate_url": "https://github.com/sponsors/rmk135",
|
||||
}
|
||||
|
|
|
@ -34,10 +34,39 @@ Injections in the declarative container are done the usual way:
|
|||
:language: python
|
||||
:lines: 3-
|
||||
|
||||
You can override the container providers when you create the container instance:
|
||||
You can override container providers while creating a container instance:
|
||||
|
||||
.. literalinclude:: ../../examples/containers/declarative_override_providers.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 13
|
||||
|
||||
Alternatively, you can call ``container.override_providers()`` method when the container instance
|
||||
already exists:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
container = Container()
|
||||
|
||||
container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar))
|
||||
|
||||
assert isinstance(container.foo(), mock.Mock)
|
||||
assert isinstance(container.bar(), mock.Mock)
|
||||
|
||||
You can also use ``container.override_providers()`` with a context manager to reset
|
||||
provided overriding after the context is closed:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
container = Container()
|
||||
|
||||
with container.override_providers(foo=mock.Mock(Foo), bar=mock.Mock(Bar)):
|
||||
assert isinstance(container.foo(), mock.Mock)
|
||||
assert isinstance(container.bar(), mock.Mock)
|
||||
|
||||
assert isinstance(container.foo(), Foo)
|
||||
assert isinstance(container.bar(), Bar)
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -19,7 +19,7 @@ additional arguments.
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
instance = concrete_factory()
|
||||
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
|
||||
|
||||
|
@ -43,21 +43,21 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
|
|||
providers.Factory(dict, arg1=1),
|
||||
arg2=2,
|
||||
)
|
||||
print(chained_dict_factory()) # prints: {'arg1': 1, 'arg2': 2}
|
||||
print(chained_dict_factory()) # prints: {"arg1": 1, "arg2": 2}
|
||||
|
||||
# 2. Keyword arguments of upper level factory have priority
|
||||
chained_dict_factory = providers.Factory(
|
||||
providers.Factory(dict, arg1=1),
|
||||
arg1=2,
|
||||
)
|
||||
print(chained_dict_factory()) # prints: {'arg1': 2}
|
||||
print(chained_dict_factory()) # prints: {"arg1": 2}
|
||||
|
||||
# 3. Keyword arguments provided from context have the most priority
|
||||
chained_dict_factory = providers.Factory(
|
||||
providers.Factory(dict, arg1=1),
|
||||
arg1=2,
|
||||
)
|
||||
print(chained_dict_factory(arg1=3)) # prints: {'arg1': 3}
|
||||
print(chained_dict_factory(arg1=3)) # prints: {"arg1": 3}
|
||||
|
||||
|
||||
Credits
|
||||
|
|
|
@ -20,7 +20,7 @@ additional arguments.
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
instance = concrete_factory()
|
||||
# Same as: # instance = SomeClass(base_argument=1, extra_argument=2)
|
||||
|
||||
|
@ -46,7 +46,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
|
|||
arg1=1,
|
||||
)
|
||||
dict_factory = factory_of_dict_factories(arg2=2)
|
||||
print(dict_factory()) # prints: {'arg1': 1, 'arg2': 2}
|
||||
print(dict_factory()) # prints: {"arg1": 1, "arg2": 2}
|
||||
|
||||
# 2. Keyword arguments of upper level factory have priority
|
||||
factory_of_dict_factories = providers.Factory(
|
||||
|
@ -55,7 +55,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
|
|||
arg1=1,
|
||||
)
|
||||
dict_factory = factory_of_dict_factories(arg1=2)
|
||||
print(dict_factory()) # prints: {'arg1': 2}
|
||||
print(dict_factory()) # prints: {"arg1": 2}
|
||||
|
||||
# 3. Keyword arguments provided from context have the most priority
|
||||
factory_of_dict_factories = providers.Factory(
|
||||
|
@ -64,7 +64,7 @@ Passing of the arguments works the same way like for any other :ref:`factory-pro
|
|||
arg1=1,
|
||||
)
|
||||
dict_factory = factory_of_dict_factories(arg1=2)
|
||||
print(dict_factory(arg1=3)) # prints: {'arg1': 3}
|
||||
print(dict_factory(arg1=3)) # prints: {"arg1": 3}
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
|
|
@ -78,4 +78,6 @@ Sources
|
|||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -84,4 +84,6 @@ 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::
|
|
@ -90,4 +90,6 @@ 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::
|
||||
|
|
|
@ -8,7 +8,7 @@ Boto3 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://www.djangoproject.com/>`_.
|
||||
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>`_.
|
||||
|
||||
|
@ -17,4 +17,6 @@ Listing of ``boto3_session_example.py``:
|
|||
.. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py
|
||||
:language: python
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -129,4 +129,6 @@ 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::
|
||||
|
|
|
@ -94,4 +94,6 @@ Sources
|
|||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -95,4 +95,6 @@ See also:
|
|||
- Resource provider :ref:`resource-async-initializers`
|
||||
- Wiring :ref:`async-injections-wiring`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -116,4 +116,6 @@ 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::
|
||||
|
|
|
@ -76,4 +76,6 @@ Sources
|
|||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -86,4 +86,6 @@ 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::
|
||||
|
|
|
@ -84,4 +84,6 @@ Sources
|
|||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -70,11 +70,13 @@ Tests use :ref:`provider-overriding` feature to replace giphy client with a mock
|
|||
|
||||
.. literalinclude:: ../../examples/miniapps/sanic/giphynavigator/tests.py
|
||||
:language: python
|
||||
:emphasize-lines: 27,54,68
|
||||
: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::
|
||||
|
|
|
@ -9,11 +9,11 @@ Dependency Injector --- Dependency injection framework for Python
|
|||
:description: Dependency Injector is a dependency injection framework
|
||||
for Python. It helps to maintain you application structure.
|
||||
It was designed to be unified, developer-friendly
|
||||
tool that helps to implement dependency injection design
|
||||
pattern in formal, pretty, Pythonic way. Dependency Injector
|
||||
provides implementations of such popular design patterns
|
||||
like IoC container, Factory and Singleton. Dependency
|
||||
Injector providers are implemented as C extension types
|
||||
tool that helps to implement dependency injection design
|
||||
pattern in formal, pretty, Pythonic way. Dependency Injector
|
||||
provides implementations of such popular design patterns
|
||||
like IoC container, Factory and Singleton. Dependency
|
||||
Injector providers are implemented as C extension types
|
||||
using Cython.
|
||||
|
||||
.. _index:
|
||||
|
@ -50,7 +50,7 @@ Dependency Injector --- Dependency injection framework for Python
|
|||
:target: https://pypi.org/project/dependency-injector/
|
||||
:alt: Wheel
|
||||
|
||||
.. image:: https://img.shields.io/github/workflow/status/ets-labs/python-dependency-injector/Tests%20and%20linters/master
|
||||
.. image:: https://img.shields.io/github/actions/workflow/status/ets-labs/python-dependency-injector/tests-and-linters.yml?branch=master
|
||||
:target: https://github.com/ets-labs/python-dependency-injector/actions
|
||||
:alt: Build Status
|
||||
|
||||
|
@ -65,28 +65,28 @@ It helps implementing the dependency injection principle.
|
|||
Key features of the ``Dependency Injector``:
|
||||
|
||||
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
|
||||
that help assembling your objects. See :ref:`providers`.
|
||||
``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
|
||||
and configuring dev/stage environment to replace API clients with stubs etc. See
|
||||
:ref:`provider-overriding`.
|
||||
- **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, ``pydantic`` settings,
|
||||
- **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 integrating with
|
||||
- **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.
|
||||
- **Maturity**. Mature and production-ready. Well-tested, documented, and supported.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -96,7 +96,7 @@ Key features of the ``Dependency Injector``:
|
|||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
timeout=config.timeout,
|
||||
)
|
||||
|
||||
service = providers.Factory(
|
||||
|
@ -106,24 +106,24 @@ Key features of the ``Dependency Injector``:
|
|||
|
||||
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]):
|
||||
def main(service: Service = Provide[Container.service]) -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
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`` objects assembling is consolidated in the container.
|
||||
With the ``Dependency Injector``, object assembling is consolidated in the container.
|
||||
Dependency injections are defined explicitly.
|
||||
This makes easier to understand and change how application works.
|
||||
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
|
||||
|
|
|
@ -11,24 +11,24 @@ Dependency injection and inversion of control in Python
|
|||
feature for testing or configuring project in different environments and explains
|
||||
why it's better than monkey-patching.
|
||||
|
||||
Originally dependency injection pattern got popular in the languages with a static typing,
|
||||
like Java. Dependency injection is a principle that helps to achieve an inversion of control.
|
||||
Dependency injection framework can significantly improve a flexibility of the language
|
||||
with a static typing. Implementation of a dependency injection framework for a language
|
||||
with a static typing is not something that one can do quickly. It will be a quite complex thing
|
||||
Originally dependency injection pattern got popular in languages with static typing like Java.
|
||||
Dependency injection is a principle that helps to achieve an inversion of control. A
|
||||
dependency injection framework can significantly improve the flexibility of a language
|
||||
with static typing. Implementation of a dependency injection framework for a language
|
||||
with static typing is not something that one can do quickly. It will be a quite complex thing
|
||||
to be done well. And will take time.
|
||||
|
||||
Python is an interpreted language with a dynamic typing. There is an opinion that dependency
|
||||
Python is an interpreted language with dynamic typing. There is an opinion that dependency
|
||||
injection doesn't work for it as well as it does for Java. A lot of the flexibility is already
|
||||
built in. Also there is an opinion that a dependency injection framework is something that
|
||||
built-in. Also, there is an opinion that a dependency injection framework is something that
|
||||
Python developer rarely needs. Python developers say that dependency injection can be implemented
|
||||
easily using language fundamentals.
|
||||
|
||||
This page describes the advantages of the dependency injection usage in Python. It
|
||||
contains Python examples that show how to implement dependency injection. It demonstrates a usage
|
||||
of the dependency injection framework ``Dependency Injector``, its container, ``Factory``,
|
||||
``Singleton`` and ``Configuration`` providers. The example shows how to use ``Dependency Injector``
|
||||
providers overriding feature for testing or configuring project in different environments and
|
||||
This page describes the advantages of applying dependency injection in Python. It
|
||||
contains Python examples that show how to implement dependency injection. It demonstrates the usage
|
||||
of the ``Dependency Injector`` framework, its container, ``Factory``, ``Singleton``,
|
||||
and ``Configuration`` providers. The example shows how to use providers' overriding feature
|
||||
of ``Dependency Injector`` for testing or re-configuring a project in different environments and
|
||||
explains why it's better than monkey-patching.
|
||||
|
||||
What is dependency injection?
|
||||
|
@ -44,14 +44,14 @@ What is coupling and cohesion?
|
|||
|
||||
Coupling and cohesion are about how tough the components are tied.
|
||||
|
||||
- **High coupling**. If the coupling is high it's like using a superglue or welding. No easy way
|
||||
- **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 the screws. Very easy to disassemble and
|
||||
assemble back or assemble a different way. It is an opposite to high coupling.
|
||||
- **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.
|
||||
|
||||
When the cohesion is high the coupling is low.
|
||||
Cohesion often correlates with coupling. Higher cohesion usually leads to lower coupling and vice versa.
|
||||
|
||||
Low coupling brings a flexibility. Your code becomes easier to change and test.
|
||||
Low coupling brings flexibility. Your code becomes easier to change and test.
|
||||
|
||||
How to implement the dependency injection?
|
||||
|
||||
|
@ -66,14 +66,14 @@ Before:
|
|||
|
||||
class ApiClient:
|
||||
|
||||
def __init__(self):
|
||||
self.api_key = os.getenv('API_KEY') # <-- dependency
|
||||
self.timeout = os.getenv('TIMEOUT') # <-- dependency
|
||||
def __init__(self) -> None:
|
||||
self.api_key = os.getenv("API_KEY") # <-- dependency
|
||||
self.timeout = int(os.getenv("TIMEOUT")) # <-- dependency
|
||||
|
||||
|
||||
class Service:
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.api_client = ApiClient() # <-- dependency
|
||||
|
||||
|
||||
|
@ -82,7 +82,7 @@ Before:
|
|||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
After:
|
||||
|
@ -94,27 +94,27 @@ After:
|
|||
|
||||
class ApiClient:
|
||||
|
||||
def __init__(self, api_key: str, timeout: int):
|
||||
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):
|
||||
def __init__(self, api_client: ApiClient) -> None:
|
||||
self.api_client = api_client # <-- dependency is injected
|
||||
|
||||
|
||||
def main(service: Service): # <-- dependency is injected
|
||||
def main(service: Service) -> None: # <-- dependency is injected
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main(
|
||||
service=Service(
|
||||
api_client=ApiClient(
|
||||
api_key=os.getenv('API_KEY'),
|
||||
timeout=os.getenv('TIMEOUT'),
|
||||
api_key=os.getenv("API_KEY"),
|
||||
timeout=int(os.getenv("TIMEOUT")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -136,8 +136,8 @@ Now you need to assemble and inject the objects like this:
|
|||
main(
|
||||
service=Service(
|
||||
api_client=ApiClient(
|
||||
api_key=os.getenv('API_KEY'),
|
||||
timeout=os.getenv('TIMEOUT'),
|
||||
api_key=os.getenv("API_KEY"),
|
||||
timeout=int(os.getenv("TIMEOUT")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -149,20 +149,20 @@ Here comes the ``Dependency Injector``.
|
|||
What does the Dependency Injector do?
|
||||
-------------------------------------
|
||||
|
||||
With the dependency injection pattern objects loose the responsibility of assembling
|
||||
the dependencies. The ``Dependency Injector`` absorbs that responsibilities.
|
||||
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
|
||||
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 inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -172,7 +172,7 @@ the dependency.
|
|||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
timeout=config.timeout,
|
||||
)
|
||||
|
||||
service = providers.Factory(
|
||||
|
@ -182,94 +182,94 @@ the dependency.
|
|||
|
||||
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]):
|
||||
def main(service: Service = Provide[Container.service]) -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
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 ``main()`` function the ``Service`` dependency is assembled and injected automatically.
|
||||
When you call the ``main()`` function the ``Service`` dependency is assembled and injected automatically.
|
||||
|
||||
When doing a testing you call the ``container.api_client.override()`` to replace the real API
|
||||
client with a mock. When you call ``main()`` the mock is injected.
|
||||
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 configuring project for the different environments: replace an API client
|
||||
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 the container. Dependency injections are defined explicitly.
|
||||
This makes easier to understand and change how application works.
|
||||
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 a monkey-patching.
|
||||
The testability benefit is opposed to monkey-patching.
|
||||
|
||||
In Python you can monkey-patch
|
||||
anything, anytime. The problem with a monkey-patching is that it's too fragile. The reason is that
|
||||
when you monkey-patch you do something that wasn't intended to be done. You monkey-patch the
|
||||
implementation details. When implementation changes the monkey-patching is broken.
|
||||
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 a dependency injection you patch the interface, not an implementation. This is a way more
|
||||
With dependency injection, you patch the interface, not an implementation. This is a way more
|
||||
stable approach.
|
||||
|
||||
Also monkey-patching is a way too dirty to be used outside of the testing code for
|
||||
reconfiguring the project for the different environments.
|
||||
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 brings you 3 advantages:
|
||||
Dependency injection provides you with three advantages:
|
||||
|
||||
- **Flexibility**. The components are loosely coupled. You can easily extend or change a
|
||||
functionality of the system by combining the components different way. You even can do it on
|
||||
- **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 easy because you can easily inject mocks instead of real objects
|
||||
- **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 the container. This
|
||||
provides an overview and control on the application structure. It is easy to understand and
|
||||
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 to use a dependency injection in Python?
|
||||
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 is the benefit.
|
||||
larger the application the more significant the benefits.
|
||||
|
||||
Is it worth to use a framework for the dependency injection?
|
||||
Is it worth using a framework for applying dependency injection?
|
||||
|
||||
The complexity of the dependency injection pattern implementation in Python is
|
||||
lower than in the other languages but it's still in place. It doesn't mean you have to use a
|
||||
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
|
||||
- Known to the other engineers
|
||||
- Other engineers are familiar with it
|
||||
|
||||
Few advices at last:
|
||||
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 now".
|
||||
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 a common sense when apply dependency injection. It is a good
|
||||
principle, but not a silver bullet. If you do it too much you will reveal too much of the
|
||||
- **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?
|
||||
|
@ -303,12 +303,13 @@ Choose one of the following as a next step:
|
|||
Useful links
|
||||
------------
|
||||
|
||||
There are some useful links related to dependency injection design pattern
|
||||
that could be used for further reading:
|
||||
A few useful links related to a dependency injection design pattern for further reading:
|
||||
|
||||
+ https://en.wikipedia.org/wiki/Dependency_injection
|
||||
+ https://martinfowler.com/articles/injection.html
|
||||
+ https://github.com/ets-labs/python-dependency-injector
|
||||
+ https://pypi.org/project/dependency-injector/
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -7,8 +7,8 @@ Introduction
|
|||
overview of the dependency injection, inversion of
|
||||
control and Dependency Injector framework.
|
||||
|
||||
Current section of the documentation provides an overview of the
|
||||
dependency injection, inversion of control and the ``Dependency Injector`` framework.
|
||||
The current section of the documentation provides an overview of the
|
||||
dependency injection, inversion of control, and the ``Dependency Injector`` framework.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
|
|
@ -2,7 +2,7 @@ Installation
|
|||
============
|
||||
|
||||
``Dependency Injector`` is available on `PyPI <https://pypi.org/project/dependency-injector/>`_.
|
||||
To install latest version you can use ``pip``:
|
||||
To install the latest version you can use ``pip``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -10,7 +10,7 @@ To install latest version you can use ``pip``:
|
|||
|
||||
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.
|
||||
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)
|
||||
|
@ -23,20 +23,20 @@ To verify the installed version:
|
|||
|
||||
>>> import dependency_injector
|
||||
>>> dependency_injector.__version__
|
||||
'4.0.0'
|
||||
'4.39.0'
|
||||
|
||||
.. note::
|
||||
When add ``Dependency Injector`` to the ``requirements.txt`` don't forget to pin version
|
||||
to the current major:
|
||||
When adding ``Dependency Injector`` to ``pyproject.toml`` or ``requirements.txt``
|
||||
don't forget to pin the version to the current major:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: bash
|
||||
|
||||
dependency-injector>=4.0,<5.0
|
||||
dependency-injector>=4.0,<5.0
|
||||
|
||||
*Next major version can be incompatible.*
|
||||
*The next major version can be incompatible.*
|
||||
|
||||
All releases are available on `PyPI release history page <https://pypi.org/project/dependency-injector/#history>`_.
|
||||
Each release has appropriate tag. The tags are available on
|
||||
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::
|
||||
|
|
|
@ -11,23 +11,23 @@ Key features
|
|||
Key features of the ``Dependency Injector``:
|
||||
|
||||
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
|
||||
that help assembling your objects. See :ref:`providers`.
|
||||
``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
|
||||
and configuring dev/stage environment to replace API clients with stubs etc. See
|
||||
:ref:`provider-overriding`.
|
||||
- **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, ``pydantic`` settings,
|
||||
- **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 integrating with
|
||||
- **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.
|
||||
- **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:
|
||||
|
||||
|
@ -37,7 +37,7 @@ The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/d
|
|||
|
||||
You need to specify how to assemble and where to inject the dependencies explicitly.
|
||||
|
||||
The power of the framework is in a simplicity.
|
||||
The power of the framework is in its simplicity.
|
||||
``Dependency Injector`` is a simple tool for the powerful concept.
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -1,12 +1,229 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
This document describes all the changes in *Dependency Injector* framework
|
||||
This document describes all the changes in *Dependency Injector* framework
|
||||
that were made in every particular version.
|
||||
|
||||
From version 0.7.6 *Dependency Injector* framework strictly
|
||||
From version 0.7.6 *Dependency Injector* framework strictly
|
||||
follows `Semantic versioning`_
|
||||
|
||||
4.46.0
|
||||
------
|
||||
|
||||
- Add option to disable env var interpolation in configs (`#861 <https://github.com/ets-labs/python-dependency-injector/pull/861>`_)
|
||||
- Fix ``Closing`` dependency resolution (`#852 <https://github.com/ets-labs/python-dependency-injector/pull/852>`_)
|
||||
- Add support for ``inspect.iscoroutinefunction()`` in ``Coroutine`` provider (`#830 <https://github.com/ets-labs/python-dependency-injector/pull/830>`_)
|
||||
- Fix broken wiring of sync inject-decorated methods (`#673 <https://github.com/ets-labs/python-dependency-injector/pull/673>`_)
|
||||
- Add support for ``typing.Annotated`` (`#721 <https://github.com/ets-labs/python-dependency-injector/pull/721>`_, `#853 <https://github.com/ets-labs/python-dependency-injector/pull/853>`_)
|
||||
- Documentation updates for movie-lister example (`#747 <https://github.com/ets-labs/python-dependency-injector/pull/747>`_)
|
||||
- Fix type propagation in ``Provider.provider`` (`#744 <https://github.com/ets-labs/python-dependency-injector/pull/744>`_)
|
||||
|
||||
Many thanks for the contributions to:
|
||||
- `ZipFile <https://github.com/ZipFile>`_
|
||||
- `Yegor Statkevich <https://github.com/jazzthief>`_
|
||||
- `Federico Tomasi <https://github.com/federinik>`_
|
||||
- `Martin Lafrance <https://github.com/martlaf>`_
|
||||
- `Philip Bjorge <https://github.com/philipbjorge>`_
|
||||
- `Ilya Kazakov <https://github.com/mrKazzila>`_
|
||||
|
||||
4.45.0
|
||||
--------
|
||||
- Add Starlette lifespan handler implementation (`#683 <https://github.com/ets-labs/python-dependency-injector/pull/683>`_).
|
||||
- Raise exception in ``ThreadLocalSingleton`` instead of hiding it in finally (`#845 <https://github.com/ets-labs/python-dependency-injector/pull/845>`_).
|
||||
- Improve debuggability of ``deepcopy`` errors (`#839 <https://github.com/ets-labs/python-dependency-injector/pull/839>`_).
|
||||
- Update examples (`#838 <https://github.com/ets-labs/python-dependency-injector/pull/838>`_).
|
||||
- Upgrade testing dependencies (`#837 <https://github.com/ets-labs/python-dependency-injector/pull/837>`_).
|
||||
- Add minor fixes to the documentation (`#709 <https://github.com/ets-labs/python-dependency-injector/pull/709>`_).
|
||||
- Remove ``six`` from the dependencies (`3ba4704 <https://github.com/ets-labs/python-dependency-injector/commit/3ba4704bc1cb00310749fd2eda0c8221167c313c>`_).
|
||||
|
||||
Many thanks for the contributions to:
|
||||
- `ZipFile <https://github.com/ZipFile>`_
|
||||
- `František Trebuňa <https://github.com/gortibaldik>`_
|
||||
- `JC (Jonathan Chen) <https://github.com/dijonkitchen>`_
|
||||
|
||||
4.44.0
|
||||
--------
|
||||
- Implement support for Pydantic 2. PR: `#832 <https://github.com/ets-labs/python-dependency-injector/pull/832>`_.
|
||||
- Implement `PEP-517 <https://peps.python.org/pep-0517/>`_, `PEP-518 <https://peps.python.org/pep-0518/>`_, and
|
||||
`PEP-621 <https://peps.python.org/pep-0621/>`_. PR: `#829 <https://github.com/ets-labs/python-dependency-injector/pull/829>`_.
|
||||
|
||||
Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
|
||||
|
||||
4.43.0
|
||||
--------
|
||||
- Add support for Python 3.13.
|
||||
- Migrate to Cython 3 (version 3.0.11). Many thanks to `ZipFile <https://github.com/ZipFile>`_ for
|
||||
this contribution `#813 <https://github.com/ets-labs/python-dependency-injector/pull/813>`_.
|
||||
|
||||
4.42.0
|
||||
--------
|
||||
- Promote release ``4.42.0b1`` to a production release.
|
||||
- Fix the Disqus comment widget.
|
||||
|
||||
4.42.0b1
|
||||
--------
|
||||
|
||||
- Add support of Python 3.12.
|
||||
- Drop support of Python 2.7, 3.5, and 3.6.
|
||||
- Regenerate C sources using Cython 0.29.37.
|
||||
- Update ``cibuildwheel`` to version ``2.20.0``.
|
||||
|
||||
4.41.0
|
||||
------
|
||||
- Add support of Python 3.11.
|
||||
- Allow Closing to detect dependent resources `#633 <https://github.com/ets-labs/python-dependency-injector/issues/633>`_,
|
||||
`#636 <https://github.com/ets-labs/python-dependency-injector/pull/636>`_. Thanks `Jamie Stumme @StummeJ <https://github.com/StummeJ>`_
|
||||
for the contribution.
|
||||
- Update CI/CD to use Ubuntu 22.04.
|
||||
- Update CI/CD to ``actions/checkout@v3``, ``actions/setup-python@v4``, ``actions/upload-artifact@v3``, ``pypa/cibuildwheel@v2.11.3``,
|
||||
and ``actions/download-artifact@v3``.
|
||||
- Fix install crash on non-utf8 systems `#644 <https://github.com/ets-labs/python-dependency-injector/pull/644>`_.
|
||||
- Fix a bug in Windows build with default charset `#635 <https://github.com/ets-labs/python-dependency-injector/pull/635>`_.
|
||||
- Update FastAPI Redis example to use ``aioredis`` version 2 `#613 <https://github.com/ets-labs/python-dependency-injector/pull/613>`_.
|
||||
- Update documentation on creating custom providers `#598 <https://github.com/ets-labs/python-dependency-injector/pull/598>`_.
|
||||
- Regenerate C sources using Cython 0.29.32.
|
||||
- Fix builds badge.
|
||||
|
||||
4.40.0
|
||||
------
|
||||
- Add ``Configuration.from_json()`` method to load configuration from a json file.
|
||||
- Fix bug with wiring not working properly with functions double wrapped by ``@functools.wraps`` decorator.
|
||||
See issue: `#454 <https://github.com/ets-labs/python-dependency-injector/issues/454>`_.
|
||||
Many thanks to: `@platipo <https://github.com/platipo>`_, `@MatthieuMoreau0 <https://github.com/MatthieuMoreau0>`_,
|
||||
`@fabiocerqueira <https://github.com/fabiocerqueira>`_, `@Jitesh-Khuttan <https://github.com/Jitesh-Khuttan>`_.
|
||||
- Refactor wiring module to store all patched callable data in the ``PatchedRegistry``.
|
||||
- Improve wording on the "Dependency injection and inversion of control in Python" docs page.
|
||||
- Add documentation on the ``@inject`` decorator.
|
||||
- Update typing in the main example and cohesion/coupling correlation definition in
|
||||
"Dependency injection and inversion of control in Python".
|
||||
Thanks to `@illia-v (Illia Volochii) <https://github.com/illia-v>`_ for the
|
||||
PR (`#580 <https://github.com/ets-labs/python-dependency-injector/pull/580>`_).
|
||||
- Update copyright year.
|
||||
- Enable skipped test ``test_schema_with_boto3_session()``.
|
||||
- Update pytest configuration.
|
||||
- Regenerate C sources using Cython 0.29.30.
|
||||
|
||||
4.39.1
|
||||
------
|
||||
- Fix bug `#574 <https://github.com/ets-labs/python-dependency-injector/issues/574>`_:
|
||||
"``@inject`` breaks ``inspect.iscoroutinefunction``". Thanks to
|
||||
`@burritoatspoton (Rafał Burczyński) <https://github.com/burritoatspoton>`_ for reporting the issue.
|
||||
|
||||
4.39.0
|
||||
------
|
||||
- Optimize injections and wiring from x1.5 to x7 times depending on the use case.
|
||||
- Fix bug `#569 <https://github.com/ets-labs/python-dependency-injector/issues/569>`_:
|
||||
"numpy.typing.NDArray breaks wiring". Thanks to
|
||||
`@VKFisher (Vlad Fisher) <https://github.com/VKFisher>`_ for reporting the issue and providing a fix.
|
||||
|
||||
4.38.0
|
||||
------
|
||||
- Add new provider ``Aggregate``. It is a generalized version of ``FactoryAggregate`` that
|
||||
can contain providers of any type, not only ``Factory``. See issue
|
||||
`#530 <https://github.com/ets-labs/python-dependency-injector/issues/530>`_. Thanks to
|
||||
`@zerlok (Danil Troshnev) <https://github.com/zerlok>`_ for suggesting the feature.
|
||||
- Add argument ``as_`` to the ``config.from_env()`` method for the explicit type casting
|
||||
of an environment variable value, e.g.: ``config.timeout.from_env("TIMEOUT", as_=int)``.
|
||||
See issue `#533 <https://github.com/ets-labs/python-dependency-injector/issues/533>`_. Thanks to
|
||||
`@gtors (Andrey Torsunov) <https://github.com/gtors>`_ for suggesting the feature.
|
||||
- Add ``.providers`` attribute to the ``FactoryAggregate`` provider. It is an alias for
|
||||
``FactoryAggregate.factories`` attribute.
|
||||
- Add ``.set_providers()`` method to the ``FactoryAggregate`` provider. It is an alias for
|
||||
``FactoryAggregate.set_factories()`` method.
|
||||
- Add string imports for ``Factory``, ``Singleton``, ``Callable``, ``Resource``, and ``Coroutine``
|
||||
providers, e.g. ``Factory("module.Class")``.
|
||||
See issue `#531 <https://github.com/ets-labs/python-dependency-injector/issues/531>`_.
|
||||
Thanks to `@al-stefanitsky-mozdor <https://github.com/al-stefanitsky-mozdor>`_ for suggesting the feature.
|
||||
- Fix ``Dependency`` provider to don't raise "Dependency is not defined" error when the ``default``
|
||||
is a falsy value of proper type.
|
||||
See issue `#550 <https://github.com/ets-labs/python-dependency-injector/issues/550>`_. Thanks to
|
||||
`@approxit <https://github.com/approxit>`_ for reporting the issue.
|
||||
- Refactor ``FactoryAggregate`` provider internals.
|
||||
- Update logo on Github and in docs to support dark themes and remove some imperfections.
|
||||
|
||||
4.37.0
|
||||
------
|
||||
- Add support of Python 3.10.
|
||||
- Improve wiring with adding importing modules and packages from a string
|
||||
``container.wire(modules=["yourapp.module1"])``.
|
||||
- Add container wiring configuration ``wiring_config = containers.WiringConfiguration()``.
|
||||
- Add support of ``with`` statement for ``container.override_providers()`` method.
|
||||
- Add ``Configuration(yaml_files=[...])`` argument.
|
||||
- Add ``Configuration(ini_files=[...])`` argument.
|
||||
- Add ``Configuration(pydantic_settings=[...])`` argument.
|
||||
- Drop support of Python 3.4. There are no immediate breaking changes, but Dependency Injector
|
||||
will no longer be tested on Python 3.4 and any bugs will not be fixed.
|
||||
- Announce the date of dropping Python 3.5 support (Jan 1st 2022).
|
||||
- Fix ``Dependency.is_defined`` attribute to always return boolean value.
|
||||
- Fix ``envs_required=False`` behavior in ``Configuration.from_*()`` methods
|
||||
to give a priority to the explicitly provided value.
|
||||
- Update documentation and fix typos.
|
||||
- Regenerate C sources using Cython 0.29.24.
|
||||
- Migrate tests to ``pytest``.
|
||||
|
||||
4.36.2
|
||||
------
|
||||
- Update docs.
|
||||
|
||||
4.36.1
|
||||
------
|
||||
- Fix a wiring bug with improper resolving of ``Provide[some_provider.provider]``.
|
||||
- Fix a typo in ``Factory`` provider docs ``service.add_attributes(clent=client)``
|
||||
`#499 <https://github.com/ets-labs/python-dependency-injector/issues/499>`_.
|
||||
Thanks to `@rajanjha786 <https://github.com/rajanjha786>`_ for the contribution.
|
||||
- Fix a typo in ``boto3`` example
|
||||
`#511 <https://github.com/ets-labs/python-dependency-injector/issues/511>`_.
|
||||
Thanks to `@whysage <https://github.com/whysage>`_ for the contribution.
|
||||
|
||||
4.36.0
|
||||
------
|
||||
- Add support of non-string keys for ``FactoryAggregate`` provider.
|
||||
- Improve ``FactoryAggregate`` typing stub.
|
||||
- Improve resource subclasses typing and make shutdown definition optional
|
||||
`PR #492 <https://github.com/ets-labs/python-dependency-injector/pull/492>`_.
|
||||
Thanks to `@EdwardBlair <https://github.com/EdwardBlair>`_ for suggesting the improvement.
|
||||
- Fix type annotations for ``.provides``.
|
||||
Thanks to `Thiago Hiromi @thiromi <https://github.com/thiromi>`_ for the fix
|
||||
`PR #491 <https://github.com/ets-labs/python-dependency-injector/pull/491>`_.
|
||||
- Fix environment variables interpolation examples in configuration provider docs ``{$ENV} -> ${ENV}``.
|
||||
Thanks to `Felipe Rubio @krouw <https://github.com/krouw>`_ for reporting the issue and
|
||||
fixing yaml example `PR #494 <https://github.com/ets-labs/python-dependency-injector/pull/494>`_.
|
||||
- Fix ``@containers.copy()`` decorator to respect dependencies on parent providers.
|
||||
See issue `#477 <https://github.com/ets-labs/python-dependency-injector/issues/477>`_.
|
||||
Thanks to `Andrey Torsunov @gtors <https://github.com/gtors>`_ for reporting the issue.
|
||||
- Fix typing stub for ``container.override_providers()`` to accept other types besides ``Provider``.
|
||||
- Fix runtime issue with generic typing in resource initializer classes ``resources.Resource``
|
||||
and ``resources.AsyncResource``.
|
||||
See issue `#488 <https://github.com/ets-labs/python-dependency-injector/issues/488>`_.
|
||||
Thanks to `@EdwardBlair <https://github.com/EdwardBlair>`_ for reporting the issue.
|
||||
|
||||
4.35.3
|
||||
------
|
||||
- *This release was removed from PyPI. It was inconsistently published because project has
|
||||
reached a PyPI size limit. Changes from this release are published on PyPI in next version.*
|
||||
|
||||
4.35.2
|
||||
------
|
||||
- Update wiring to support modules provided as packages.
|
||||
See issue `#481 <https://github.com/ets-labs/python-dependency-injector/issues/481>`_.
|
||||
Thanks to `@Sadbot <https://github.com/Sadbot>`_ for demonstrating the issue.
|
||||
|
||||
4.35.1
|
||||
------
|
||||
- Fix a container issue with supporting custom string types.
|
||||
See issue `#479 <https://github.com/ets-labs/python-dependency-injector/issues/479>`_.
|
||||
Thanks to `@ilsurih <https://github.com/ilsurih>`_ for reporting the issue.
|
||||
|
||||
4.35.0
|
||||
------
|
||||
- Add support of six 1.16.0.
|
||||
|
||||
4.34.2
|
||||
------
|
||||
- Fix a bug with reverse shutdown order in ``container.shutdown_resources()``.
|
||||
See issue `#432 <https://github.com/ets-labs/python-dependency-injector/issues/432>`_.
|
||||
Thanks to `Saulius Beinorius <https://github.com/saulbein>`_ for bringing up the issue.
|
||||
|
||||
4.34.1
|
||||
------
|
||||
- Update ``container.shutdown_resources()`` to respect dependencies order while shutdown.
|
||||
|
@ -1154,24 +1371,24 @@ Misc:
|
|||
------
|
||||
- Add ``DependenciesContainer`` provider.
|
||||
- Add "use_cases" example miniapp.
|
||||
- Update documentation requirements to use fixed version of
|
||||
- Update documentation requirements to use fixed version of
|
||||
``sphinxcontrib-disqus``.
|
||||
|
||||
|
||||
3.9.1
|
||||
-----
|
||||
- Fix docs build problem (``sphinx`` is frozen on ``1.5.6`` version because of
|
||||
incompatibility with ``sphinxcontrib-discus``).
|
||||
incompatibility with ``sphinxcontrib-discus``).
|
||||
- Add badge for docs.
|
||||
|
||||
3.9.0
|
||||
-----
|
||||
- Change initialization of declarative container, so it accepts overriding
|
||||
providers as keyword arguments -
|
||||
- Change initialization of declarative container, so it accepts overriding
|
||||
providers as keyword arguments -
|
||||
``DeclarativeContainer(**overriding_providers)``.
|
||||
- Add method to dynamic catalog for setting groups of providers -
|
||||
- Add method to dynamic catalog for setting groups of providers -
|
||||
``DynamicContainer.set_providers(**providers)``.
|
||||
- Add method to dynamic catalog for overriding groups of providers -
|
||||
- Add method to dynamic catalog for overriding groups of providers -
|
||||
``DynamicContainer.set_providers(**overriding_providers)``.
|
||||
- Rename ``ExternalDependency`` provider to ``Dependency``.
|
||||
- Add default value for ``instance_of`` argument of ``Dependency`` provider -
|
||||
|
@ -1203,7 +1420,7 @@ Misc:
|
|||
3.7.0
|
||||
-----
|
||||
- Add ``FactoryAggregate`` provider.
|
||||
- Add ``Provider.provider`` dynamic attribute that return new provider's
|
||||
- Add ``Provider.provider`` dynamic attribute that return new provider's
|
||||
delegate (alias of method ``Provider.delegate()``).
|
||||
- Add support of six 1.11.0.
|
||||
- Regenerate C sources using Cython 0.27.1.
|
||||
|
@ -1220,7 +1437,7 @@ Misc:
|
|||
|
||||
3.5.0
|
||||
-----
|
||||
- Add functionality for initializing ``Configuration`` provider with default
|
||||
- Add functionality for initializing ``Configuration`` provider with default
|
||||
values.
|
||||
|
||||
3.4.8
|
||||
|
@ -1243,7 +1460,7 @@ Misc:
|
|||
|
||||
3.4.4
|
||||
-----
|
||||
- Add ``Provider.last_overriding`` read-only property that points to last
|
||||
- Add ``Provider.last_overriding`` read-only property that points to last
|
||||
overriding provider, if any. If target provider is not overridden, ``None``
|
||||
would be returned.
|
||||
- Update example of writing custom providers.
|
||||
|
@ -1257,7 +1474,7 @@ Misc:
|
|||
3.4.2
|
||||
-----
|
||||
- Make ``Provider`` overriding methods thread safe:
|
||||
``Provider.override(provider)``, ``Provider.reset_last_overriding()``,
|
||||
``Provider.override(provider)``, ``Provider.reset_last_overriding()``,
|
||||
``Provider.reset_override()``.
|
||||
- Refactor storage locking of ``ThreadSafeSingleton`` provider.
|
||||
- Fix few ``pydocstyle`` errors in examples.
|
||||
|
@ -1329,8 +1546,8 @@ Misc:
|
|||
|
||||
3.2.4
|
||||
-----
|
||||
- Switch to single version of documentation for getting shorter urls (without
|
||||
``/en/stable/``). Add appropriate redirects for compatibility with previous
|
||||
- Switch to single version of documentation for getting shorter urls (without
|
||||
``/en/stable/``). Add appropriate redirects for compatibility with previous
|
||||
links.
|
||||
- Update copyright date.
|
||||
|
||||
|
@ -1349,7 +1566,7 @@ Misc:
|
|||
|
||||
3.2.0
|
||||
-----
|
||||
- Add ``Configuration`` provider for late static binding of configuration
|
||||
- Add ``Configuration`` provider for late static binding of configuration
|
||||
options.
|
||||
|
||||
3.1.5
|
||||
|
@ -1359,7 +1576,7 @@ Misc:
|
|||
|
||||
3.1.4
|
||||
-----
|
||||
- Move ``inline`` functions from class level to module level for removing them
|
||||
- Move ``inline`` functions from class level to module level for removing them
|
||||
from virtual table and enable inlining.
|
||||
|
||||
3.1.3
|
||||
|
@ -1391,34 +1608,34 @@ Misc:
|
|||
|
||||
- **Providers**
|
||||
|
||||
1. All providers from ``dependency_injector.providers`` package are
|
||||
1. All providers from ``dependency_injector.providers`` package are
|
||||
implemented as C extension types using Cython.
|
||||
2. Add ``BaseSingleton`` super class for all singleton providers.
|
||||
3. Make ``Singleton`` provider not thread-safe. It makes performance of
|
||||
3. Make ``Singleton`` provider not thread-safe. It makes performance of
|
||||
``Singleton`` provider 10x times faster.
|
||||
4. Add ``ThreadSafeSingleton`` provider - thread-safe version of
|
||||
4. Add ``ThreadSafeSingleton`` provider - thread-safe version of
|
||||
``Singleton`` provider.
|
||||
5. Add ``ThreadLocalSingleton`` provider - ``Singleton`` provider that uses
|
||||
5. Add ``ThreadLocalSingleton`` provider - ``Singleton`` provider that uses
|
||||
thread-local storage.
|
||||
6. Remove ``provides`` attribute from ``Factory`` and ``Singleton``
|
||||
6. Remove ``provides`` attribute from ``Factory`` and ``Singleton``
|
||||
providers.
|
||||
7. Add ``set_args()`` and ``clear_args()`` methods for ``Callable``,
|
||||
7. Add ``set_args()`` and ``clear_args()`` methods for ``Callable``,
|
||||
``Factory`` and ``Singleton`` providers.
|
||||
|
||||
- **Containers**
|
||||
|
||||
1. Module ``dependency_injector.containers`` was split into submodules
|
||||
1. Module ``dependency_injector.containers`` was split into submodules
|
||||
without any functional changes.
|
||||
|
||||
- **Utils**
|
||||
|
||||
1. Module ``dependency_injector.utils`` is split into
|
||||
1. Module ``dependency_injector.utils`` is split into
|
||||
``dependency_injector.containers`` and ``dependency_injector.providers``.
|
||||
|
||||
- **Miscellaneous**
|
||||
|
||||
1. Remove ``@inject`` decorator.
|
||||
2. Add makefile (``clean``, ``test``, ``build``, ``install``, ``uninstall``
|
||||
2. Add makefile (``clean``, ``test``, ``build``, ``install``, ``uninstall``
|
||||
& ``publish`` commands).
|
||||
3. Update repository structure:
|
||||
|
||||
|
@ -1485,7 +1702,7 @@ Misc:
|
|||
|
||||
2.0.0
|
||||
------
|
||||
- Introduce new injections style for ``Callable``, ``Factory`` &
|
||||
- Introduce new injections style for ``Callable``, ``Factory`` &
|
||||
``Singleton`` providers.
|
||||
- Drop providers: ``Static``, ``Value``, ``Function``, ``Class``, ``Config``.
|
||||
- Increase performance of making injections in 2 times (+100%).
|
||||
|
@ -1498,8 +1715,8 @@ Misc:
|
|||
|
||||
1.17.0
|
||||
------
|
||||
- Add ``add_injections()`` method to ``Callable``, ``DelegatedCallable``,
|
||||
``Factory``, ``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton``
|
||||
- Add ``add_injections()`` method to ``Callable``, ``DelegatedCallable``,
|
||||
``Factory``, ``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton``
|
||||
providers.
|
||||
- Fix bug with accessing to declarative catalog attributes from instance level.
|
||||
|
||||
|
@ -1527,14 +1744,14 @@ Misc:
|
|||
- Add "Examples" section into documentation.
|
||||
- Add "Movie Lister" example.
|
||||
- Add "Services" example.
|
||||
- Move project documentation into organisation's domain
|
||||
- Move project documentation into organisation's domain
|
||||
(dependency-injector.ets-labs.org).
|
||||
|
||||
1.15.2
|
||||
------
|
||||
- [Refactoring] split ``catalogs`` module into smaller modules,
|
||||
- [Refactoring] split ``catalogs`` module into smaller modules,
|
||||
``catalogs`` module become a package.
|
||||
- [Refactoring] split ``providers`` module into smaller modules,
|
||||
- [Refactoring] split ``providers`` module into smaller modules,
|
||||
``providers`` module become a package.
|
||||
- Update introduction documentation.
|
||||
|
||||
|
@ -1544,7 +1761,7 @@ Misc:
|
|||
|
||||
1.15.0
|
||||
------
|
||||
- Add ``Provider.provide()`` method. ``Provider.__call__()`` become a
|
||||
- Add ``Provider.provide()`` method. ``Provider.__call__()`` become a
|
||||
reference to ``Provider.provide()``.
|
||||
- Add provider overriding context.
|
||||
- Update main examples and README.
|
||||
|
@ -1574,7 +1791,7 @@ Misc:
|
|||
|
||||
1.14.6
|
||||
------
|
||||
- Add ``cls`` alias for ``provides`` attributes of ``Factory``,
|
||||
- Add ``cls`` alias for ``provides`` attributes of ``Factory``,
|
||||
``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton`` providers.
|
||||
|
||||
1.14.5
|
||||
|
@ -1633,27 +1850,27 @@ Misc:
|
|||
|
||||
1.11.1
|
||||
------
|
||||
Previous state of *Dependency Injector* framework (0.11.0 version) is
|
||||
considered to be production ready / stable, so current release is considered
|
||||
Previous state of *Dependency Injector* framework (0.11.0 version) is
|
||||
considered to be production ready / stable, so current release is considered
|
||||
to be the first major release.
|
||||
|
||||
- Increase major version.
|
||||
- Increase major version.
|
||||
- Backward compatibility with all previous versions above 0.7.6 has been saved.
|
||||
|
||||
0.11.0
|
||||
------
|
||||
- Rename ``AbstractCatalog`` to ``DeclarativeCatalog``
|
||||
- Rename ``AbstractCatalog`` to ``DeclarativeCatalog``
|
||||
(with backward compatibility).
|
||||
- Rename ``catalog`` module to ``catalogs`` with backward compatibility.
|
||||
- Implement dynamic binding of providers for ``DeclarativeCatalog``.
|
||||
- Add ``DynamicCatalog``.
|
||||
- Change restrictions for providers-to-catalogs bindings - provider could be
|
||||
- Change restrictions for providers-to-catalogs bindings - provider could be
|
||||
bound to several catalogs with different names.
|
||||
- Restrict overriding of providers by themselves.
|
||||
- Restrict overriding of catalogs by themselves.
|
||||
- Make ``DeclarativeCatalog.last_overriding`` attribute to be ``None`` by
|
||||
- Make ``DeclarativeCatalog.last_overriding`` attribute to be ``None`` by
|
||||
default.
|
||||
- Make ``Provider.last_overriding`` attribute to be ``None`` by
|
||||
- Make ``Provider.last_overriding`` attribute to be ``None`` by
|
||||
default.
|
||||
- Refactor catalogs and providers modules.
|
||||
- Add API documentation
|
||||
|
@ -1661,7 +1878,7 @@ to be the first major release.
|
|||
|
||||
0.10.5
|
||||
------
|
||||
- Add more representable implementation for ``AbstractCatalog`` and
|
||||
- Add more representable implementation for ``AbstractCatalog`` and
|
||||
``AbstractCatalog.Bundle``.
|
||||
|
||||
0.10.4
|
||||
|
@ -1685,17 +1902,17 @@ to be the first major release.
|
|||
- Add functionality for creating ``AbstractCatalog`` provider bundles.
|
||||
- Improve ``AbstractCatalog`` inheritance.
|
||||
- Improve ``AbstractCatalog`` overriding.
|
||||
- Add images for catalog "Writing catalogs" and "Operating with catalogs"
|
||||
- Add images for catalog "Writing catalogs" and "Operating with catalogs"
|
||||
examples.
|
||||
- Add functionality for using positional argument injections with
|
||||
``Factory``, ``Singleton``, ``Callable`` providers and
|
||||
- Add functionality for using positional argument injections with
|
||||
``Factory``, ``Singleton``, ``Callable`` providers and
|
||||
``inject`` decorator.
|
||||
- Add functionality for decorating classes with ``@inject``.
|
||||
- Add ``Singleton.injections`` attribute that represents a tuple of all
|
||||
- Add ``Singleton.injections`` attribute that represents a tuple of all
|
||||
``Singleton`` injections (including args, kwargs, attributes and methods).
|
||||
- Add ``Callable.injections`` attribute that represents a tuple of all
|
||||
- Add ``Callable.injections`` attribute that represents a tuple of all
|
||||
``Callable`` injections (including args and kwargs).
|
||||
- Add optimization for ``Injection.value`` property that will compute
|
||||
- Add optimization for ``Injection.value`` property that will compute
|
||||
type of injection once, instead of doing this on every call.
|
||||
- Add ``VERSION`` constant for verification of currently installed version.
|
||||
- Add support of Python 3.5.
|
||||
|
@ -1705,7 +1922,7 @@ to be the first major release.
|
|||
0.9.5
|
||||
-----
|
||||
- Change provider attributes scope to public.
|
||||
- Add ``Factory.injections`` attribute that represents a tuple of all
|
||||
- Add ``Factory.injections`` attribute that represents a tuple of all
|
||||
``Factory`` injections (including kwargs, attributes and methods).
|
||||
|
||||
0.9.4
|
||||
|
@ -1722,14 +1939,14 @@ to be the first major release.
|
|||
|
||||
0.9.1
|
||||
-----
|
||||
- Add simplified syntax of kwarg injections for ``di.Factory`` and
|
||||
``di.Singleton`` providers:
|
||||
- Add simplified syntax of kwarg injections for ``di.Factory`` and
|
||||
``di.Singleton`` providers:
|
||||
``di.Factory(SomeClass, dependency1=injectable_provider_or_value)``.
|
||||
- Add simplified syntax of kwarg injections for ``di.Callable`` provider:
|
||||
``di.Callable(some_callable, dependency1=injectable_provider_or_value)``
|
||||
- Add simplified syntax of kwarg injections for ``@di.inject`` decorator:
|
||||
``@di.inject(dependency1=injectable_provider_or_value)``.
|
||||
- Optimize ``@di.inject()`` decorations when they were made several times for
|
||||
- Optimize ``@di.inject()`` decorations when they were made several times for
|
||||
the same callback.
|
||||
- Add minor refactorings.
|
||||
- Fix of minor documentation issues.
|
||||
|
@ -1749,21 +1966,21 @@ to be the first major release.
|
|||
0.7.6
|
||||
-----
|
||||
|
||||
- Adding support of six from 1.7.0 to 1.9.0.
|
||||
- Factory / Singleton providers are free from restriction to operate with
|
||||
classes only. This feature gives a change to use factory method and
|
||||
- Adding support of six from 1.7.0 to 1.9.0.
|
||||
- Factory / Singleton providers are free from restriction to operate with
|
||||
classes only. This feature gives a change to use factory method and
|
||||
functions with Factory / Singleton providers.
|
||||
- All attributes of all entities that have to be protected was renamed using
|
||||
``_protected`` manner.
|
||||
- Providers extending was improved by implementing overriding logic in
|
||||
``Provider.__call__()`` and moving providing logic into
|
||||
- All attributes of all entities that have to be protected was renamed using
|
||||
``_protected`` manner.
|
||||
- Providers extending was improved by implementing overriding logic in
|
||||
``Provider.__call__()`` and moving providing logic into
|
||||
``Provider._provide()``.
|
||||
- ``NewInstance`` provider was renamed to ``Factory`` provider.
|
||||
``NewInstance`` still can be used, but it considered to be deprecated and
|
||||
- ``NewInstance`` provider was renamed to ``Factory`` provider.
|
||||
``NewInstance`` still can be used, but it considered to be deprecated and
|
||||
will be removed in further releases.
|
||||
- ``@inject`` decorator was refactored to keep all injections in
|
||||
- ``@inject`` decorator was refactored to keep all injections in
|
||||
``_injections`` attribute of decorated callback. It will give a possibility to
|
||||
track all the injections of particular callbacks and gives some performance
|
||||
track all the injections of particular callbacks and gives some performance
|
||||
boost due minimizing number of calls for doing injections.
|
||||
- A lot of documentation updates were made.
|
||||
- A lot of examples were added.
|
||||
|
|
72
docs/providers/aggregate.rst
Normal file
72
docs/providers/aggregate.rst
Normal file
|
@ -0,0 +1,72 @@
|
|||
.. _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::
|
|
@ -45,14 +45,29 @@ where ``examples/providers/configuration/config.ini`` is:
|
|||
.. literalinclude:: ../../examples/providers/configuration/config.ini
|
||||
:language: ini
|
||||
|
||||
Alternatively, you can provide a path to the INI file over the configuration provider argument. In that case,
|
||||
the container will call ``config.from_ini()`` automatically:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(ini_files=["./config.ini"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # Config is loaded from ./config.ini
|
||||
|
||||
|
||||
:py:meth:`Configuration.from_ini` method supports environment variables interpolation.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[section]
|
||||
option1 = {$ENV_VAR}
|
||||
option2 = {$ENV_VAR}/path
|
||||
option3 = {$ENV_VAR:default}
|
||||
option1 = ${ENV_VAR}
|
||||
option2 = ${ENV_VAR}/path
|
||||
option3 = ${ENV_VAR:default}
|
||||
|
||||
See also: :ref:`configuration-envs-interpolation`.
|
||||
|
||||
|
@ -72,14 +87,28 @@ where ``examples/providers/configuration/config.yml`` is:
|
|||
.. literalinclude:: ../../examples/providers/configuration/config.yml
|
||||
:language: ini
|
||||
|
||||
Alternatively, you can provide a path to the YAML file over the configuration provider argument. In that case,
|
||||
the container will call ``config.from_yaml()`` automatically:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(yaml_files=["./config.yml"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # Config is loaded from ./config.yml
|
||||
|
||||
:py:meth:`Configuration.from_yaml` method supports environment variables interpolation.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
section:
|
||||
option1: {$ENV_VAR}
|
||||
option2: {$ENV_VAR}/path
|
||||
option3: {$ENV_VAR:default}
|
||||
option1: ${ENV_VAR}
|
||||
option2: ${ENV_VAR}/path
|
||||
option3: ${ENV_VAR:default}
|
||||
|
||||
See also: :ref:`configuration-envs-interpolation`.
|
||||
|
||||
|
@ -91,7 +120,7 @@ To use another loader use ``loader`` argument:
|
|||
import yaml
|
||||
|
||||
|
||||
container.config.from_yaml('config.yml', loader=yaml.UnsafeLoader)
|
||||
container.config.from_yaml("config.yml", loader=yaml.UnsafeLoader)
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -107,38 +136,102 @@ To use another loader use ``loader`` argument:
|
|||
|
||||
*Don't forget to mirror the changes in the requirements file.*
|
||||
|
||||
Loading from a JSON file
|
||||
------------------------
|
||||
|
||||
``Configuration`` provider can load configuration from a ``json`` file using the
|
||||
:py:meth:`Configuration.from_json` method:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/configuration_json.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 12
|
||||
|
||||
where ``examples/providers/configuration/config.json`` is:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/config.json
|
||||
:language: json
|
||||
|
||||
Alternatively, you can provide a path to a json file over the configuration provider argument. In that case,
|
||||
the container will call ``config.from_json()`` automatically:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration(json_files=["./config.json"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # Config is loaded from ./config.json
|
||||
|
||||
:py:meth:`Configuration.from_json` method supports environment variables interpolation.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"section": {
|
||||
"option1": "${ENV_VAR}",
|
||||
"option2": "${ENV_VAR}/path",
|
||||
"option3": "${ENV_VAR:default}"
|
||||
}
|
||||
}
|
||||
|
||||
See also: :ref:`configuration-envs-interpolation`.
|
||||
|
||||
Loading from a Pydantic settings
|
||||
--------------------------------
|
||||
|
||||
``Configuration`` provider can load configuration from a ``pydantic`` settings object using the
|
||||
``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: 31
|
||||
:emphasize-lines: 32
|
||||
|
||||
To get the data from pydantic settings ``Configuration`` provider calls ``Settings.dict()`` method.
|
||||
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'})
|
||||
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`` by default.
|
||||
``Dependency Injector`` doesn't install ``pydantic-settings`` by default.
|
||||
|
||||
You can install the ``Dependency Injector`` with an extra dependency::
|
||||
|
||||
pip install dependency-injector[pydantic]
|
||||
pip install dependency-injector[pydantic2]
|
||||
|
||||
or install ``pydantic`` directly::
|
||||
or install ``pydantic-settings`` directly::
|
||||
|
||||
pip install pydantic
|
||||
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
|
||||
-------------------------
|
||||
|
||||
|
@ -161,6 +254,24 @@ Loading from an environment variable
|
|||
:lines: 3-
|
||||
:emphasize-lines: 18-20
|
||||
|
||||
You can use ``as_`` argument for the type casting of an environment variable value:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 2,6,10
|
||||
|
||||
# API_KEY=secret
|
||||
container.config.api_key.from_env("API_KEY", as_=str, required=True)
|
||||
assert container.config.api_key() == "secret"
|
||||
|
||||
# SAMPLING_RATIO=0.5
|
||||
container.config.sampling.from_env("SAMPLING_RATIO", as_=float, required=True)
|
||||
assert container.config.sampling() == 0.5
|
||||
|
||||
# TIMEOUT undefined, default is used
|
||||
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
|
||||
assert container.config.timeout() == 5
|
||||
|
||||
|
||||
Loading a value
|
||||
---------------
|
||||
|
||||
|
@ -208,7 +319,7 @@ variable ``ENV_NAME`` is undefined, configuration provider will substitute value
|
|||
.. code-block:: ini
|
||||
|
||||
[section]
|
||||
option = {$ENV_NAME:default}
|
||||
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()``.
|
||||
|
@ -225,7 +336,7 @@ undefined environment variable that doesn't have a default value, pass argument
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_yaml('config.yml', envs_required=True)
|
||||
container.config.from_yaml("config.yml", envs_required=True)
|
||||
|
||||
See also: :ref:`configuration-strict-mode`.
|
||||
|
||||
|
@ -255,6 +366,19 @@ See also: :ref:`configuration-strict-mode`.
|
|||
|
||||
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
|
||||
------------------------------
|
||||
|
||||
|
@ -270,13 +394,13 @@ Mandatory YAML file:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_yaml('config.yaml', required=True)
|
||||
container.config.from_yaml("config.yaml", required=True)
|
||||
|
||||
Mandatory INI file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_ini('config.ini', required=True)
|
||||
container.config.from_ini("config.ini", required=True)
|
||||
|
||||
Mandatory dictionary:
|
||||
|
||||
|
@ -288,7 +412,7 @@ Mandatory environment variable:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.api_key.from_env('API_KEY', required=True)
|
||||
container.config.api_key.from_env("API_KEY", required=True)
|
||||
|
||||
See also: :ref:`configuration-strict-mode`.
|
||||
|
||||
|
@ -346,16 +470,16 @@ configuration data is undefined:
|
|||
config = providers.Configuration(strict=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
try:
|
||||
container.config.from_yaml('does-not_exist.yml') # raise exception
|
||||
container.config.from_yaml("does-not_exist.yml") # raise exception
|
||||
except FileNotFoundError:
|
||||
...
|
||||
|
||||
try:
|
||||
container.config.from_ini('does-not_exist.ini') # raise exception
|
||||
container.config.from_ini("does-not_exist.ini") # raise exception
|
||||
except FileNotFoundError:
|
||||
...
|
||||
|
||||
|
@ -365,7 +489,7 @@ configuration data is undefined:
|
|||
...
|
||||
|
||||
try:
|
||||
container.config.from_env('UNDEFINED_ENV_VAR') # raise exception
|
||||
container.config.from_env("UNDEFINED_ENV_VAR") # raise exception
|
||||
except ValueError:
|
||||
...
|
||||
|
||||
|
@ -380,12 +504,12 @@ an undefined environment variable without a default value.
|
|||
.. code-block:: ini
|
||||
|
||||
section:
|
||||
option: {$UNDEFINED}
|
||||
option: ${UNDEFINED}
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
try:
|
||||
container.config.from_yaml('undefined_env.yml') # raise exception
|
||||
container.config.from_yaml("undefined_env.yml") # raise exception
|
||||
except ValueError:
|
||||
...
|
||||
|
||||
|
@ -398,11 +522,11 @@ You can override ``.from_*()`` methods behaviour in strict mode using ``required
|
|||
config = providers.Configuration(strict=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.from_yaml('config.local.yml', required=False)
|
||||
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.
|
||||
|
|
|
@ -16,10 +16,11 @@ To create a custom provider you need to follow these rules:
|
|||
1. New provider class should inherit :py:class:`Provider`.
|
||||
2. You need to implement the ``Provider._provide()`` method.
|
||||
3. You need to implement the ``Provider.__deepcopy__()`` method. It should return an
|
||||
equivalent copy of a provider. All providers must be copied with a ``deepcopy()`` function
|
||||
from the ``providers`` module. After the a new provider object is created use
|
||||
``Provider._copy_overriding()`` method to copy all overriding providers. See the example
|
||||
below.
|
||||
equivalent copy of a provider. All providers must be copied with the ``deepcopy()`` function
|
||||
from the ``providers`` module. It's essential to pass ``memo`` into ``deepcopy`` in order to keep
|
||||
the preconfigured ``args`` and ``kwargs`` of stored providers. After the a new provider object
|
||||
is created, use ``Provider._copy_overriding()`` method to copy all overriding providers. See the
|
||||
example below.
|
||||
4. If new provider has a ``__init__()`` method, it should call the parent
|
||||
``Provider.__init__()``.
|
||||
5. If new provider stores any other providers, these providers should be listed in
|
||||
|
@ -33,7 +34,7 @@ To create a custom provider you need to follow these rules:
|
|||
.. note::
|
||||
1. Prefer delegation over inheritance. If you choose between inheriting a ``Factory`` or
|
||||
inheriting a ``Provider`` and use ``Factory`` internally - the last is better.
|
||||
2. When create a new provider follow the ``Factory``-like injections style. Consistency matters.
|
||||
2. When creating a new provider follow the ``Factory``-like injections style. Consistency matters.
|
||||
3. Use the ``__slots__`` attribute to make sure nothing could be attached to your provider. You
|
||||
will also save some memory.
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@ To use non-string keys or keys with ``.`` and ``-`` provide a dictionary as a po
|
|||
|
||||
providers.Dict({
|
||||
SomeClass: providers.Factory(...),
|
||||
'key.with.periods': providers.Factory(...),
|
||||
'key-with-dashes': providers.Factory(...),
|
||||
"key.with.periods": providers.Factory(...),
|
||||
"key-with-dashes": providers.Factory(...),
|
||||
})
|
||||
|
||||
Example:
|
||||
|
|
|
@ -110,6 +110,45 @@ attribute of the provider that you're going to inject.
|
|||
|
||||
.. note:: Any provider has a ``.provider`` attribute.
|
||||
|
||||
.. _factory-string-imports:
|
||||
|
||||
String imports
|
||||
--------------
|
||||
|
||||
``Factory`` provider can handle string imports:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service = providers.Factory("myapp.mypackage.mymodule.Service")
|
||||
|
||||
You can also make a relative import:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# in myapp/container.py
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service = providers.Factory(".mypackage.mymodule.Service")
|
||||
|
||||
or import a member of the current module just specifying its name:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Service:
|
||||
...
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service = providers.Factory("Service")
|
||||
|
||||
.. note::
|
||||
``Singleton``, ``Callable``, ``Resource``, and ``Coroutine`` providers handle string imports
|
||||
the same way as a ``Factory`` provider.
|
||||
|
||||
.. _factory-specialize-provided-type:
|
||||
|
||||
Specializing the provided type
|
||||
|
@ -145,16 +184,20 @@ provider with two peculiarities:
|
|||
:lines: 3-
|
||||
:emphasize-lines: 34
|
||||
|
||||
.. _factory-aggregate-provider:
|
||||
|
||||
Factory aggregate
|
||||
-----------------
|
||||
|
||||
:py:class:`FactoryAggregate` provider aggregates multiple factories. When you call the
|
||||
``FactoryAggregate`` it delegates the call to one of the factories.
|
||||
:py:class:`FactoryAggregate` provider aggregates multiple factories.
|
||||
|
||||
The aggregated factories are associated with the string names. When you call the
|
||||
``FactoryAggregate`` you have to provide one of the these names as a first argument.
|
||||
``FactoryAggregate`` looks for the factory with a matching name and delegates it the work. The
|
||||
rest of the arguments are passed to the delegated ``Factory``.
|
||||
.. seealso::
|
||||
:ref:`aggregate-provider` – it's a successor of ``FactoryAggregate`` provider that can aggregate
|
||||
any type of provider, not only ``Factory``.
|
||||
|
||||
The aggregated factories are associated with the string keys. When you call the
|
||||
``FactoryAggregate`` you have to provide one of the these keys as a first argument.
|
||||
``FactoryAggregate`` looks for the factory with a matching key and calls it with the rest of the arguments.
|
||||
|
||||
.. image:: images/factory_aggregate.png
|
||||
:width: 100%
|
||||
|
@ -165,12 +208,12 @@ rest of the arguments are passed to the delegated ``Factory``.
|
|||
:lines: 3-
|
||||
:emphasize-lines: 33-37,47
|
||||
|
||||
You can get a dictionary of the aggregated factories using the ``.factories`` attribute of the
|
||||
``FactoryAggregate``. To get a game factories dictionary from the previous example you can use
|
||||
``game_factory.factories`` attribute.
|
||||
You can get a dictionary of the aggregated providers using ``.providers`` attribute.
|
||||
To get a game provider dictionary from the previous example you can use
|
||||
``game_factory.providers`` attribute.
|
||||
|
||||
You can also access an aggregated factory as an attribute. To create the ``Chess`` object from the
|
||||
previous example you can do ``chess = game_factory.chess('John', 'Jane')``.
|
||||
previous example you can do ``chess = game_factory.chess("John", "Jane")``.
|
||||
|
||||
.. note::
|
||||
You can not override the ``FactoryAggregate`` provider.
|
||||
|
@ -178,4 +221,22 @@ previous example you can do ``chess = game_factory.chess('John', 'Jane')``.
|
|||
.. note::
|
||||
When you inject the ``FactoryAggregate`` provider it is passed "as is".
|
||||
|
||||
To use non-string keys or string keys with ``.`` and ``-``, you can provide a dictionary as a positional argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
providers.FactoryAggregate({
|
||||
SomeClass: providers.Factory(...),
|
||||
"key.with.periods": providers.Factory(...),
|
||||
"key-with-dashes": providers.Factory(...),
|
||||
})
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/factory_aggregate_non_string_keys.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 30-33,39-40
|
||||
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -46,6 +46,7 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
|
|||
dict
|
||||
configuration
|
||||
resource
|
||||
aggregate
|
||||
selector
|
||||
dependency
|
||||
overriding
|
||||
|
|
|
@ -98,7 +98,7 @@ you configure global resource:
|
|||
|
||||
configure_logging = providers.Resource(
|
||||
logging.config.fileConfig,
|
||||
fname='logging.ini',
|
||||
fname="logging.ini",
|
||||
)
|
||||
|
||||
Function initializer does not provide a way to specify custom resource shutdown.
|
||||
|
@ -210,8 +210,8 @@ first argument.
|
|||
|
||||
.. _resource-provider-wiring-closing:
|
||||
|
||||
Resources, wiring and per-function execution scope
|
||||
--------------------------------------------------
|
||||
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
|
||||
|
@ -220,7 +220,7 @@ execution scope. For doing this you need to use additional ``Closing`` marker fr
|
|||
.. literalinclude:: ../../examples/wiring/flask_resource_closing.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 24
|
||||
: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.
|
||||
|
@ -325,7 +325,7 @@ When you use resource provider with asynchronous initializer you need to call it
|
|||
connection = await container.connection.shutdown()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
Container ``init_resources()`` and ``shutdown_resources()`` methods should be used asynchronously if there is
|
||||
|
@ -349,7 +349,7 @@ at least one asynchronous resource provider:
|
|||
await container.shutdown_resources()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
See also:
|
||||
|
|
|
@ -30,4 +30,7 @@ When a ``Selector`` provider is called, it gets a ``selector`` value and delegat
|
|||
the provider with a matching name. The ``selector`` callable works as a switch: when the returned
|
||||
value is changed the ``Selector`` provider will delegate the work to another provider.
|
||||
|
||||
.. seealso::
|
||||
:ref:`aggregate-provider` to inject a group of providers.
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -24,7 +24,7 @@ returns it on the rest of the calls.
|
|||
|
||||
.. note::
|
||||
|
||||
``Singleton`` provider makes dependencies injection only when creates an object. When an object
|
||||
``Singleton`` provider makes dependencies injection only when it creates an object. When an object
|
||||
is created and memorized ``Singleton`` provider just returns it without applying injections.
|
||||
|
||||
Specialization of the provided type and abstract singletons work the same like like for the
|
||||
|
|
|
@ -30,7 +30,7 @@ IDE.
|
|||
provider = providers.Factory(Cat)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
animal = provider() # mypy knows that animal is of type "Cat"
|
||||
|
||||
|
||||
|
@ -54,5 +54,7 @@ function or method.
|
|||
provider: providers.Provider[Animal] = providers.Factory(Cat)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
animal = provider() # mypy knows that animal is of type "Animal"
|
||||
|
||||
.. disqus::
|
||||
|
|
7
docs/sponsor.rst
Normal file
7
docs/sponsor.rst
Normal file
|
@ -0,0 +1,7 @@
|
|||
.. list-table::
|
||||
:class: no-border
|
||||
:align: left
|
||||
|
||||
* - Sponsor the project on GitHub:
|
||||
- .. raw:: html
|
||||
:file: _static/sponsor.html
|
|
@ -127,8 +127,6 @@ Now it's time to install the project requirements. We will use next packages:
|
|||
|
||||
- ``dependency-injector`` - the dependency injection framework
|
||||
- ``aiohttp`` - the web framework
|
||||
- ``aiohttp-devtools`` - the helper library that will provide a development server with live
|
||||
reloading
|
||||
- ``pyyaml`` - the YAML files parsing library, used for the reading of the configuration files
|
||||
- ``pytest-aiohttp`` - the helper library for the testing of the ``aiohttp`` application
|
||||
- ``pytest-cov`` - the helper library for measuring the test coverage
|
||||
|
@ -139,7 +137,6 @@ Put next lines into the ``requirements.txt`` file:
|
|||
|
||||
dependency-injector
|
||||
aiohttp
|
||||
aiohttp-devtools
|
||||
pyyaml
|
||||
pytest-aiohttp
|
||||
pytest-cov
|
||||
|
@ -177,16 +174,16 @@ Edit ``handlers.py``:
|
|||
|
||||
|
||||
async def index(request: web.Request) -> web.Response:
|
||||
query = request.query.get('query', 'Dependency Injector')
|
||||
limit = int(request.query.get('limit', 10))
|
||||
query = request.query.get("query", "Dependency Injector")
|
||||
limit = int(request.query.get("limit", 10))
|
||||
|
||||
gifs = []
|
||||
|
||||
return web.json_response(
|
||||
{
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
'gifs': gifs,
|
||||
"query": query,
|
||||
"limit": limit,
|
||||
"gifs": gifs,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -228,30 +225,35 @@ Put next into the ``application.py``:
|
|||
app = web.Application()
|
||||
app.container = container
|
||||
app.add_routes([
|
||||
web.get('/', handlers.index),
|
||||
web.get("/", handlers.index),
|
||||
])
|
||||
return app
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = create_app()
|
||||
web.run_app(app)
|
||||
|
||||
Now we're ready to run our application
|
||||
|
||||
Do next in the terminal:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
adev runserver giphynavigator/application.py --livereload
|
||||
python -m giphynavigator.application
|
||||
|
||||
The output should be something like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[18:52:59] Starting aux server at http://localhost:8001 ◆
|
||||
[18:52:59] Starting dev server at http://localhost:8000 ●
|
||||
======== Running on http://0.0.0.0:8080 ========
|
||||
(Press CTRL+C to quit)
|
||||
|
||||
Let's check that it works. Open another terminal session and use ``httpie``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
http http://127.0.0.1:8000/
|
||||
http http://0.0.0.0:8080/
|
||||
|
||||
You should see:
|
||||
|
||||
|
@ -261,7 +263,7 @@ You should see:
|
|||
Content-Length: 844
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Date: Wed, 29 Jul 2020 21:01:50 GMT
|
||||
Server: Python/3.8 aiohttp/3.6.2
|
||||
Server: Python/3.10 aiohttp/3.6.2
|
||||
|
||||
{
|
||||
"gifs": [],
|
||||
|
@ -304,7 +306,7 @@ and put next into it:
|
|||
|
||||
class GiphyClient:
|
||||
|
||||
API_URL = 'https://api.giphy.com/v1'
|
||||
API_URL = "https://api.giphy.com/v1"
|
||||
|
||||
def __init__(self, api_key, timeout):
|
||||
self._api_key = api_key
|
||||
|
@ -312,11 +314,11 @@ and put next into it:
|
|||
|
||||
async def search(self, query, limit):
|
||||
"""Make search API call and return result."""
|
||||
url = f'{self.API_URL}/gifs/search'
|
||||
url = f"{self.API_URL}/gifs/search"
|
||||
params = {
|
||||
'q': query,
|
||||
'api_key': self._api_key,
|
||||
'limit': limit,
|
||||
"q": query,
|
||||
"api_key": self._api_key,
|
||||
"limit": limit,
|
||||
}
|
||||
async with ClientSession(timeout=self._timeout) as session:
|
||||
async with session.get(url, params=params) as response:
|
||||
|
@ -328,8 +330,10 @@ Now we need to add ``GiphyClient`` into the container. The ``GiphyClient`` has t
|
|||
that have to be injected: the API key and the request timeout. We will need to use two more
|
||||
providers from the ``dependency_injector.providers`` module:
|
||||
|
||||
- ``Factory`` provider that will create the ``GiphyClient`` client.
|
||||
- ``Configuration`` provider that will provide the API key and the request timeout.
|
||||
- ``Factory`` provider. It will create a ``GiphyClient`` client.
|
||||
- ``Configuration`` provider. It will provide an API key and a request timeout for the ``GiphyClient``
|
||||
client. We will specify the location of the configuration file. The configuration provider will parse
|
||||
the configuration file when we create a container instance.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
|
@ -345,7 +349,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
|
@ -353,18 +357,8 @@ Edit ``containers.py``:
|
|||
timeout=config.giphy.request_timeout,
|
||||
)
|
||||
|
||||
.. 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:
|
||||
Now let's add the configuration file. We will use YAML. Create an empty file ``config.yml`` in
|
||||
the root root of the project:
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 9
|
||||
|
@ -387,17 +381,14 @@ and put next into it:
|
|||
giphy:
|
||||
request_timeout: 10
|
||||
|
||||
We will use an environment variable ``GIPHY_API_KEY`` to provide the API key.
|
||||
|
||||
Now we need to edit ``create_app()`` to make two things when application starts:
|
||||
|
||||
- Load the configuration file the ``config.yml``.
|
||||
- Load the API key from the ``GIPHY_API_KEY`` environment variable.
|
||||
We will use the ``GIPHY_API_KEY`` environment variable to provide the API key. Let’s edit
|
||||
``create_app()`` to fetch the key value from it.
|
||||
|
||||
Edit ``application.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 11-12
|
||||
:emphasize-lines: 11
|
||||
|
||||
"""Application module."""
|
||||
|
||||
|
@ -409,17 +400,20 @@ Edit ``application.py``:
|
|||
|
||||
def create_app() -> web.Application:
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.giphy.api_key.from_env('GIPHY_API_KEY')
|
||||
container.config.giphy.api_key.from_env("GIPHY_API_KEY")
|
||||
|
||||
app = web.Application()
|
||||
app.container = container
|
||||
app.add_routes([
|
||||
web.get('/', handlers.index),
|
||||
web.get("/", handlers.index),
|
||||
])
|
||||
return app
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = create_app()
|
||||
web.run_app(app)
|
||||
|
||||
Now we need to create an API key and set it to the environment variable.
|
||||
|
||||
As for now, don’t worry, just take this one:
|
||||
|
@ -483,7 +477,7 @@ and put next into it:
|
|||
|
||||
result = await self._giphy_client.search(query, limit)
|
||||
|
||||
return [{'url': gif['url']} for gif in result['data']]
|
||||
return [{"url": gif["url"]} for gif in result["data"]]
|
||||
|
||||
The ``SearchService`` has a dependency on the ``GiphyClient``. This dependency will be
|
||||
injected when we add ``SearchService`` to the container.
|
||||
|
@ -502,7 +496,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
|
@ -531,7 +525,7 @@ Edit ``handlers.py``:
|
|||
"""Handlers module."""
|
||||
|
||||
from aiohttp import web
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
@ -542,60 +536,63 @@ Edit ``handlers.py``:
|
|||
request: web.Request,
|
||||
search_service: SearchService = Provide[Container.search_service],
|
||||
) -> web.Response:
|
||||
query = request.query.get('query', 'Dependency Injector')
|
||||
limit = int(request.query.get('limit', 10))
|
||||
query = request.query.get("query", "Dependency Injector")
|
||||
limit = int(request.query.get("limit", 10))
|
||||
|
||||
gifs = await search_service.search(query, limit)
|
||||
|
||||
return web.json_response(
|
||||
{
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
'gifs': gifs,
|
||||
"query": query,
|
||||
"limit": limit,
|
||||
"gifs": gifs,
|
||||
},
|
||||
)
|
||||
|
||||
To make the injection work we need to wire the container instance with the ``handlers`` module.
|
||||
This needs to be done once. After it's done we can use ``Provide`` markers to specify as many
|
||||
injections as needed for any handler.
|
||||
To make the injection work we need to wire the container with the ``handlers`` module.
|
||||
Let's configure the container to automatically make wiring with the ``handlers`` module when we
|
||||
create a container instance.
|
||||
|
||||
Edit ``application.py``:
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 13
|
||||
:emphasize-lines: 10
|
||||
|
||||
"""Application module."""
|
||||
"""Containers module."""
|
||||
|
||||
from aiohttp import web
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
from .containers import Container
|
||||
from . import handlers
|
||||
from . import giphy, services
|
||||
|
||||
|
||||
def create_app() -> web.Application:
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.giphy.api_key.from_env('GIPHY_API_KEY')
|
||||
container.wire(modules=[handlers])
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
app = web.Application()
|
||||
app.container = container
|
||||
app.add_routes([
|
||||
web.get('/', handlers.index),
|
||||
])
|
||||
return app
|
||||
wiring_config = containers.WiringConfiguration(modules=[".handlers"])
|
||||
|
||||
Make sure the app is running or use:
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
Make sure the app is running:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
adev runserver giphynavigator/application.py --livereload
|
||||
python -m giphynavigator.application
|
||||
|
||||
and make a request to the API in the terminal:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
http http://localhost:8000/ query=="wow,it works" limit==5
|
||||
http http://0.0.0.0:8080/ query=="wow,it works" limit==5
|
||||
|
||||
You should see:
|
||||
|
||||
|
@ -605,7 +602,7 @@ You should see:
|
|||
Content-Length: 492
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Date: Fri, 09 Oct 2020 01:35:48 GMT
|
||||
Server: Python/3.8 aiohttp/3.6.2
|
||||
Server: Python/3.10 aiohttp/3.6.2
|
||||
|
||||
{
|
||||
"gifs": [
|
||||
|
@ -651,7 +648,7 @@ Edit ``handlers.py``:
|
|||
"""Handlers module."""
|
||||
|
||||
from aiohttp import web
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
@ -664,16 +661,16 @@ Edit ``handlers.py``:
|
|||
default_query: str = Provide[Container.config.default.query],
|
||||
default_limit: int = Provide[Container.config.default.limit.as_int()],
|
||||
) -> web.Response:
|
||||
query = request.query.get('query', default_query)
|
||||
limit = int(request.query.get('limit', default_limit))
|
||||
query = request.query.get("query", default_query)
|
||||
limit = int(request.query.get("limit", default_limit))
|
||||
|
||||
gifs = await search_service.search(query, limit)
|
||||
|
||||
return web.json_response(
|
||||
{
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
'gifs': gifs,
|
||||
"query": query,
|
||||
"limit": limit,
|
||||
"gifs": gifs,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -745,29 +742,29 @@ and put next into it:
|
|||
async def test_index(client, app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [
|
||||
{'url': 'https://giphy.com/gif1.gif'},
|
||||
{'url': 'https://giphy.com/gif2.gif'},
|
||||
"data": [
|
||||
{"url": "https://giphy.com/gif1.gif"},
|
||||
{"url": "https://giphy.com/gif2.gif"},
|
||||
],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
response = await client.get(
|
||||
'/',
|
||||
"/",
|
||||
params={
|
||||
'query': 'test',
|
||||
'limit': 10,
|
||||
"query": "test",
|
||||
"limit": 10,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data == {
|
||||
'query': 'test',
|
||||
'limit': 10,
|
||||
'gifs': [
|
||||
{'url': 'https://giphy.com/gif1.gif'},
|
||||
{'url': 'https://giphy.com/gif2.gif'},
|
||||
"query": "test",
|
||||
"limit": 10,
|
||||
"gifs": [
|
||||
{"url": "https://giphy.com/gif1.gif"},
|
||||
{"url": "https://giphy.com/gif2.gif"},
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -775,30 +772,30 @@ and put next into it:
|
|||
async def test_index_no_data(client, app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [],
|
||||
"data": [],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
response = await client.get('/')
|
||||
response = await client.get("/")
|
||||
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data['gifs'] == []
|
||||
assert data["gifs"] == []
|
||||
|
||||
|
||||
async def test_index_default_params(client, app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [],
|
||||
"data": [],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
response = await client.get('/')
|
||||
response = await client.get("/")
|
||||
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data['query'] == app.container.config.default.query()
|
||||
assert data['limit'] == app.container.config.default.limit()
|
||||
assert data["query"] == app.container.config.default.query()
|
||||
assert data["limit"] == app.container.config.default.limit()
|
||||
|
||||
Now let's run it and check the coverage:
|
||||
|
||||
|
@ -810,24 +807,24 @@ You should see:
|
|||
|
||||
.. code-block::
|
||||
|
||||
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
|
||||
plugins: cov-2.10.0, aiohttp-0.3.0, asyncio-0.14.0
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: asyncio-0.16.0, anyio-3.3.4, aiohttp-0.3.0, cov-3.0.0
|
||||
collected 3 items
|
||||
|
||||
giphynavigator/tests.py ... [100%]
|
||||
|
||||
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
|
||||
---------- coverage: platform darwin, python 3.10.0-final-0 ----------
|
||||
Name Stmts Miss Cover
|
||||
---------------------------------------------------
|
||||
giphynavigator/__init__.py 0 0 100%
|
||||
giphynavigator/application.py 12 0 100%
|
||||
giphynavigator/containers.py 6 0 100%
|
||||
giphynavigator/application.py 13 2 85%
|
||||
giphynavigator/containers.py 7 0 100%
|
||||
giphynavigator/giphy.py 14 9 36%
|
||||
giphynavigator/handlers.py 10 0 100%
|
||||
giphynavigator/services.py 9 1 89%
|
||||
giphynavigator/tests.py 37 0 100%
|
||||
---------------------------------------------------
|
||||
TOTAL 88 10 89%
|
||||
TOTAL 90 12 87%
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -862,4 +859,6 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -18,7 +18,7 @@ In this tutorial we will use:
|
|||
|
||||
- Python 3
|
||||
- Docker
|
||||
- Docker-compose
|
||||
- Docker Compose
|
||||
|
||||
Start from the scratch or jump to the section:
|
||||
|
||||
|
@ -47,28 +47,27 @@ response it will log:
|
|||
Prerequisites
|
||||
-------------
|
||||
|
||||
We will use `Docker <https://www.docker.com/>`_ and
|
||||
`docker-compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
|
||||
We will use `docker compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker --version
|
||||
docker-compose --version
|
||||
docker compose version
|
||||
|
||||
The output should look something like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Docker version 19.03.12, build 48a66213fe
|
||||
docker-compose version 1.26.2, build eefe0d31
|
||||
Docker version 27.3.1, build ce12230
|
||||
Docker Compose version v2.29.7
|
||||
|
||||
.. note::
|
||||
|
||||
If you don't have ``Docker`` or ``docker-compose`` you need to install them before proceeding.
|
||||
If you don't have ``Docker`` or ``docker compose`` you need to install them before proceeding.
|
||||
Follow these installation guides:
|
||||
|
||||
- `Install Docker <https://docs.docker.com/get-docker/>`_
|
||||
- `Install docker-compose <https://docs.docker.com/compose/install/>`_
|
||||
- `Install docker compose <https://docs.docker.com/compose/install/>`_
|
||||
|
||||
The prerequisites are satisfied. Let's get started with the project layout.
|
||||
|
||||
|
@ -129,13 +128,13 @@ Put next lines into the ``requirements.txt`` file:
|
|||
pytest-cov
|
||||
|
||||
Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and
|
||||
specify how to run it. We will use ``python:3.8-buster`` as a base image.
|
||||
specify how to run it. We will use ``python:3.13-bookworm`` as a base image.
|
||||
|
||||
Put next lines into the ``Dockerfile`` file:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM python:3.8-buster
|
||||
FROM python:3.13-bookworm
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
|
@ -155,8 +154,6 @@ Put next lines into the ``docker-compose.yml`` file:
|
|||
|
||||
.. code-block:: yaml
|
||||
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
|
||||
monitor:
|
||||
|
@ -171,7 +168,7 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose build
|
||||
docker compose build
|
||||
|
||||
The build process may take a couple of minutes. You should see something like this in the end:
|
||||
|
||||
|
@ -184,7 +181,7 @@ After the build is done run the container:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up
|
||||
docker compose up
|
||||
|
||||
The output should look like:
|
||||
|
||||
|
@ -204,11 +201,11 @@ Logging and configuration
|
|||
|
||||
In this section we will configure the logging and configuration file parsing.
|
||||
|
||||
Let's start with the the main part of our application - the container. Container will keep all of
|
||||
Let's start with the the main part of our application – the container. Container will keep all of
|
||||
the application components and their dependencies.
|
||||
|
||||
First two components that we're going to add are the config object and the provider for
|
||||
configuring the logging.
|
||||
First two components that we're going to add are the configuration provider and the resource provider
|
||||
for configuring the logging.
|
||||
|
||||
Put next lines into the ``containers.py`` file:
|
||||
|
||||
|
@ -224,7 +221,7 @@ Put next lines into the ``containers.py`` file:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
|
@ -233,16 +230,7 @@ Put next lines into the ``containers.py`` file:
|
|||
format=config.log.format,
|
||||
)
|
||||
|
||||
.. note::
|
||||
|
||||
We have used the configuration value before it was defined. That's the principle how the
|
||||
``Configuration`` provider works.
|
||||
|
||||
Use first, define later.
|
||||
|
||||
The configuration file will keep the logging settings.
|
||||
|
||||
Put next lines into the ``config.yml`` file:
|
||||
The configuration file will keep the logging settings. Put next lines into the ``config.yml`` file:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
@ -250,9 +238,10 @@ Put next lines into the ``config.yml`` file:
|
|||
level: "INFO"
|
||||
format: "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s"
|
||||
|
||||
Now let's create the function that will run our daemon. It's traditionally called
|
||||
``main()``. The ``main()`` function will create the container. Then it will use the container
|
||||
to parse the ``config.yml`` file and call the logging configuration provider.
|
||||
Now let's create the function that will run our daemon. It's traditionally called ``main()``.
|
||||
The ``main()`` function will start the dispatcher, but we will keep it empty for now.
|
||||
We will create the container instance before calling ``main()`` in ``if __name__ == "__main__"``.
|
||||
Container instance will parse ``config.yml`` and then we will call the logging configuration provider.
|
||||
|
||||
Put next lines into the ``__main__.py`` file:
|
||||
|
||||
|
@ -267,9 +256,8 @@ Put next lines into the ``__main__.py`` file:
|
|||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.init_resources()
|
||||
|
||||
main()
|
||||
|
@ -356,7 +344,7 @@ and next into the ``dispatcher.py``:
|
|||
asyncio.run(self.start())
|
||||
|
||||
async def start(self) -> None:
|
||||
self._logger.info('Starting up')
|
||||
self._logger.info("Starting up")
|
||||
|
||||
for monitor in self._monitors:
|
||||
self._monitor_tasks.append(
|
||||
|
@ -376,11 +364,11 @@ and next into the ``dispatcher.py``:
|
|||
|
||||
self._stopping = True
|
||||
|
||||
self._logger.info('Shutting down')
|
||||
self._logger.info("Shutting down")
|
||||
for task, monitor in zip(self._monitor_tasks, self._monitors):
|
||||
task.cancel()
|
||||
self._monitor_tasks.clear()
|
||||
self._logger.info('Shutdown finished successfully')
|
||||
self._logger.info("Shutdown finished successfully")
|
||||
|
||||
@staticmethod
|
||||
async def _run_monitor(monitor: Monitor) -> None:
|
||||
|
@ -396,7 +384,7 @@ and next into the ``dispatcher.py``:
|
|||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception:
|
||||
monitor.logger.exception('Error executing monitor check')
|
||||
monitor.logger.exception("Error executing monitor check")
|
||||
|
||||
await asyncio.sleep(_until_next(last=time_start))
|
||||
|
||||
|
@ -419,7 +407,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
|
@ -442,13 +430,11 @@ and call the ``run()`` method. We will use :ref:`wiring` feature.
|
|||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3-7,11-13,20
|
||||
:emphasize-lines: 3-5,9-11,17
|
||||
|
||||
"""Main module."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .dispatcher import Dispatcher
|
||||
from .containers import Container
|
||||
|
@ -459,11 +445,10 @@ Edit ``__main__.py``:
|
|||
dispatcher.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.init_resources()
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
main()
|
||||
|
||||
|
@ -473,7 +458,7 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up
|
||||
docker compose up
|
||||
|
||||
The output should look like:
|
||||
|
||||
|
@ -561,7 +546,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
|
@ -613,10 +598,10 @@ Edit ``monitors.py``:
|
|||
options: Dict[str, Any],
|
||||
) -> None:
|
||||
self._client = http_client
|
||||
self._method = options.pop('method')
|
||||
self._url = options.pop('url')
|
||||
self._timeout = options.pop('timeout')
|
||||
super().__init__(check_every=options.pop('check_every'))
|
||||
self._method = options.pop("method")
|
||||
self._url = options.pop("url")
|
||||
self._timeout = options.pop("timeout")
|
||||
super().__init__(check_every=options.pop("check_every"))
|
||||
|
||||
async def check(self) -> None:
|
||||
time_start = time.time()
|
||||
|
@ -631,11 +616,11 @@ Edit ``monitors.py``:
|
|||
time_took = time_end - time_start
|
||||
|
||||
self.logger.info(
|
||||
'Check\n'
|
||||
' %s %s\n'
|
||||
' response code: %s\n'
|
||||
' content length: %s\n'
|
||||
' request took: %s seconds',
|
||||
"Check\n"
|
||||
" %s %s\n"
|
||||
" response code: %s\n"
|
||||
" content length: %s\n"
|
||||
" request took: %s seconds",
|
||||
self._method,
|
||||
self._url,
|
||||
response.status,
|
||||
|
@ -666,7 +651,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
|
@ -717,7 +702,7 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up
|
||||
docker compose up
|
||||
|
||||
You should see:
|
||||
|
||||
|
@ -765,7 +750,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
|
@ -825,7 +810,7 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up
|
||||
docker compose up
|
||||
|
||||
You should see:
|
||||
|
||||
|
@ -890,7 +875,7 @@ Create ``tests.py`` in the ``monitoringdaemon`` package:
|
|||
and put next into it:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 54,70-71
|
||||
:emphasize-lines: 54,70-73
|
||||
|
||||
"""Tests module."""
|
||||
|
||||
|
@ -911,33 +896,33 @@ and put next into it:
|
|||
|
||||
@pytest.fixture
|
||||
def container():
|
||||
container = Container()
|
||||
container.config.from_dict({
|
||||
'log': {
|
||||
'level': 'INFO',
|
||||
'formant': '[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s',
|
||||
},
|
||||
'monitors': {
|
||||
'example': {
|
||||
'method': 'GET',
|
||||
'url': 'http://fake-example.com',
|
||||
'timeout': 1,
|
||||
'check_every': 1,
|
||||
return Container(
|
||||
config={
|
||||
"log": {
|
||||
"level": "INFO",
|
||||
"formant": "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s",
|
||||
},
|
||||
'httpbin': {
|
||||
'method': 'GET',
|
||||
'url': 'https://fake-httpbin.org/get',
|
||||
'timeout': 1,
|
||||
'check_every': 1,
|
||||
"monitors": {
|
||||
"example": {
|
||||
"method": "GET",
|
||||
"url": "http://fake-example.com",
|
||||
"timeout": 1,
|
||||
"check_every": 1,
|
||||
},
|
||||
"httpbin": {
|
||||
"method": "GET",
|
||||
"url": "https://fake-httpbin.org/get",
|
||||
"timeout": 1,
|
||||
"check_every": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return container
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_example_monitor(container, caplog):
|
||||
caplog.set_level('INFO')
|
||||
caplog.set_level("INFO")
|
||||
|
||||
http_client_mock = mock.AsyncMock()
|
||||
http_client_mock.request.return_value = RequestStub(
|
||||
|
@ -949,21 +934,22 @@ and put next into it:
|
|||
example_monitor = container.example_monitor()
|
||||
await example_monitor.check()
|
||||
|
||||
assert 'http://fake-example.com' in caplog.text
|
||||
assert 'response code: 200' in caplog.text
|
||||
assert 'content length: 635' in caplog.text
|
||||
assert "http://fake-example.com" in caplog.text
|
||||
assert "response code: 200" in caplog.text
|
||||
assert "content length: 635" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatcher(container, caplog, event_loop):
|
||||
caplog.set_level('INFO')
|
||||
caplog.set_level("INFO")
|
||||
|
||||
example_monitor_mock = mock.AsyncMock()
|
||||
httpbin_monitor_mock = mock.AsyncMock()
|
||||
|
||||
with container.example_monitor.override(example_monitor_mock), \
|
||||
container.httpbin_monitor.override(httpbin_monitor_mock):
|
||||
|
||||
with container.override_providers(
|
||||
example_monitor=example_monitor_mock,
|
||||
httpbin_monitor=httpbin_monitor_mock,
|
||||
):
|
||||
dispatcher = container.dispatcher()
|
||||
event_loop.create_task(dispatcher.start())
|
||||
await asyncio.sleep(0.1)
|
||||
|
@ -976,31 +962,32 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
|
||||
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
|
||||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
platform linux -- Python 3.8.3, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
|
||||
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||
rootdir: /code
|
||||
plugins: asyncio-0.14.0, cov-2.10.0
|
||||
plugins: cov-6.0.0, asyncio-0.24.0
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
collected 2 items
|
||||
|
||||
monitoringdaemon/tests.py .. [100%]
|
||||
|
||||
----------- coverage: platform linux, python 3.8.3-final-0 -----------
|
||||
---------- coverage: platform linux, python 3.10.0-final-0 -----------
|
||||
Name Stmts Miss Cover
|
||||
----------------------------------------------------
|
||||
monitoringdaemon/__init__.py 0 0 100%
|
||||
monitoringdaemon/__main__.py 13 13 0%
|
||||
monitoringdaemon/__main__.py 11 11 0%
|
||||
monitoringdaemon/containers.py 11 0 100%
|
||||
monitoringdaemon/dispatcher.py 44 5 89%
|
||||
monitoringdaemon/dispatcher.py 45 5 89%
|
||||
monitoringdaemon/http.py 6 3 50%
|
||||
monitoringdaemon/monitors.py 23 1 96%
|
||||
monitoringdaemon/tests.py 37 0 100%
|
||||
monitoringdaemon/tests.py 35 0 100%
|
||||
----------------------------------------------------
|
||||
TOTAL 134 22 84%
|
||||
TOTAL 131 20 85%
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -1039,4 +1026,6 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -160,19 +160,19 @@ Second put next in the ``fixtures.py``:
|
|||
|
||||
|
||||
SAMPLE_DATA = [
|
||||
('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'),
|
||||
('Rogue One: A Star Wars Story', 2016, 'Gareth Edwards'),
|
||||
('The Jungle Book', 2016, 'Jon Favreau'),
|
||||
("The Hunger Games: Mockingjay - Part 2", 2015, "Francis Lawrence"),
|
||||
("Rogue One: A Star Wars Story", 2016, "Gareth Edwards"),
|
||||
("The Jungle Book", 2016, "Jon Favreau"),
|
||||
]
|
||||
|
||||
FILE = pathlib.Path(__file__)
|
||||
DIR = FILE.parent
|
||||
CSV_FILE = DIR / 'movies.csv'
|
||||
SQLITE_FILE = DIR / 'movies.db'
|
||||
CSV_FILE = DIR / "movies.csv"
|
||||
SQLITE_FILE = DIR / "movies.db"
|
||||
|
||||
|
||||
def create_csv(movies_data, path):
|
||||
with open(path, 'w') as opened_file:
|
||||
with open(path, "w") as opened_file:
|
||||
writer = csv.writer(opened_file)
|
||||
for row in movies_data:
|
||||
writer.writerow(row)
|
||||
|
@ -181,20 +181,20 @@ Second put next in the ``fixtures.py``:
|
|||
def create_sqlite(movies_data, path):
|
||||
with sqlite3.connect(path) as db:
|
||||
db.execute(
|
||||
'CREATE TABLE IF NOT EXISTS movies '
|
||||
'(title text, year int, director text)'
|
||||
"CREATE TABLE IF NOT EXISTS movies "
|
||||
"(title text, year int, director text)"
|
||||
)
|
||||
db.execute('DELETE FROM movies')
|
||||
db.executemany('INSERT INTO movies VALUES (?,?,?)', movies_data)
|
||||
db.execute("DELETE FROM movies")
|
||||
db.executemany("INSERT INTO movies VALUES (?,?,?)", movies_data)
|
||||
|
||||
|
||||
def main():
|
||||
create_csv(SAMPLE_DATA, CSV_FILE)
|
||||
create_sqlite(SAMPLE_DATA, SQLITE_FILE)
|
||||
print('OK')
|
||||
print("OK")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Now run in the terminal:
|
||||
|
@ -266,7 +266,7 @@ Edit ``__main__.py``:
|
|||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
main()
|
||||
|
@ -321,7 +321,7 @@ and put next into it:
|
|||
self.director = str(director)
|
||||
|
||||
def __repr__(self):
|
||||
return '{0}(title={1}, year={2}, director={3})'.format(
|
||||
return "{0}(title={1}, year={2}, director={3})".format(
|
||||
self.__class__.__name__,
|
||||
repr(self.title),
|
||||
repr(self.year),
|
||||
|
@ -428,7 +428,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
movie = providers.Factory(entities.Movie)
|
||||
|
||||
|
@ -445,15 +445,9 @@ This is also called the delegation of the provider. If we just pass the movie fa
|
|||
as the dependency, it will be called when csv finder is created and the ``Movie`` instance will
|
||||
be injected. With the ``.provider`` attribute the provider itself will be injected.
|
||||
|
||||
The csv finder also has a few dependencies on the configuration options. We added configuration
|
||||
provider to provide these dependencies.
|
||||
|
||||
.. note::
|
||||
|
||||
We have used the configuration value before it was defined. That's the principle how the
|
||||
Configuration provider works.
|
||||
|
||||
Use first, define later.
|
||||
The csv finder also has a few dependencies on the configuration options. We added a configuration
|
||||
provider to provide these dependencies and specified the location of the configuration file.
|
||||
The configuration provider will parse the configuration file when we create a container instance.
|
||||
|
||||
Not let's define the configuration values.
|
||||
|
||||
|
@ -467,29 +461,7 @@ Edit ``config.yml``:
|
|||
path: "data/movies.csv"
|
||||
delimiter: ","
|
||||
|
||||
The configuration file is ready. Now let's update the ``main()`` function to specify its location.
|
||||
|
||||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 12
|
||||
|
||||
"""Main module."""
|
||||
|
||||
from .containers import Container
|
||||
|
||||
|
||||
def main() -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
|
||||
main()
|
||||
|
||||
Move on to the lister.
|
||||
The configuration file is ready. Move on to the lister.
|
||||
|
||||
Create the ``listers.py`` in the ``movies`` package:
|
||||
|
||||
|
@ -552,7 +524,7 @@ and edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
movie = providers.Factory(entities.Movie)
|
||||
|
||||
|
@ -575,13 +547,11 @@ Let's inject the ``lister`` into the ``main()`` function.
|
|||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 3-7,11-12,19
|
||||
:emphasize-lines: 3-5,9-10,16
|
||||
|
||||
"""Main module."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .listers import MovieLister
|
||||
from .containers import Container
|
||||
|
@ -592,10 +562,9 @@ Edit ``__main__.py``:
|
|||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
main()
|
||||
|
||||
|
@ -607,13 +576,11 @@ Francis Lawrence and movies released in 2016.
|
|||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 13-19
|
||||
:emphasize-lines: 11-17
|
||||
|
||||
"""Main module."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .listers import MovieLister
|
||||
from .containers import Container
|
||||
|
@ -621,19 +588,18 @@ Edit ``__main__.py``:
|
|||
|
||||
@inject
|
||||
def main(lister: MovieLister = Provide[Container.lister]) -> None:
|
||||
print('Francis Lawrence movies:')
|
||||
for movie in lister.movies_directed_by('Francis Lawrence'):
|
||||
print('\t-', movie)
|
||||
print("Francis Lawrence movies:")
|
||||
for movie in lister.movies_directed_by("Francis Lawrence"):
|
||||
print("\t-", movie)
|
||||
|
||||
print('2016 movies:')
|
||||
print("2016 movies:")
|
||||
for movie in lister.movies_released_in(2016):
|
||||
print('\t-', movie)
|
||||
print("\t-", movie)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
main()
|
||||
|
||||
|
@ -718,7 +684,7 @@ Edit ``finders.py``:
|
|||
|
||||
def find_all(self) -> List[Movie]:
|
||||
with self._database as db:
|
||||
rows = db.execute('SELECT title, year, director FROM movies')
|
||||
rows = db.execute("SELECT title, year, director FROM movies")
|
||||
return [self._movie_factory(*row) for row in rows]
|
||||
|
||||
Now we need to add the sqlite finder to the container and update lister's dependency to use it.
|
||||
|
@ -737,7 +703,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
movie = providers.Factory(entities.Movie)
|
||||
|
||||
|
@ -826,7 +792,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
movie = providers.Factory(entities.Movie)
|
||||
|
||||
|
@ -863,13 +829,11 @@ Now we need to read the value of the ``config.finder.type`` option from the envi
|
|||
Edit ``__main__.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 25
|
||||
:emphasize-lines: 22
|
||||
|
||||
"""Main module."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .listers import MovieLister
|
||||
from .containers import Container
|
||||
|
@ -877,19 +841,18 @@ Edit ``__main__.py``:
|
|||
|
||||
@inject
|
||||
def main(lister: MovieLister = Provide[Container.lister]) -> None:
|
||||
print('Francis Lawrence movies:')
|
||||
for movie in lister.movies_directed_by('Francis Lawrence'):
|
||||
print('\t-', movie)
|
||||
print("Francis Lawrence movies:")
|
||||
for movie in lister.movies_directed_by("Francis Lawrence"):
|
||||
print("\t-", movie)
|
||||
|
||||
print('2016 movies:')
|
||||
print("2016 movies:")
|
||||
for movie in lister.movies_released_in(2016):
|
||||
print('\t-', movie)
|
||||
print("\t-", movie)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.finder.type.from_env('MOVIE_FINDER_TYPE')
|
||||
container.config.finder.type.from_env("MOVIE_FINDER_TYPE")
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
|
||||
main()
|
||||
|
@ -948,7 +911,7 @@ Create ``tests.py`` in the ``movies`` package:
|
|||
and put next into it:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 35,50
|
||||
:emphasize-lines: 41,50
|
||||
|
||||
"""Tests module."""
|
||||
|
||||
|
@ -961,50 +924,50 @@ and put next into it:
|
|||
|
||||
@pytest.fixture
|
||||
def container():
|
||||
container = Container()
|
||||
container.config.from_dict({
|
||||
'finder': {
|
||||
'type': 'csv',
|
||||
'csv': {
|
||||
'path': '/fake-movies.csv',
|
||||
'delimiter': ',',
|
||||
},
|
||||
'sqlite': {
|
||||
'path': '/fake-movies.db',
|
||||
container = Container(
|
||||
config={
|
||||
"finder": {
|
||||
"type": "csv",
|
||||
"csv": {
|
||||
"path": "/fake-movies.csv",
|
||||
"delimiter": ",",
|
||||
},
|
||||
"sqlite": {
|
||||
"path": "/fake-movies.db",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
return container
|
||||
|
||||
|
||||
def test_movies_directed_by(container):
|
||||
@pytest.fixture
|
||||
def finder_mock(container):
|
||||
finder_mock = mock.Mock()
|
||||
finder_mock.find_all.return_value = [
|
||||
container.movie('The 33', 2015, 'Patricia Riggen'),
|
||||
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
|
||||
container.movie("The 33", 2015, "Patricia Riggen"),
|
||||
container.movie("The Jungle Book", 2016, "Jon Favreau"),
|
||||
]
|
||||
|
||||
return finder_mock
|
||||
|
||||
|
||||
def test_movies_directed_by(container, finder_mock):
|
||||
with container.finder.override(finder_mock):
|
||||
lister = container.lister()
|
||||
movies = lister.movies_directed_by('Jon Favreau')
|
||||
movies = lister.movies_directed_by("Jon Favreau")
|
||||
|
||||
assert len(movies) == 1
|
||||
assert movies[0].title == 'The Jungle Book'
|
||||
assert movies[0].title == "The Jungle Book"
|
||||
|
||||
|
||||
def test_movies_released_in(container):
|
||||
finder_mock = mock.Mock()
|
||||
finder_mock.find_all.return_value = [
|
||||
container.movie('The 33', 2015, 'Patricia Riggen'),
|
||||
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
|
||||
]
|
||||
|
||||
def test_movies_released_in(container, finder_mock):
|
||||
with container.finder.override(finder_mock):
|
||||
lister = container.lister()
|
||||
movies = lister.movies_released_in(2015)
|
||||
|
||||
assert len(movies) == 1
|
||||
assert movies[0].title == 'The 33'
|
||||
assert movies[0].title == "The 33"
|
||||
|
||||
Run in the terminal:
|
||||
|
||||
|
@ -1016,24 +979,24 @@ You should see:
|
|||
|
||||
.. code-block::
|
||||
|
||||
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
|
||||
plugins: cov-2.10.0
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: cov-3.0.0
|
||||
collected 2 items
|
||||
|
||||
movies/tests.py .. [100%]
|
||||
|
||||
---------- coverage: platform darwin, python 3.8.5-final-0 -----------
|
||||
---------- coverage: platform darwin, python 3.10 -----------
|
||||
Name Stmts Miss Cover
|
||||
------------------------------------------
|
||||
movies/__init__.py 0 0 100%
|
||||
movies/__main__.py 18 18 0%
|
||||
movies/__main__.py 16 16 0%
|
||||
movies/containers.py 9 0 100%
|
||||
movies/entities.py 7 1 86%
|
||||
movies/finders.py 26 13 50%
|
||||
movies/listers.py 8 0 100%
|
||||
movies/tests.py 24 0 100%
|
||||
------------------------------------------
|
||||
TOTAL 92 32 65%
|
||||
TOTAL 90 30 67%
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -1053,7 +1016,7 @@ We've used the ``Dependency Injector`` as a dependency injection framework.
|
|||
With a help of :ref:`containers` and :ref:`providers` we have defined how to assemble application components.
|
||||
|
||||
``Selector`` provider served as a switch for selecting the database format based on a configuration.
|
||||
:ref:`configuration-provider` helped to deal with reading YAML file and environment variable.
|
||||
:ref:`configuration-provider` helped to deal with reading a YAML file and environment variables.
|
||||
|
||||
We used :ref:`wiring` feature to inject the dependencies into the ``main()`` function.
|
||||
:ref:`provider-overriding` feature helped in testing.
|
||||
|
@ -1070,4 +1033,6 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -110,9 +110,9 @@ You should see something like:
|
|||
.. code-block:: bash
|
||||
|
||||
(venv) $ python -c "import dependency_injector; print(dependency_injector.__version__)"
|
||||
4.0.0
|
||||
4.37.0
|
||||
(venv) $ python -c "import flask; print(flask.__version__)"
|
||||
1.1.2
|
||||
2.0.2
|
||||
|
||||
*Versions can be different. That's fine.*
|
||||
|
||||
|
@ -129,7 +129,7 @@ Put next into the ``views.py``:
|
|||
|
||||
|
||||
def index():
|
||||
return 'Hello, World!'
|
||||
return "Hello, World!"
|
||||
|
||||
Ok, we have the view.
|
||||
|
||||
|
@ -170,7 +170,7 @@ Put next into the ``application.py``:
|
|||
|
||||
app = Flask(__name__)
|
||||
app.container = container
|
||||
app.add_url_rule('/', 'index', views.index)
|
||||
app.add_url_rule("/", "index", views.index)
|
||||
|
||||
return app
|
||||
|
||||
|
@ -246,7 +246,7 @@ Edit ``application.py``:
|
|||
|
||||
app = Flask(__name__)
|
||||
app.container = container
|
||||
app.add_url_rule('/', 'index', views.index)
|
||||
app.add_url_rule("/", "index", views.index)
|
||||
|
||||
bootstrap = Bootstrap()
|
||||
bootstrap.init_app(app)
|
||||
|
@ -398,13 +398,13 @@ Edit ``views.py``:
|
|||
|
||||
|
||||
def index():
|
||||
query = request.args.get('query', 'Dependency Injector')
|
||||
limit = request.args.get('limit', 10, int)
|
||||
query = request.args.get("query", "Dependency Injector")
|
||||
limit = request.args.get("limit", 10, int)
|
||||
|
||||
repositories = []
|
||||
|
||||
return render_template(
|
||||
'index.html',
|
||||
"index.html",
|
||||
query=query,
|
||||
limit=limit,
|
||||
repositories=repositories,
|
||||
|
@ -444,9 +444,10 @@ and run in the terminal:
|
|||
Now we need to add Github API client the container. We will need to add two more providers from
|
||||
the ``dependency_injector.providers`` module:
|
||||
|
||||
- ``Factory`` provider that will create ``Github`` client.
|
||||
- ``Configuration`` provider that will be used for providing the API token and the request timeout
|
||||
for the ``Github`` client.
|
||||
- ``Factory`` provider. It will create a ``Github`` client.
|
||||
- ``Configuration`` provider. It will provide an API token and a request timeout for the ``Github`` client.
|
||||
We will specify the location of the configuration file. The configuration provider will parse
|
||||
the configuration file when we create a container instance.
|
||||
|
||||
Edit ``containers.py``:
|
||||
|
||||
|
@ -461,7 +462,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
github_client = providers.Factory(
|
||||
Github,
|
||||
|
@ -469,23 +470,14 @@ Edit ``containers.py``:
|
|||
timeout=config.github.request_timeout,
|
||||
)
|
||||
|
||||
.. note::
|
||||
|
||||
We have used the configuration value before it was defined. That's the principle how
|
||||
``Configuration`` provider works.
|
||||
|
||||
Use first, define later.
|
||||
|
||||
.. note::
|
||||
|
||||
Don't forget to remove the Ellipsis ``...`` from the container. We don't need it anymore
|
||||
since we container is not empty.
|
||||
|
||||
Now let's add the configuration file.
|
||||
|
||||
We will use YAML.
|
||||
|
||||
Create an empty file ``config.yml`` in the root of the project:
|
||||
Now let's add the configuration file. We will use YAML. Create an empty file ``config.yml``
|
||||
in the root of the project:
|
||||
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 11
|
||||
|
@ -530,17 +522,13 @@ and install it:
|
|||
|
||||
pip install -r requirements.txt
|
||||
|
||||
We will use environment variable ``GITHUB_TOKEN`` to provide the API token.
|
||||
|
||||
Now we need to edit ``create_app()`` to make two things when application starts:
|
||||
|
||||
- Load the configuration file the ``config.yml``.
|
||||
- Load the API token from the ``GITHUB_TOKEN`` environment variable.
|
||||
We will use the ``GITHUB_TOKEN`` environment variable to provide the API token. Let's edit
|
||||
``create_app()`` to fetch the token value from it.
|
||||
|
||||
Edit ``application.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 12-13
|
||||
:emphasize-lines: 12
|
||||
|
||||
"""Application module."""
|
||||
|
||||
|
@ -553,12 +541,11 @@ Edit ``application.py``:
|
|||
|
||||
def create_app() -> Flask:
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.github.auth_token.from_env('GITHUB_TOKEN')
|
||||
container.config.github.auth_token.from_env("GITHUB_TOKEN")
|
||||
|
||||
app = Flask(__name__)
|
||||
app.container = container
|
||||
app.add_url_rule('/', 'index', views.index)
|
||||
app.add_url_rule("/", "index", views.index)
|
||||
|
||||
bootstrap = Bootstrap()
|
||||
bootstrap.init_app(app)
|
||||
|
@ -639,7 +626,7 @@ and put next into it:
|
|||
"""Search for repositories and return formatted data."""
|
||||
repositories = self._github_client.search_repositories(
|
||||
query=query,
|
||||
**{'in': 'name'},
|
||||
**{"in": "name"},
|
||||
)
|
||||
return [
|
||||
self._format_repo(repository)
|
||||
|
@ -649,22 +636,22 @@ and put next into it:
|
|||
def _format_repo(self, repository: Repository):
|
||||
commits = repository.get_commits()
|
||||
return {
|
||||
'url': repository.html_url,
|
||||
'name': repository.name,
|
||||
'owner': {
|
||||
'login': repository.owner.login,
|
||||
'url': repository.owner.html_url,
|
||||
'avatar_url': repository.owner.avatar_url,
|
||||
"url": repository.html_url,
|
||||
"name": repository.name,
|
||||
"owner": {
|
||||
"login": repository.owner.login,
|
||||
"url": repository.owner.html_url,
|
||||
"avatar_url": repository.owner.avatar_url,
|
||||
},
|
||||
'latest_commit': self._format_commit(commits[0]) if commits else {},
|
||||
"latest_commit": self._format_commit(commits[0]) if commits else {},
|
||||
}
|
||||
|
||||
def _format_commit(self, commit: Commit):
|
||||
return {
|
||||
'sha': commit.sha,
|
||||
'url': commit.html_url,
|
||||
'message': commit.commit.message,
|
||||
'author_name': commit.commit.author.name,
|
||||
"sha": commit.sha,
|
||||
"url": commit.html_url,
|
||||
"message": commit.commit.message,
|
||||
"author_name": commit.commit.author.name,
|
||||
}
|
||||
|
||||
Now let's add ``SearchService`` to the container.
|
||||
|
@ -684,7 +671,7 @@ Edit ``containers.py``:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
github_client = providers.Factory(
|
||||
Github,
|
||||
|
@ -720,50 +707,51 @@ Edit ``views.py``:
|
|||
|
||||
@inject
|
||||
def index(search_service: SearchService = Provide[Container.search_service]):
|
||||
query = request.args.get('query', 'Dependency Injector')
|
||||
limit = request.args.get('limit', 10, int)
|
||||
query = request.args.get("query", "Dependency Injector")
|
||||
limit = request.args.get("limit", 10, int)
|
||||
|
||||
repositories = search_service.search_repositories(query, limit)
|
||||
|
||||
return render_template(
|
||||
'index.html',
|
||||
"index.html",
|
||||
query=query,
|
||||
limit=limit,
|
||||
repositories=repositories,
|
||||
)
|
||||
|
||||
To make the injection work we need to wire the container instance with the ``views`` module.
|
||||
This needs to be done once. After it's done we can use ``Provide`` markers to specify as many
|
||||
injections as needed for any view.
|
||||
To make the injection work we need to wire the container with the ``views`` module.
|
||||
Let's configure the container to automatically make wiring with the ``views`` module when we
|
||||
create a container instance.
|
||||
|
||||
Edit ``application.py``:
|
||||
Edit ``containers.py``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 14
|
||||
:emphasize-lines: 11
|
||||
|
||||
"""Application module."""
|
||||
"""Containers module."""
|
||||
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap
|
||||
from dependency_injector import containers, providers
|
||||
from github import Github
|
||||
|
||||
from .containers import Container
|
||||
from . import views
|
||||
from . import services
|
||||
|
||||
|
||||
def create_app() -> Flask:
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.github.auth_token.from_env('GITHUB_TOKEN')
|
||||
container.wire(modules=[views])
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
app = Flask(__name__)
|
||||
app.container = container
|
||||
app.add_url_rule('/', 'index', views.index)
|
||||
wiring_config = containers.WiringConfiguration(modules=[".views"])
|
||||
|
||||
bootstrap = Bootstrap()
|
||||
bootstrap.init_app(app)
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
return app
|
||||
github_client = providers.Factory(
|
||||
Github,
|
||||
login_or_token=config.github.auth_token,
|
||||
timeout=config.github.request_timeout,
|
||||
)
|
||||
|
||||
search_service = providers.Factory(
|
||||
services.SearchService,
|
||||
github_client=github_client,
|
||||
)
|
||||
|
||||
Make sure the app is running or use ``flask run`` and open ``http://127.0.0.1:5000/``.
|
||||
|
||||
|
@ -801,13 +789,13 @@ Edit ``views.py``:
|
|||
default_query: str = Provide[Container.config.default.query],
|
||||
default_limit: int = Provide[Container.config.default.limit.as_int()],
|
||||
):
|
||||
query = request.args.get('query', default_query)
|
||||
limit = request.args.get('limit', default_limit, int)
|
||||
query = request.args.get("query", default_query)
|
||||
limit = request.args.get("limit", default_limit, int)
|
||||
|
||||
repositories = search_service.search_repositories(query, limit)
|
||||
|
||||
return render_template(
|
||||
'index.html',
|
||||
"index.html",
|
||||
query=query,
|
||||
limit=limit,
|
||||
repositories=repositories,
|
||||
|
@ -900,44 +888,44 @@ and put next into it:
|
|||
github_client_mock = mock.Mock(spec=Github)
|
||||
github_client_mock.search_repositories.return_value = [
|
||||
mock.Mock(
|
||||
html_url='repo1-url',
|
||||
name='repo1-name',
|
||||
html_url="repo1-url",
|
||||
name="repo1-name",
|
||||
owner=mock.Mock(
|
||||
login='owner1-login',
|
||||
html_url='owner1-url',
|
||||
avatar_url='owner1-avatar-url',
|
||||
login="owner1-login",
|
||||
html_url="owner1-url",
|
||||
avatar_url="owner1-avatar-url",
|
||||
),
|
||||
get_commits=mock.Mock(return_value=[mock.Mock()]),
|
||||
),
|
||||
mock.Mock(
|
||||
html_url='repo2-url',
|
||||
name='repo2-name',
|
||||
html_url="repo2-url",
|
||||
name="repo2-name",
|
||||
owner=mock.Mock(
|
||||
login='owner2-login',
|
||||
html_url='owner2-url',
|
||||
avatar_url='owner2-avatar-url',
|
||||
login="owner2-login",
|
||||
html_url="owner2-url",
|
||||
avatar_url="owner2-avatar-url",
|
||||
),
|
||||
get_commits=mock.Mock(return_value=[mock.Mock()]),
|
||||
),
|
||||
]
|
||||
|
||||
with app.container.github_client.override(github_client_mock):
|
||||
response = client.get(url_for('index'))
|
||||
response = client.get(url_for("index"))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b'Results found: 2' in response.data
|
||||
assert b"Results found: 2" in response.data
|
||||
|
||||
assert b'repo1-url' in response.data
|
||||
assert b'repo1-name' in response.data
|
||||
assert b'owner1-login' in response.data
|
||||
assert b'owner1-url' in response.data
|
||||
assert b'owner1-avatar-url' in response.data
|
||||
assert b"repo1-url" in response.data
|
||||
assert b"repo1-name" in response.data
|
||||
assert b"owner1-login" in response.data
|
||||
assert b"owner1-url" in response.data
|
||||
assert b"owner1-avatar-url" in response.data
|
||||
|
||||
assert b'repo2-url' in response.data
|
||||
assert b'repo2-name' in response.data
|
||||
assert b'owner2-login' in response.data
|
||||
assert b'owner2-url' in response.data
|
||||
assert b'owner2-avatar-url' in response.data
|
||||
assert b"repo2-url" in response.data
|
||||
assert b"repo2-name" in response.data
|
||||
assert b"owner2-login" in response.data
|
||||
assert b"owner2-url" in response.data
|
||||
assert b"owner2-avatar-url" in response.data
|
||||
|
||||
|
||||
def test_index_no_results(client, app):
|
||||
|
@ -945,10 +933,10 @@ and put next into it:
|
|||
github_client_mock.search_repositories.return_value = []
|
||||
|
||||
with app.container.github_client.override(github_client_mock):
|
||||
response = client.get(url_for('index'))
|
||||
response = client.get(url_for("index"))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b'Results found: 0' in response.data
|
||||
assert b"Results found: 0" in response.data
|
||||
|
||||
Now let's run it and check the coverage:
|
||||
|
||||
|
@ -960,23 +948,23 @@ You should see:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
|
||||
plugins: flask-1.0.0, cov-2.10.0
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: cov-3.0.0, flask-1.2.0
|
||||
collected 2 items
|
||||
|
||||
githubnavigator/tests.py .. [100%]
|
||||
|
||||
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
|
||||
---------- coverage: platform darwin, python 3.10.0-final-0 ----------
|
||||
Name Stmts Miss Cover
|
||||
----------------------------------------------------
|
||||
githubnavigator/__init__.py 0 0 100%
|
||||
githubnavigator/application.py 15 0 100%
|
||||
githubnavigator/containers.py 7 0 100%
|
||||
githubnavigator/application.py 13 0 100%
|
||||
githubnavigator/containers.py 8 0 100%
|
||||
githubnavigator/services.py 14 0 100%
|
||||
githubnavigator/tests.py 34 0 100%
|
||||
githubnavigator/views.py 10 0 100%
|
||||
----------------------------------------------------
|
||||
TOTAL 80 0 100%
|
||||
TOTAL 79 0 100%
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -1010,5 +998,6 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
265
docs/wiring.rst
265
docs/wiring.rst
|
@ -22,6 +22,82 @@ To use wiring you need:
|
|||
:local:
|
||||
:backlinks: none
|
||||
|
||||
Decorator @inject
|
||||
-----------------
|
||||
|
||||
Decorator ``@inject`` injects the dependencies. Use it to decorate all functions and methods
|
||||
with the injections.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
@inject
|
||||
def foo(bar: Bar = Provide[Container.bar]):
|
||||
...
|
||||
|
||||
Decorator ``@inject`` must be specified as a very first decorator of a function to ensure that
|
||||
the wiring works appropriately. This will also contribute to the performance of the wiring process.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
@decorator_etc
|
||||
@decorator_2
|
||||
@decorator_1
|
||||
@inject
|
||||
def foo(bar: Bar = Provide[Container.bar]):
|
||||
...
|
||||
|
||||
Specifying the ``@inject`` as a first decorator is also crucial for FastAPI, other frameworks
|
||||
using decorators similarly, for closures, and for any types of custom decorators with the injections.
|
||||
|
||||
FastAPI example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.api_route("/")
|
||||
@inject
|
||||
async def index(service: Annotated[Service, Depends(Provide[Container.service])]):
|
||||
value = await service.process()
|
||||
return {"result": value}
|
||||
|
||||
Decorators example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def decorator1(func):
|
||||
@functools.wraps(func)
|
||||
@inject
|
||||
def wrapper(value1: int = Provide[Container.config.value1]):
|
||||
result = func()
|
||||
return result + value1
|
||||
return wrapper
|
||||
|
||||
|
||||
def decorator2(func):
|
||||
@functools.wraps(func)
|
||||
@inject
|
||||
def wrapper(value2: int = Provide[Container.config.value2]):
|
||||
result = func()
|
||||
return result + value2
|
||||
return wrapper
|
||||
|
||||
@decorator1
|
||||
@decorator2
|
||||
def sample():
|
||||
...
|
||||
|
||||
.. seealso::
|
||||
`Issue #404 <https://github.com/ets-labs/python-dependency-injector/issues/404#issuecomment-785216978>`_
|
||||
explains ``@inject`` decorator in a few more details.
|
||||
|
||||
Markers
|
||||
-------
|
||||
|
||||
|
@ -39,19 +115,29 @@ a function or method argument:
|
|||
|
||||
Specifying an annotation is optional.
|
||||
|
||||
There are two types of markers:
|
||||
|
||||
- ``Provide[foo]`` - call the provider ``foo`` and injects the result
|
||||
- ``Provider[foo]`` - injects the provider ``foo`` itself
|
||||
To inject the provider itself use ``Provide[foo.provider]``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.providers import Factory
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
@inject
|
||||
def foo(bar_provider: Factory[Bar] = Provide[Container.bar.provider]):
|
||||
bar = bar_provider(argument="baz")
|
||||
...
|
||||
You can also use ``Provider[foo]`` for injecting the provider itself:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.providers import Factory
|
||||
from dependency_injector.wiring import inject, Provider
|
||||
|
||||
|
||||
@inject
|
||||
def foo(bar_provider: Callable[..., Bar] = Provider[Container.bar]):
|
||||
bar = bar_provider()
|
||||
def foo(bar_provider: Factory[Bar] = Provider[Container.bar]):
|
||||
bar = bar_provider(argument="baz")
|
||||
...
|
||||
|
||||
You can use configuration, provided instance and sub-container providers as you normally do.
|
||||
|
@ -85,17 +171,17 @@ Also you can use ``Provide`` marker to inject a container.
|
|||
|
||||
.. literalinclude:: ../examples/wiring/example_container.py
|
||||
:language: python
|
||||
:emphasize-lines: 16-19
|
||||
:emphasize-lines: 14-17
|
||||
:lines: 3-
|
||||
|
||||
Strings identifiers
|
||||
-------------------
|
||||
String identifiers
|
||||
------------------
|
||||
|
||||
You can use wiring with string identifiers. String identifier should match provider name in the container:
|
||||
|
||||
.. literalinclude:: ../examples/wiring/example_string_id.py
|
||||
:language: python
|
||||
:emphasize-lines: 17
|
||||
:emphasize-lines: 15
|
||||
:lines: 3-
|
||||
|
||||
With string identifiers you don't need to use a container to specify an injection.
|
||||
|
@ -105,7 +191,7 @@ To specify an injection from a nested container use point ``.`` as a separator:
|
|||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
def foo(service: UserService = Provide['services.user']) -> None:
|
||||
def foo(service: UserService = Provide["services.user"]) -> None:
|
||||
...
|
||||
|
||||
You can also use injection modifiers:
|
||||
|
@ -125,34 +211,34 @@ You can also use injection modifiers:
|
|||
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide['config.option', as_int()]) -> None:
|
||||
def foo(value: int = Provide["config.option", as_int()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: float = Provide['config.option', as_float()]) -> None:
|
||||
def foo(value: float = Provide["config.option", as_float()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: Decimal = Provide['config.option', as_(Decimal)]) -> None:
|
||||
def foo(value: Decimal = Provide["config.option", as_(Decimal)]) -> None:
|
||||
...
|
||||
|
||||
@inject
|
||||
def foo(value: str = Provide['config.option', required()]) -> None:
|
||||
def foo(value: str = Provide["config.option", required()]) -> None:
|
||||
...
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide['config.option', required().as_int()]) -> None:
|
||||
def foo(value: int = Provide["config.option", required().as_int()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide['config.option', invariant('config.switch')]) -> None:
|
||||
def foo(value: int = Provide["config.option", invariant("config.switch")]) -> None:
|
||||
...
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide['service', provided().foo['bar'].call()]) -> None:
|
||||
def foo(value: int = Provide["service", provided().foo["bar"].call()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
|
@ -161,7 +247,7 @@ To inject a container use special identifier ``<container>``:
|
|||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
def foo(container: Container = Provide['<container>']) -> None:
|
||||
def foo(container: Container = Provide["<container>"]) -> None:
|
||||
...
|
||||
|
||||
|
||||
|
@ -173,25 +259,63 @@ You can use wiring to make injections into modules and class attributes.
|
|||
.. literalinclude:: ../examples/wiring/example_attribute.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 16,21
|
||||
:emphasize-lines: 14,19
|
||||
|
||||
You could also use string identifiers to avoid a dependency on a container:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1,6
|
||||
|
||||
service: Service = Provide['service']
|
||||
service: Service = Provide["service"]
|
||||
|
||||
|
||||
class Main:
|
||||
|
||||
service: Service = Provide['service']
|
||||
service: Service = Provide["service"]
|
||||
|
||||
Wiring with modules and packages
|
||||
--------------------------------
|
||||
|
||||
To wire a container with a module you need to call ``container.wire(modules=[...])`` method. Argument
|
||||
``modules`` is an iterable of the module objects.
|
||||
To wire a container with the modules you need to call ``container.wire()`` method:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.wire(
|
||||
modules=[
|
||||
"yourapp.module1",
|
||||
"yourapp.module2",
|
||||
],
|
||||
)
|
||||
|
||||
Method ``container.wire()`` can resolve relative imports:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# In module "yourapp.main":
|
||||
|
||||
container.wire(
|
||||
modules=[
|
||||
".module1", # Resolved to: "yourapp.module1"
|
||||
".module2", # Resolved to: "yourapp.module2"
|
||||
],
|
||||
)
|
||||
|
||||
You can also manually specify a base package for resolving relative imports with
|
||||
the ``from_package`` argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# In module "yourapp.main":
|
||||
|
||||
container.wire(
|
||||
modules=[
|
||||
".module1", # Resolved to: "anotherapp.module1"
|
||||
".module2", # Resolved to: "anotherapp.module2"
|
||||
],
|
||||
from_package="anotherapp",
|
||||
)
|
||||
|
||||
Argument ``modules`` can also take already imported modules:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -201,15 +325,16 @@ To wire a container with a module you need to call ``container.wire(modules=[...
|
|||
container = Container()
|
||||
container.wire(modules=[module1, module2])
|
||||
|
||||
You can wire container with a package. Container walks recursively over package modules.
|
||||
You can wire container with a package. Container walks recursively over the package modules:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from yourapp import package1, package2
|
||||
|
||||
|
||||
container = Container()
|
||||
container.wire(packages=[package1, package2])
|
||||
container.wire(
|
||||
packages=[
|
||||
"yourapp.package1",
|
||||
"yourapp.package2",
|
||||
],
|
||||
)
|
||||
|
||||
Arguments ``modules`` and ``packages`` can be used together.
|
||||
|
||||
|
@ -223,7 +348,7 @@ When wiring is done functions and methods with the markers are patched to provid
|
|||
|
||||
|
||||
container = Container()
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
foo() # <--- Argument "bar" is injected
|
||||
|
||||
|
@ -257,7 +382,7 @@ You can use that in testing to re-create and re-wire a container before each tes
|
|||
|
||||
def setUp(self):
|
||||
self.container = Container()
|
||||
self.container.wire(modules=[module1, module2])
|
||||
self.container.wire(modules=["yourapp.module1", "yourapp.module2"])
|
||||
self.addCleanup(self.container.unwire)
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -268,7 +393,7 @@ You can use that in testing to re-create and re-wire a container before each tes
|
|||
@pytest.fixture
|
||||
def container():
|
||||
container = Container()
|
||||
container.wire(modules=[module1, module2])
|
||||
container.wire(modules=["yourapp.module1", "yourapp.module2"])
|
||||
yield container
|
||||
container.unwire()
|
||||
|
||||
|
@ -299,6 +424,76 @@ You can use that in testing to re-create and re-wire a container before each tes
|
|||
|
||||
module.fn()
|
||||
|
||||
Wiring configuration
|
||||
--------------------
|
||||
|
||||
You can specify wiring configuration in the container. When wiring configuration is defined,
|
||||
container will call method ``.wire()`` automatically when you create an instance:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
wiring_config = containers.WiringConfiguration(
|
||||
modules=[
|
||||
"yourapp.module1",
|
||||
"yourapp.module2",
|
||||
],
|
||||
packages=[
|
||||
"yourapp.package1",
|
||||
"yourapp.package2",
|
||||
],
|
||||
)
|
||||
|
||||
...
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # container.wire() is called automatically
|
||||
...
|
||||
|
||||
You can also use relative imports. Container will resolve them corresponding
|
||||
to the module of the container class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# In module "yourapp.container":
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
wiring_config = containers.WiringConfiguration(
|
||||
modules=[
|
||||
".module1", # Resolved to: "yourapp.module1"
|
||||
".module2", # Resolved to: "yourapp.module2"
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# In module "yourapp.foo.bar.main":
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # wire to "yourapp.module1" and "yourapp.module2"
|
||||
...
|
||||
|
||||
To use wiring configuration and call method ``.wire()`` manually, set flag ``auto_wire=False``:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 5
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
wiring_config = containers.WiringConfiguration(
|
||||
modules=["yourapp.module1"],
|
||||
auto_wire=False,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
container = Container() # container.wire() is NOT called automatically
|
||||
container.wire() # wire to "yourapp.module1"
|
||||
...
|
||||
|
||||
.. _async-injections-wiring:
|
||||
|
||||
Asynchronous injections
|
||||
|
@ -392,11 +587,11 @@ This is useful when you import modules dynamically.
|
|||
from .containers import Container
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
register_loader_containers(container) # <--- installs import hook
|
||||
|
||||
module = importlib.import_module('package.module')
|
||||
module = importlib.import_module("package.module")
|
||||
module.foo()
|
||||
|
||||
You can register multiple containers in the import hook. For doing this call register function
|
||||
|
|
|
@ -9,7 +9,7 @@ class Container(containers.DeclarativeContainer):
|
|||
service2 = providers.Dependency()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.check_dependencies() # <-- raises error:
|
||||
# Container has undefined dependencies: "Container.service1", "Container.service2"
|
||||
|
|
|
@ -10,7 +10,7 @@ class Container(containers.DeclarativeContainer):
|
|||
factory2 = providers.Factory(object)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
object1 = container.factory1()
|
||||
|
@ -18,6 +18,6 @@ if __name__ == '__main__':
|
|||
|
||||
print(container.providers)
|
||||
# {
|
||||
# 'factory1': <dependency_injector.providers.Factory(...),
|
||||
# 'factory2': <dependency_injector.providers.Factory(...),
|
||||
# "factory1": <dependency_injector.providers.Factory(...),
|
||||
# "factory2": <dependency_injector.providers.Factory(...),
|
||||
# }
|
||||
|
|
|
@ -13,7 +13,7 @@ class Service:
|
|||
|
||||
class SourceContainer(containers.DeclarativeContainer):
|
||||
|
||||
database = providers.Singleton(sqlite3.connect, ':memory:')
|
||||
database = providers.Singleton(sqlite3.connect, ":memory:")
|
||||
service = providers.Factory(Service, db=database)
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ class DestinationContainer(SourceContainer):
|
|||
database = providers.Singleton(mock.Mock)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = DestinationContainer()
|
||||
|
||||
service = container.service()
|
||||
|
|
|
@ -9,21 +9,21 @@ class Service:
|
|||
|
||||
|
||||
class Base(containers.DeclarativeContainer):
|
||||
dependency = providers.Dependency(instance_of=str, default='Default value')
|
||||
dependency = providers.Dependency(instance_of=str, default="Default value")
|
||||
service = providers.Factory(Service, dependency=dependency)
|
||||
|
||||
|
||||
@containers.copy(Base)
|
||||
class Derived1(Base):
|
||||
dependency = providers.Dependency(instance_of=str, default='Derived 1')
|
||||
dependency = providers.Dependency(instance_of=str, default="Derived 1")
|
||||
|
||||
|
||||
# @containers.copy(Base) # <-- No @copy decorator
|
||||
class Derived2(Base):
|
||||
dependency = providers.Dependency(instance_of=str, default='Derived 2')
|
||||
dependency = providers.Dependency(instance_of=str, default="Derived 2")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container1 = Derived1()
|
||||
service1 = container1.service()
|
||||
print(service1.dependency) # Derived 1
|
||||
|
|
|
@ -14,21 +14,21 @@ class ContainerB(ContainerA):
|
|||
|
||||
|
||||
assert ContainerA.providers == {
|
||||
'provider1': ContainerA.provider1,
|
||||
"provider1": ContainerA.provider1,
|
||||
}
|
||||
assert ContainerB.providers == {
|
||||
'provider1': ContainerA.provider1,
|
||||
'provider2': ContainerB.provider2,
|
||||
"provider1": ContainerA.provider1,
|
||||
"provider2": ContainerB.provider2,
|
||||
}
|
||||
|
||||
assert ContainerA.cls_providers == {
|
||||
'provider1': ContainerA.provider1,
|
||||
"provider1": ContainerA.provider1,
|
||||
}
|
||||
assert ContainerB.cls_providers == {
|
||||
'provider2': ContainerB.provider2,
|
||||
"provider2": ContainerB.provider2,
|
||||
}
|
||||
|
||||
assert ContainerA.inherited_providers == {}
|
||||
assert ContainerB.inherited_providers == {
|
||||
'provider1': ContainerA.provider1,
|
||||
"provider1": ContainerA.provider1,
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class AuthService:
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
database = providers.Singleton(sqlite3.connect, ':memory:')
|
||||
database = providers.Singleton(sqlite3.connect, ":memory:")
|
||||
|
||||
user_service = providers.Factory(
|
||||
UserService,
|
||||
|
@ -32,7 +32,7 @@ class Container(containers.DeclarativeContainer):
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
user_service = container.user_service()
|
||||
|
|
|
@ -8,7 +8,7 @@ from dependency_injector import containers, providers
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
database = providers.Singleton(sqlite3.connect, ':memory:')
|
||||
database = providers.Singleton(sqlite3.connect, ":memory:")
|
||||
|
||||
|
||||
# Overriding ``Container`` with ``OverridingContainer``:
|
||||
|
@ -18,7 +18,7 @@ class OverridingContainer(containers.DeclarativeContainer):
|
|||
database = providers.Singleton(mock.Mock)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
database = container.database()
|
||||
|
|
|
@ -8,10 +8,10 @@ from dependency_injector import containers, providers
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
database = providers.Singleton(sqlite3.connect, ':memory:')
|
||||
database = providers.Singleton(sqlite3.connect, ":memory:")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container(database=mock.Mock(sqlite3.Connection))
|
||||
|
||||
database = container.database()
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from dependency_injector import containers, providers
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = containers.DynamicContainer()
|
||||
container.factory1 = providers.Factory(object)
|
||||
container.factory2 = providers.Factory(object)
|
||||
|
@ -13,6 +13,6 @@ if __name__ == '__main__':
|
|||
|
||||
print(container.providers)
|
||||
# {
|
||||
# 'factory1': <dependency_injector.providers.Factory(...),
|
||||
# 'factory2': <dependency_injector.providers.Factory(...),
|
||||
# "factory1": <dependency_injector.providers.Factory(...),
|
||||
# "factory2": <dependency_injector.providers.Factory(...),
|
||||
# }
|
||||
|
|
|
@ -13,20 +13,20 @@ class AuthService:
|
|||
|
||||
def populate_container(container, providers_config):
|
||||
for provider_name, provider_info in providers_config.items():
|
||||
provided_cls = globals().get(provider_info['class'])
|
||||
provider_cls = getattr(providers, provider_info['provider_class'])
|
||||
provided_cls = globals().get(provider_info["class"])
|
||||
provider_cls = getattr(providers, provider_info["provider_class"])
|
||||
setattr(container, provider_name, provider_cls(provided_cls))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
services_config = {
|
||||
'user': {
|
||||
'class': 'UserService',
|
||||
'provider_class': 'Factory',
|
||||
"user": {
|
||||
"class": "UserService",
|
||||
"provider_class": "Factory",
|
||||
},
|
||||
'auth': {
|
||||
'class': 'AuthService',
|
||||
'provider_class': 'Factory',
|
||||
"auth": {
|
||||
"class": "AuthService",
|
||||
"provider_class": "Factory",
|
||||
},
|
||||
}
|
||||
services = containers.DynamicContainer()
|
||||
|
|
|
@ -21,14 +21,14 @@ class Container(containers.DeclarativeContainer):
|
|||
|
||||
__self__ = providers.Self()
|
||||
|
||||
service1 = providers.Factory(Service, name='Service 1')
|
||||
service2 = providers.Factory(Service, name='Service 2')
|
||||
service3 = providers.Factory(Service, name='Service 3')
|
||||
service1 = providers.Factory(Service, name="Service 1")
|
||||
service2 = providers.Factory(Service, name="Service 2")
|
||||
service3 = providers.Factory(Service, name="Service 3")
|
||||
|
||||
dispatcher = providers.Singleton(ServiceDispatcher, __self__)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
dispatcher = container.dispatcher()
|
||||
|
|
|
@ -21,7 +21,7 @@ class OverridingContainer(containers.DeclarativeContainer):
|
|||
service = providers.Factory(ServiceStub)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
overriding_container = OverridingContainer()
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class Container(containers.DeclarativeContainer):
|
|||
service2 = providers.Singleton(object)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
service1 = container.service1()
|
||||
|
|
|
@ -14,7 +14,7 @@ class Container(containers.DeclarativeContainer):
|
|||
sub = providers.Container(SubContainer)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
service1 = container.service()
|
||||
|
|
|
@ -8,7 +8,7 @@ class Container(containers.DeclarativeContainer):
|
|||
service = providers.Singleton(object)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
service1 = container.service()
|
||||
|
|
|
@ -34,15 +34,15 @@ class Container(containers.DeclarativeContainer):
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
|
||||
for provider in container.traverse():
|
||||
print(provider)
|
||||
|
||||
# <dependency_injector.providers.Configuration('config') at 0x10d37d200>
|
||||
# <dependency_injector.providers.Factory(<class '__main__.Service'>) at 0x10d3a2820>
|
||||
# <dependency_injector.providers.Configuration("config") at 0x10d37d200>
|
||||
# <dependency_injector.providers.Factory(<class "__main__.Service">) at 0x10d3a2820>
|
||||
# <dependency_injector.providers.Resource(<function init_database at 0x10bd2cb80>) at 0x10d346b40>
|
||||
# <dependency_injector.providers.ConfigurationOption('config.cache_hosts') at 0x10d37d350>
|
||||
# <dependency_injector.providers.ConfigurationOption("config.cache_hosts") at 0x10d37d350>
|
||||
# <dependency_injector.providers.Resource(<function init_cache at 0x10be373a0>) at 0x10d346bc0>
|
||||
# <dependency_injector.providers.ConfigurationOption('config.database_url') at 0x10d37d2e0>
|
||||
# <dependency_injector.providers.ConfigurationOption("config.database_url") at 0x10d37d2e0>
|
||||
|
|
|
@ -3,27 +3,27 @@ import os
|
|||
|
||||
class ApiClient:
|
||||
|
||||
def __init__(self, api_key: str, timeout: int):
|
||||
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):
|
||||
def __init__(self, api_client: ApiClient) -> None:
|
||||
self.api_client = api_client # <-- dependency is injected
|
||||
|
||||
|
||||
def main(service: Service): # <-- dependency is injected
|
||||
def main(service: Service) -> None: # <-- dependency is injected
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main(
|
||||
service=Service(
|
||||
api_client=ApiClient(
|
||||
api_key=os.getenv('API_KEY'),
|
||||
timeout=os.getenv('TIMEOUT'),
|
||||
api_key=os.getenv("API_KEY"),
|
||||
timeout=int(os.getenv("TIMEOUT")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -4,8 +4,8 @@ import os
|
|||
class ApiClient:
|
||||
|
||||
def __init__(self):
|
||||
self.api_key = os.getenv('API_KEY') # <-- dependency
|
||||
self.timeout = os.getenv('TIMEOUT') # <-- dependency
|
||||
self.api_key = os.getenv("API_KEY") # <-- dependency
|
||||
self.timeout = os.getenv("TIMEOUT") # <-- dependency
|
||||
|
||||
|
||||
class Service:
|
||||
|
@ -19,5 +19,5 @@ def main() -> None:
|
|||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import sys
|
||||
from unittest import mock
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from after import ApiClient, Service
|
||||
|
||||
|
@ -14,7 +13,7 @@ class Container(containers.DeclarativeContainer):
|
|||
api_client = providers.Singleton(
|
||||
ApiClient,
|
||||
api_key=config.api_key,
|
||||
timeout=config.timeout.as_int(),
|
||||
timeout=config.timeout,
|
||||
)
|
||||
|
||||
service = providers.Factory(
|
||||
|
@ -24,15 +23,15 @@ class Container(containers.DeclarativeContainer):
|
|||
|
||||
|
||||
@inject
|
||||
def main(service: Service = Provide[Container.service]):
|
||||
def main(service: Service = Provide[Container.service]) -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.api_key.from_env('API_KEY')
|
||||
container.config.timeout.from_env('TIMEOUT')
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
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
|
||||
|
||||
|
|
|
@ -27,16 +27,16 @@ To run the application do:
|
|||
.. code-block:: bash
|
||||
|
||||
export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0
|
||||
adev runserver giphynavigator/application.py --livereload
|
||||
python -m giphynavigator.application
|
||||
|
||||
The output should be something like:
|
||||
|
||||
.. code-block::
|
||||
|
||||
[18:52:59] Starting aux server at http://localhost:8001 ◆
|
||||
[18:52:59] Starting dev server at http://localhost:8000 ●
|
||||
======== Running on http://0.0.0.0:8080 ========
|
||||
(Press CTRL+C to quit)
|
||||
|
||||
After that visit http://127.0.0.1:8000/ in your browser or use CLI command (``curl``, ``httpie``,
|
||||
After that visit http://0.0.0.0:8080/ in your browser or use CLI command (``curl``, ``httpie``,
|
||||
etc). You should see something like:
|
||||
|
||||
.. code-block:: json
|
||||
|
@ -98,21 +98,22 @@ The output should be something like:
|
|||
|
||||
.. code-block::
|
||||
|
||||
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
|
||||
plugins: cov-2.10.0, aiohttp-0.3.0, asyncio-0.14.0
|
||||
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0, aiohttp-1.0.5
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
collected 3 items
|
||||
|
||||
giphynavigator/tests.py ... [100%]
|
||||
|
||||
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
|
||||
---------- coverage: platform darwin, python 3.10.0-final-0 ----------
|
||||
Name Stmts Miss Cover
|
||||
---------------------------------------------------
|
||||
giphynavigator/__init__.py 0 0 100%
|
||||
giphynavigator/application.py 12 0 100%
|
||||
giphynavigator/containers.py 6 0 100%
|
||||
giphynavigator/application.py 13 2 85%
|
||||
giphynavigator/containers.py 7 0 100%
|
||||
giphynavigator/giphy.py 14 9 36%
|
||||
giphynavigator/handlers.py 10 0 100%
|
||||
giphynavigator/services.py 9 1 89%
|
||||
giphynavigator/tests.py 37 0 100%
|
||||
---------------------------------------------------
|
||||
TOTAL 88 10 89%
|
||||
TOTAL 90 12 87%
|
||||
|
|
|
@ -8,13 +8,16 @@ from . import handlers
|
|||
|
||||
def create_app() -> web.Application:
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.config.giphy.api_key.from_env('GIPHY_API_KEY')
|
||||
container.wire(modules=[handlers])
|
||||
container.config.giphy.api_key.from_env("GIPHY_API_KEY")
|
||||
|
||||
app = web.Application()
|
||||
app.container = container
|
||||
app.add_routes([
|
||||
web.get('/', handlers.index),
|
||||
web.get("/", handlers.index),
|
||||
])
|
||||
return app
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = create_app()
|
||||
web.run_app(app)
|
||||
|
|
|
@ -7,7 +7,9 @@ from . import giphy, services
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
wiring_config = containers.WiringConfiguration(modules=[".handlers"])
|
||||
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
giphy_client = providers.Factory(
|
||||
giphy.GiphyClient,
|
||||
|
|
|
@ -5,7 +5,7 @@ from aiohttp import ClientSession, ClientTimeout
|
|||
|
||||
class GiphyClient:
|
||||
|
||||
API_URL = 'https://api.giphy.com/v1'
|
||||
API_URL = "https://api.giphy.com/v1"
|
||||
|
||||
def __init__(self, api_key, timeout):
|
||||
self._api_key = api_key
|
||||
|
@ -13,11 +13,11 @@ class GiphyClient:
|
|||
|
||||
async def search(self, query, limit):
|
||||
"""Make search API call and return result."""
|
||||
url = f'{self.API_URL}/gifs/search'
|
||||
url = f"{self.API_URL}/gifs/search"
|
||||
params = {
|
||||
'q': query,
|
||||
'api_key': self._api_key,
|
||||
'limit': limit,
|
||||
"q": query,
|
||||
"api_key": self._api_key,
|
||||
"limit": limit,
|
||||
}
|
||||
async with ClientSession(timeout=self._timeout) as session:
|
||||
async with session.get(url, params=params) as response:
|
||||
|
|
|
@ -14,15 +14,15 @@ async def index(
|
|||
default_query: str = Provide[Container.config.default.query],
|
||||
default_limit: int = Provide[Container.config.default.limit.as_int()],
|
||||
) -> web.Response:
|
||||
query = request.query.get('query', default_query)
|
||||
limit = int(request.query.get('limit', default_limit))
|
||||
query = request.query.get("query", default_query)
|
||||
limit = int(request.query.get("limit", default_limit))
|
||||
|
||||
gifs = await search_service.search(query, limit)
|
||||
|
||||
return web.json_response(
|
||||
{
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
'gifs': gifs,
|
||||
"query": query,
|
||||
"limit": limit,
|
||||
"gifs": gifs,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -15,4 +15,4 @@ class SearchService:
|
|||
|
||||
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"]]
|
||||
|
|
|
@ -3,11 +3,15 @@
|
|||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from giphynavigator.application import create_app
|
||||
from giphynavigator.giphy import GiphyClient
|
||||
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = create_app()
|
||||
|
@ -15,37 +19,37 @@ def app():
|
|||
app.container.unwire()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(app, aiohttp_client, loop):
|
||||
return loop.run_until_complete(aiohttp_client(app))
|
||||
@pytest_asyncio.fixture
|
||||
async def client(app, aiohttp_client):
|
||||
return await aiohttp_client(app)
|
||||
|
||||
|
||||
async def test_index(client, app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [
|
||||
{'url': 'https://giphy.com/gif1.gif'},
|
||||
{'url': 'https://giphy.com/gif2.gif'},
|
||||
"data": [
|
||||
{"url": "https://giphy.com/gif1.gif"},
|
||||
{"url": "https://giphy.com/gif2.gif"},
|
||||
],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
response = await client.get(
|
||||
'/',
|
||||
"/",
|
||||
params={
|
||||
'query': 'test',
|
||||
'limit': 10,
|
||||
"query": "test",
|
||||
"limit": 10,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data == {
|
||||
'query': 'test',
|
||||
'limit': 10,
|
||||
'gifs': [
|
||||
{'url': 'https://giphy.com/gif1.gif'},
|
||||
{'url': 'https://giphy.com/gif2.gif'},
|
||||
"query": "test",
|
||||
"limit": 10,
|
||||
"gifs": [
|
||||
{"url": "https://giphy.com/gif1.gif"},
|
||||
{"url": "https://giphy.com/gif2.gif"},
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -53,27 +57,27 @@ async def test_index(client, app):
|
|||
async def test_index_no_data(client, app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [],
|
||||
"data": [],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
response = await client.get('/')
|
||||
response = await client.get("/")
|
||||
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data['gifs'] == []
|
||||
assert data["gifs"] == []
|
||||
|
||||
|
||||
async def test_index_default_params(client, app):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
'data': [],
|
||||
"data": [],
|
||||
}
|
||||
|
||||
with app.container.giphy_client.override(giphy_client_mock):
|
||||
response = await client.get('/')
|
||||
response = await client.get("/")
|
||||
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data['query'] == app.container.config.default.query()
|
||||
assert data['limit'] == app.container.config.default.limit()
|
||||
assert data["query"] == app.container.config.default.query()
|
||||
assert data["limit"] == app.container.config.default.limit()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
dependency-injector
|
||||
aiohttp
|
||||
aiohttp-devtools
|
||||
pyyaml
|
||||
pytest-aiohttp
|
||||
pytest-asyncio
|
||||
pytest-cov
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from .containers import Application
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
application = Application()
|
||||
config = application.service.config()
|
||||
config.build()
|
||||
|
|
|
@ -6,17 +6,17 @@ from .services import ConfigService
|
|||
|
||||
|
||||
class Core(containers.DeclarativeContainer):
|
||||
config = providers.Configuration('config')
|
||||
config = providers.Configuration("config")
|
||||
|
||||
|
||||
class Storage(containers.DeclarativeContainer):
|
||||
queue = providers.Singleton(lambda: 'Some storage')
|
||||
queue = providers.Singleton(lambda: "Some storage")
|
||||
|
||||
|
||||
class Adapter(containers.DeclarativeContainer):
|
||||
core = providers.DependenciesContainer(config=providers.Configuration())
|
||||
tinydb = providers.Singleton(
|
||||
lambda db_path: f'DB Path=[{db_path}]',
|
||||
lambda db_path: f"DB Path=[{db_path}]",
|
||||
db_path=core.config.default.db_path,
|
||||
)
|
||||
|
||||
|
@ -25,7 +25,7 @@ class Repository(containers.DeclarativeContainer):
|
|||
adapter = providers.DependenciesContainer()
|
||||
storage = providers.DependenciesContainer()
|
||||
site = providers.Singleton(
|
||||
lambda adapter, queue: f'Adapter=[{adapter}], queue=[{queue}]',
|
||||
lambda adapter, queue: f"Adapter=[{adapter}], queue=[{queue}]",
|
||||
adapter=adapter.tinydb,
|
||||
queue=storage.queue,
|
||||
)
|
||||
|
|
|
@ -6,4 +6,4 @@ class ConfigService:
|
|||
self._config = config
|
||||
|
||||
def build(self):
|
||||
self._config.from_dict({'default': {'db_path': '~/test'}})
|
||||
self._config.from_dict({"default": {"db_path": "~/test"}})
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .services import UserService, AuthService, PhotoService
|
||||
from .containers import Application
|
||||
|
@ -22,10 +22,9 @@ def main(
|
|||
photo_service.upload_photo(user, photo)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
application = Application()
|
||||
application.config.from_yaml('config.yml')
|
||||
application.core.init_resources()
|
||||
application.wire(modules=[sys.modules[__name__]])
|
||||
application.wire(modules=[__name__])
|
||||
|
||||
main(*sys.argv[1:])
|
||||
|
|
|
@ -30,7 +30,7 @@ class Gateways(containers.DeclarativeContainer):
|
|||
|
||||
s3_client = providers.Singleton(
|
||||
boto3.client,
|
||||
service_name='s3',
|
||||
service_name="s3",
|
||||
aws_access_key_id=config.aws.access_key_id,
|
||||
aws_secret_access_key=config.aws.secret_access_key,
|
||||
)
|
||||
|
@ -61,7 +61,7 @@ class Services(containers.DeclarativeContainer):
|
|||
|
||||
class Application(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
core = providers.Container(
|
||||
Core,
|
||||
|
|
|
@ -11,7 +11,7 @@ class BaseService:
|
|||
|
||||
def __init__(self) -> None:
|
||||
self.logger = logging.getLogger(
|
||||
f'{__name__}.{self.__class__.__name__}',
|
||||
f"{__name__}.{self.__class__.__name__}",
|
||||
)
|
||||
|
||||
|
||||
|
@ -22,8 +22,8 @@ class UserService(BaseService):
|
|||
super().__init__()
|
||||
|
||||
def get_user(self, email: str) -> Dict[str, str]:
|
||||
self.logger.debug('User %s has been found in database', email)
|
||||
return {'email': email, 'password_hash': '...'}
|
||||
self.logger.debug("User %s has been found in database", email)
|
||||
return {"email": email, "password_hash": "..."}
|
||||
|
||||
|
||||
class AuthService(BaseService):
|
||||
|
@ -36,8 +36,8 @@ class AuthService(BaseService):
|
|||
def authenticate(self, user: Dict[str, str], password: str) -> None:
|
||||
assert password is not None
|
||||
self.logger.debug(
|
||||
'User %s has been successfully authenticated',
|
||||
user['email'],
|
||||
"User %s has been successfully authenticated",
|
||||
user["email"],
|
||||
)
|
||||
|
||||
|
||||
|
@ -50,7 +50,7 @@ class PhotoService(BaseService):
|
|||
|
||||
def upload_photo(self, user: Dict[str, str], photo_path: str) -> None:
|
||||
self.logger.debug(
|
||||
'Photo %s has been successfully uploaded by user %s',
|
||||
"Photo %s has been successfully uploaded by user %s",
|
||||
photo_path,
|
||||
user['email'],
|
||||
user["email"],
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .services import UserService, AuthService, PhotoService
|
||||
from .containers import Container
|
||||
|
@ -22,10 +22,9 @@ def main(
|
|||
photo_service.upload_photo(user, photo)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.init_resources()
|
||||
container.config.from_ini('config.ini')
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
main(*sys.argv[1:])
|
||||
|
|
|
@ -11,11 +11,11 @@ from . import services
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(ini_files=["config.ini"])
|
||||
|
||||
logging = providers.Resource(
|
||||
logging.config.fileConfig,
|
||||
fname='logging.ini',
|
||||
fname="logging.ini",
|
||||
)
|
||||
|
||||
# Gateways
|
||||
|
@ -27,7 +27,7 @@ class Container(containers.DeclarativeContainer):
|
|||
|
||||
s3_client = providers.Singleton(
|
||||
boto3.client,
|
||||
service_name='s3',
|
||||
service_name="s3",
|
||||
aws_access_key_id=config.aws.access_key_id,
|
||||
aws_secret_access_key=config.aws.secret_access_key,
|
||||
)
|
||||
|
|
|
@ -11,7 +11,7 @@ class BaseService:
|
|||
|
||||
def __init__(self) -> None:
|
||||
self.logger = logging.getLogger(
|
||||
f'{__name__}.{self.__class__.__name__}',
|
||||
f"{__name__}.{self.__class__.__name__}",
|
||||
)
|
||||
|
||||
|
||||
|
@ -22,8 +22,8 @@ class UserService(BaseService):
|
|||
super().__init__()
|
||||
|
||||
def get_user(self, email: str) -> Dict[str, str]:
|
||||
self.logger.debug('User %s has been found in database', email)
|
||||
return {'email': email, 'password_hash': '...'}
|
||||
self.logger.debug("User %s has been found in database", email)
|
||||
return {"email": email, "password_hash": "..."}
|
||||
|
||||
|
||||
class AuthService(BaseService):
|
||||
|
@ -36,8 +36,8 @@ class AuthService(BaseService):
|
|||
def authenticate(self, user: Dict[str, str], password: str) -> None:
|
||||
assert password is not None
|
||||
self.logger.debug(
|
||||
'User %s has been successfully authenticated',
|
||||
user['email'],
|
||||
"User %s has been successfully authenticated",
|
||||
user["email"],
|
||||
)
|
||||
|
||||
|
||||
|
@ -50,7 +50,7 @@ class PhotoService(BaseService):
|
|||
|
||||
def upload_photo(self, user: Dict[str, str], photo_path: str) -> None:
|
||||
self.logger.debug(
|
||||
'Photo %s has been successfully uploaded by user %s',
|
||||
"Photo %s has been successfully uploaded by user %s",
|
||||
photo_path,
|
||||
user['email'],
|
||||
user["email"],
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM python:3.8-buster
|
||||
FROM python:3.13-bookworm
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@ Build the Docker image:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose build
|
||||
docker compose build
|
||||
|
||||
Run the docker-compose environment:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose up
|
||||
docker compose up
|
||||
|
||||
The output should be something like:
|
||||
|
||||
|
@ -59,28 +59,29 @@ To run the tests do:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
|
||||
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
|
||||
|
||||
The output should be something like:
|
||||
|
||||
.. code-block::
|
||||
|
||||
platform linux -- Python 3.8.3, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
|
||||
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||
rootdir: /code
|
||||
plugins: asyncio-0.14.0, cov-2.10.0
|
||||
plugins: cov-6.0.0, asyncio-0.24.0
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
collected 2 items
|
||||
|
||||
monitoringdaemon/tests.py .. [100%]
|
||||
|
||||
----------- coverage: platform linux, python 3.8.3-final-0 -----------
|
||||
---------- coverage: platform linux, python 3.10.0-final-0 -----------
|
||||
Name Stmts Miss Cover
|
||||
----------------------------------------------------
|
||||
monitoringdaemon/__init__.py 0 0 100%
|
||||
monitoringdaemon/__main__.py 13 13 0%
|
||||
monitoringdaemon/__main__.py 11 11 0%
|
||||
monitoringdaemon/containers.py 11 0 100%
|
||||
monitoringdaemon/dispatcher.py 44 5 89%
|
||||
monitoringdaemon/dispatcher.py 45 5 89%
|
||||
monitoringdaemon/http.py 6 3 50%
|
||||
monitoringdaemon/monitors.py 23 1 96%
|
||||
monitoringdaemon/tests.py 37 0 100%
|
||||
monitoringdaemon/tests.py 35 0 100%
|
||||
----------------------------------------------------
|
||||
TOTAL 134 22 84%
|
||||
TOTAL 131 20 85%
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
"""Main module."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .dispatcher import Dispatcher
|
||||
|
@ -13,10 +11,9 @@ def main(dispatcher: Dispatcher = Provide[Container.dispatcher]) -> None:
|
|||
dispatcher.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
container.config.from_yaml('config.yml')
|
||||
container.init_resources()
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
main()
|
||||
|
|
|
@ -10,7 +10,7 @@ from . import http, monitors, dispatcher
|
|||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(yaml_files=["config.yml"])
|
||||
|
||||
logging = providers.Resource(
|
||||
logging.basicConfig,
|
||||
|
|
|
@ -21,7 +21,7 @@ class Dispatcher:
|
|||
asyncio.run(self.start())
|
||||
|
||||
async def start(self) -> None:
|
||||
self._logger.info('Starting up')
|
||||
self._logger.info("Starting up")
|
||||
|
||||
for monitor in self._monitors:
|
||||
self._monitor_tasks.append(
|
||||
|
@ -41,11 +41,11 @@ class Dispatcher:
|
|||
|
||||
self._stopping = True
|
||||
|
||||
self._logger.info('Shutting down')
|
||||
self._logger.info("Shutting down")
|
||||
for task, monitor in zip(self._monitor_tasks, self._monitors):
|
||||
task.cancel()
|
||||
self._monitor_tasks.clear()
|
||||
self._logger.info('Shutdown finished successfully')
|
||||
self._logger.info("Shutdown finished successfully")
|
||||
|
||||
@staticmethod
|
||||
async def _run_monitor(monitor: Monitor) -> None:
|
||||
|
@ -61,6 +61,6 @@ class Dispatcher:
|
|||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception:
|
||||
monitor.logger.exception('Error executing monitor check')
|
||||
monitor.logger.exception("Error executing monitor check")
|
||||
|
||||
await asyncio.sleep(_until_next(last=time_start))
|
||||
|
|
|
@ -25,10 +25,10 @@ class HttpMonitor(Monitor):
|
|||
options: Dict[str, Any],
|
||||
) -> None:
|
||||
self._client = http_client
|
||||
self._method = options.pop('method')
|
||||
self._url = options.pop('url')
|
||||
self._timeout = options.pop('timeout')
|
||||
super().__init__(check_every=options.pop('check_every'))
|
||||
self._method = options.pop("method")
|
||||
self._url = options.pop("url")
|
||||
self._timeout = options.pop("timeout")
|
||||
super().__init__(check_every=options.pop("check_every"))
|
||||
|
||||
async def check(self) -> None:
|
||||
time_start = time.time()
|
||||
|
@ -43,11 +43,11 @@ class HttpMonitor(Monitor):
|
|||
time_took = time_end - time_start
|
||||
|
||||
self.logger.info(
|
||||
'Check\n'
|
||||
' %s %s\n'
|
||||
' response code: %s\n'
|
||||
' content length: %s\n'
|
||||
' request took: %s seconds',
|
||||
"Check\n"
|
||||
" %s %s\n"
|
||||
" response code: %s\n"
|
||||
" content length: %s\n"
|
||||
" request took: %s seconds",
|
||||
self._method,
|
||||
self._url,
|
||||
response.status,
|
||||
|
|
|
@ -17,33 +17,33 @@ class RequestStub:
|
|||
|
||||
@pytest.fixture
|
||||
def container():
|
||||
container = Container()
|
||||
container.config.from_dict({
|
||||
'log': {
|
||||
'level': 'INFO',
|
||||
'formant': '[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s',
|
||||
},
|
||||
'monitors': {
|
||||
'example': {
|
||||
'method': 'GET',
|
||||
'url': 'http://fake-example.com',
|
||||
'timeout': 1,
|
||||
'check_every': 1,
|
||||
return Container(
|
||||
config={
|
||||
"log": {
|
||||
"level": "INFO",
|
||||
"formant": "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s",
|
||||
},
|
||||
'httpbin': {
|
||||
'method': 'GET',
|
||||
'url': 'https://fake-httpbin.org/get',
|
||||
'timeout': 1,
|
||||
'check_every': 1,
|
||||
"monitors": {
|
||||
"example": {
|
||||
"method": "GET",
|
||||
"url": "http://fake-example.com",
|
||||
"timeout": 1,
|
||||
"check_every": 1,
|
||||
},
|
||||
"httpbin": {
|
||||
"method": "GET",
|
||||
"url": "https://fake-httpbin.org/get",
|
||||
"timeout": 1,
|
||||
"check_every": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return container
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_example_monitor(container, caplog):
|
||||
caplog.set_level('INFO')
|
||||
caplog.set_level("INFO")
|
||||
|
||||
http_client_mock = mock.AsyncMock()
|
||||
http_client_mock.request.return_value = RequestStub(
|
||||
|
@ -55,22 +55,24 @@ async def test_example_monitor(container, caplog):
|
|||
example_monitor = container.example_monitor()
|
||||
await example_monitor.check()
|
||||
|
||||
assert 'http://fake-example.com' in caplog.text
|
||||
assert 'response code: 200' in caplog.text
|
||||
assert 'content length: 635' in caplog.text
|
||||
assert "http://fake-example.com" in caplog.text
|
||||
assert "response code: 200" in caplog.text
|
||||
assert "content length: 635" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatcher(container, caplog, event_loop):
|
||||
caplog.set_level('INFO')
|
||||
async def test_dispatcher(container, caplog):
|
||||
caplog.set_level("INFO")
|
||||
|
||||
example_monitor_mock = mock.AsyncMock()
|
||||
httpbin_monitor_mock = mock.AsyncMock()
|
||||
|
||||
with container.example_monitor.override(example_monitor_mock), \
|
||||
container.httpbin_monitor.override(httpbin_monitor_mock):
|
||||
|
||||
with container.override_providers(
|
||||
example_monitor=example_monitor_mock,
|
||||
httpbin_monitor=httpbin_monitor_mock,
|
||||
):
|
||||
dispatcher = container.dispatcher()
|
||||
event_loop = asyncio.get_running_loop()
|
||||
event_loop.create_task(dispatcher.start())
|
||||
await asyncio.sleep(0.1)
|
||||
dispatcher.stop()
|
||||
|
|
|
@ -23,12 +23,12 @@ class Container(containers.DeclarativeContainer):
|
|||
|
||||
s3_client = providers.Resource(
|
||||
session.provided.client.call(),
|
||||
service_name='s3',
|
||||
service_name="s3",
|
||||
)
|
||||
|
||||
sqs_client = providers.Resource(
|
||||
providers.MethodCaller(session.provided.client), # Alternative syntax
|
||||
service_name='sqs',
|
||||
service_name="sqs",
|
||||
)
|
||||
|
||||
service1 = providers.Factory(
|
||||
|
@ -39,16 +39,16 @@ class Container(containers.DeclarativeContainer):
|
|||
|
||||
service2 = providers.Factory(
|
||||
Service,
|
||||
s3_client=session.provided.client.call(service_name='s3'), # Alternative inline syntax
|
||||
sqs_client=session.provided.client.call(service_name='sqs'), # Alternative inline syntax
|
||||
s3_client=session.provided.client.call(service_name="s3"), # Alternative inline syntax
|
||||
sqs_client=session.provided.client.call(service_name="sqs"), # Alternative inline syntax
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
container = Container()
|
||||
container.config.aws_access_key_id.from_env('AWS_ACCESS_KEY_ID')
|
||||
container.config.aws_secret_access_key.from_env('AWS_SECRET_ACCESS_KEY')
|
||||
container.config.aws_session_token.from_env('AWS_SESSION_TOKEN')
|
||||
container.config.aws_access_key_id.from_env("AWS_ACCESS_KEY_ID")
|
||||
container.config.aws_secret_access_key.from_env("AWS_SECRET_ACCESS_KEY")
|
||||
container.config.aws_session_token.from_env("AWS_SESSION_TOKEN")
|
||||
container.init_resources()
|
||||
|
||||
s3_client = container.s3_client()
|
||||
|
@ -62,11 +62,11 @@ def main():
|
|||
assert service1.s3_client is s3_client
|
||||
assert service1.sqs_client is sqs_client
|
||||
|
||||
service2 = container.service1()
|
||||
service2 = container.service2()
|
||||
print(service2, service2.s3_client, service2.sqs_client)
|
||||
assert service2.s3_client is s3_client
|
||||
assert service2.sqs_client is sqs_client
|
||||
assert service2.s3_client.__class__.__name__ == "S3"
|
||||
assert service2.sqs_client.__class__.__name__ == "SQS"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -4,7 +4,7 @@ from .containers import Container
|
|||
from .commands import SaveRating, DoSomethingElse
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
container = Container()
|
||||
message_bus = container.message_bus()
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ class CommandHandler:
|
|||
self.rating_repo = rating_repo
|
||||
|
||||
def save_rating(self):
|
||||
print('Saving rating')
|
||||
print("Saving rating")
|
||||
|
||||
def something_else(self):
|
||||
print('Doing something else')
|
||||
print("Doing something else")
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
"""Main module."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .user.repositories import UserRepository
|
||||
from .photo.repositories import PhotoRepository
|
||||
|
@ -24,20 +22,19 @@ def main(
|
|||
) -> None:
|
||||
user1 = user_repository.get(id=1)
|
||||
user1_photos = photo_repository.get_photos(user1.id)
|
||||
print(f'Retrieve user id={user1.id}, photos count={len(user1_photos)}')
|
||||
print(f"Retrieve user id={user1.id}, photos count={len(user1_photos)}")
|
||||
|
||||
user2 = user_repository.get(id=2)
|
||||
user2_photos = photo_repository.get_photos(user2.id)
|
||||
print(f'Retrieve user id={user2.id}, photos count={len(user2_photos)}')
|
||||
print(f"Retrieve user id={user2.id}, photos count={len(user2_photos)}")
|
||||
|
||||
assert aggregation_service.user_repository is user_repository
|
||||
assert aggregation_service.photo_repository is photo_repository
|
||||
print('Aggregate analytics from user and photo packages')
|
||||
print("Aggregate analytics from user and photo packages")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
application = ApplicationContainer()
|
||||
application.config.from_ini('config.ini')
|
||||
application.wire(modules=[sys.modules[__name__]])
|
||||
application.wire(modules=[__name__])
|
||||
|
||||
main()
|
||||
|
|
|
@ -12,13 +12,13 @@ from .analytics.containers import AnalyticsContainer
|
|||
|
||||
class ApplicationContainer(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
config = providers.Configuration(ini_files=["config.ini"])
|
||||
|
||||
sqlite = providers.Singleton(sqlite3.connect, config.database.dsn)
|
||||
|
||||
s3 = providers.Singleton(
|
||||
boto3.client,
|
||||
service_name='s3',
|
||||
service_name="s3",
|
||||
aws_access_key_id=config.aws.access_key_id,
|
||||
aws_secret_access_key=config.aws.secret_access_key,
|
||||
)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user