mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-07-18 12:02:23 +03:00
Compare commits
155 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2c0aede4aa | ||
|
84a14f2ca7 | ||
|
0c58064a36 | ||
|
eb74b1e9d0 | ||
|
be7d25518d | ||
|
04b5907f21 | ||
|
e6cc12762f | ||
|
bf2ddbce32 | ||
|
9d6994391f | ||
|
dd84a1b5d6 | ||
|
31a98f7731 | ||
|
b261251b34 | ||
|
4bfe64563e | ||
|
b411807572 | ||
|
f2da51e0d4 | ||
|
2293251986 | ||
|
1b4b3d349f | ||
|
d8e49f7dd5 | ||
|
c1f14a876a | ||
|
c97a0cc515 | ||
|
0ada62acbf | ||
|
67827a36d1 | ||
|
ceed6a8222 | ||
|
6766ef3eba | ||
|
a8914e54e0 | ||
|
cdd9ce5048 | ||
|
51c7db771d | ||
|
a322584308 | ||
|
4b3476cb48 | ||
|
8460465b5e | ||
|
16f444b230 | ||
|
1ae96e3eeb | ||
|
7fcf1ac7ad | ||
|
1271d0fa79 | ||
|
b9df88eea7 | ||
|
99489afa3f | ||
|
193249f7ec | ||
|
01349c43e1 | ||
|
41ed07a210 | ||
|
8be79126ad | ||
|
dfee54932b | ||
|
a61749c68d | ||
|
14be69371b | ||
|
cfeb018ca7 | ||
|
183b2ae7ff | ||
|
561ff46658 | ||
|
49cc8ed827 | ||
|
383e95faed | ||
|
e7e64f6ae0 | ||
|
f50cc95405 | ||
|
8814db3fb3 | ||
|
8bf9ed04c8 | ||
|
dbf86e4eb4 | ||
|
9a08bfcede | ||
|
4ae79ac21f | ||
|
35bfafdfe2 | ||
|
6685c5a141 | ||
|
57123cebaa | ||
|
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 |
10
.coveragerc
10
.coveragerc
|
@ -1,10 +0,0 @@
|
||||||
[run]
|
|
||||||
source = src/dependency_injector
|
|
||||||
omit = tests/unit
|
|
||||||
plugins = Cython.Coverage
|
|
||||||
|
|
||||||
[report]
|
|
||||||
show_missing = true
|
|
||||||
|
|
||||||
[html]
|
|
||||||
directory=reports/unittests/
|
|
29
.cursor/rules/coding-guide.mdc
Normal file
29
.cursor/rules/coding-guide.mdc
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
description: Code in Python and Cython
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
- Follow PEP 8 rules
|
||||||
|
- When you write imports, split system, 3rd-party, and local imports with a new line
|
||||||
|
- Have two empty lines between the import block and the rest of the code
|
||||||
|
- Have an empty line (\n) at the end of every file
|
||||||
|
- If a file is supposed to be run, always add ``if __name__ == 'main'``
|
||||||
|
- Always follow a consistent pattern of using double or single quotes
|
||||||
|
- When there is a class without a docblock, leave one blank line before its members, e.g.:
|
||||||
|
```python
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
service = providers.Factory(Service)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Avoid shortcuts in names unless absolutely necessary, exceptions:
|
||||||
|
```
|
||||||
|
arg
|
||||||
|
args
|
||||||
|
kwarg
|
||||||
|
kwargs
|
||||||
|
obj
|
||||||
|
cls
|
||||||
|
```
|
||||||
|
|
||||||
|
- Avoid inline comments unless absolutely necessary
|
7
.cursor/rules/makefile-commands.mdc
Normal file
7
.cursor/rules/makefile-commands.mdc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
description: Build and run tests
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
- Use Makefile commands to build, test, lint and other similar operations when they are available.
|
||||||
|
- Activate virtualenv before running any commands by ``. venv/bin/actvate``
|
8
.cursor/rules/run-examples.mdc
Normal file
8
.cursor/rules/run-examples.mdc
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
description: Run examples
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
- When you run an example from the ``examples/`` folder, switch to the example folder and run it from there.
|
||||||
|
- If there are instructions on running the examples or its tests in readme, follow them
|
||||||
|
- Activate virtualenv before running any commands by ``. venv/bin/actvate``
|
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.{py,pyi,pxd,pyx}]
|
||||||
|
ij_visual_guides = 80,88
|
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
github: rmk135
|
112
.github/workflows/publishing.yml
vendored
112
.github/workflows/publishing.yml
vendored
|
@ -1,6 +1,7 @@
|
||||||
name: Publishing
|
name: Publishing
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
|
@ -9,28 +10,28 @@ jobs:
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: 3.13
|
||||||
- run: pip install tox
|
- run: pip install tox
|
||||||
- run: tox
|
- run: tox
|
||||||
env:
|
env:
|
||||||
TOXENV: "3.10"
|
TOXENV: 3.13
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
name: Run linters
|
name: Run linters
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
toxenv: [flake8, pydocstyle, mypy, pylint]
|
toxenv: [flake8, pydocstyle, mypy, pylint]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: 3.13
|
||||||
- run: pip install tox
|
- run: pip install tox
|
||||||
- run: tox
|
- run: tox
|
||||||
env:
|
env:
|
||||||
|
@ -39,15 +40,18 @@ jobs:
|
||||||
build-sdist:
|
build-sdist:
|
||||||
name: Build source tarball
|
name: Build source tarball
|
||||||
needs: [tests, linters]
|
needs: [tests, linters]
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: 3.13
|
||||||
- run: python setup.py sdist
|
- run: |
|
||||||
- uses: actions/upload-artifact@v2
|
python -m pip install --upgrade build
|
||||||
|
python -m build --sdist
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
name: cibw-sdist
|
||||||
path: ./dist/*
|
path: ./dist/*
|
||||||
|
|
||||||
build-wheels:
|
build-wheels:
|
||||||
|
@ -56,65 +60,65 @@ jobs:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04, windows-2019, macOS-10.15]
|
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2022, macos-14]
|
||||||
env:
|
env:
|
||||||
CIBW_SKIP: cp27-win*
|
CIBW_ENABLE: pypy
|
||||||
|
CIBW_ENVIRONMENT: >-
|
||||||
|
PIP_CONFIG_SETTINGS="build_ext=-j4"
|
||||||
|
DEPENDENCY_INJECTOR_LIMITED_API="1"
|
||||||
|
CFLAGS="-g0"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- name: Build wheels
|
||||||
with:
|
uses: pypa/cibuildwheel@v3.0.0
|
||||||
python-version: "3.10"
|
- uses: actions/upload-artifact@v4
|
||||||
- run: pip install cibuildwheel==2.1.3
|
|
||||||
- run: cibuildwheel --output-dir wheelhouse
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
with:
|
with:
|
||||||
|
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
|
||||||
path: ./wheelhouse/*.whl
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
build-wheels-linux-aarch64:
|
test-publish:
|
||||||
name: Build wheels (ubuntu-latest-aarch64)
|
name: Upload release to TestPyPI
|
||||||
needs: [tests, linters]
|
needs: [build-sdist, build-wheels]
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
|
environment: test-pypi
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/download-artifact@v4
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
pattern: cibw-*
|
||||||
- run: pip install cibuildwheel==2.1.3
|
path: dist
|
||||||
- run: cibuildwheel --archs aarch64 --output-dir wheelhouse
|
merge-multiple: true
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
with:
|
with:
|
||||||
path: ./wheelhouse/*.whl
|
repository-url: https://test.pypi.org/legacy/
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
name: Publish on PyPI
|
name: Upload release to PyPI
|
||||||
needs: [build-sdist, build-wheels, build-wheels-linux-aarch64]
|
needs: [build-sdist, build-wheels, test-publish]
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
|
environment: pypi
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifact
|
pattern: cibw-*
|
||||||
path: dist
|
path: dist
|
||||||
- uses: pypa/gh-action-pypi-publish@master
|
merge-multiple: true
|
||||||
with:
|
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
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:
|
publish-docs:
|
||||||
name: Publish docs
|
name: Publish docs
|
||||||
needs: [publish]
|
needs: [publish]
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: 3.13
|
||||||
- run: pip install -r requirements-doc.txt
|
|
||||||
- run: pip install awscli
|
- run: pip install awscli
|
||||||
|
- run: pip install -r requirements-doc.txt
|
||||||
- run: pip install -e .
|
- run: pip install -e .
|
||||||
- run: (cd docs && make clean html)
|
- run: (cd docs && make clean html)
|
||||||
- run: |
|
- run: |
|
||||||
|
|
36
.github/workflows/tests-and-linters.yml
vendored
36
.github/workflows/tests-and-linters.yml
vendored
|
@ -9,32 +9,44 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10", pypy2, pypy3]
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- run: pip install tox
|
- run: pip install tox
|
||||||
- run: tox
|
- run: tox
|
||||||
env:
|
env:
|
||||||
|
DEPENDENCY_INJECTOR_LIMITED_API: 1
|
||||||
TOXENV: ${{ matrix.python-version }}
|
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:
|
test-coverage:
|
||||||
name: Run tests with coverage
|
name: Run tests with coverage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
DEPENDENCY_INJECTOR_DEBUG_MODE: 1
|
DEPENDENCY_INJECTOR_DEBUG_MODE: 1
|
||||||
|
PIP_VERBOSE: 1
|
||||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: 3.12
|
||||||
- run: pip install tox cython
|
- run: pip install tox
|
||||||
- run: make cythonize
|
- run: tox -vv
|
||||||
- run: tox
|
|
||||||
env:
|
env:
|
||||||
TOXENV: coveralls
|
TOXENV: coveralls
|
||||||
|
|
||||||
|
@ -45,10 +57,10 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
toxenv: [flake8, pydocstyle, mypy, pylint]
|
toxenv: [flake8, pydocstyle, mypy, pylint]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: 3.13
|
||||||
- run: pip install tox
|
- run: pip install tox
|
||||||
- run: tox
|
- run: tox
|
||||||
env:
|
env:
|
||||||
|
|
15
.gitignore
vendored
15
.gitignore
vendored
|
@ -15,6 +15,7 @@ lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
wheelhouse/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
@ -63,13 +64,13 @@ venv*/
|
||||||
# Vim Rope
|
# Vim Rope
|
||||||
.ropeproject/
|
.ropeproject/
|
||||||
|
|
||||||
# C extensions
|
# Cython artifacts
|
||||||
src/dependency_injector/*.h
|
src/**/*.c
|
||||||
src/dependency_injector/*.so
|
src/**/*.h
|
||||||
src/dependency_injector/containers/*.h
|
src/**/*.so
|
||||||
src/dependency_injector/containers/*.so
|
src/**/*.html
|
||||||
src/dependency_injector/providers/*.h
|
|
||||||
src/dependency_injector/providers/*.so
|
|
||||||
|
|
||||||
# Workspace for samples
|
# Workspace for samples
|
||||||
.workspace/
|
.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
|
|
|
@ -20,3 +20,5 @@ Dependency Injector Contributors
|
||||||
+ Ngo Thanh Loi (Leonn) (loingo95)
|
+ Ngo Thanh Loi (Leonn) (loingo95)
|
||||||
+ Thiago Hiromi (thiromi)
|
+ Thiago Hiromi (thiromi)
|
||||||
+ Felipe Rubio (krouw)
|
+ Felipe Rubio (krouw)
|
||||||
|
+ Anton Petrov (anton-petrov)
|
||||||
|
+ ZipFile (ZipFile)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2021, Roman Mogylatov
|
Copyright (c) 2024, Roman Mogylatov
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
recursive-include src/dependency_injector *.py* *.c
|
recursive-include src/dependency_injector *.py* *.c py.typed
|
||||||
recursive-include tests *.py
|
recursive-include tests *.py
|
||||||
include README.rst
|
include README.rst
|
||||||
include CONTRIBUTORS.rst
|
include CONTRIBUTORS.rst
|
||||||
include LICENSE.rst
|
include LICENSE.rst
|
||||||
include requirements.txt
|
|
||||||
include setup.py
|
include setup.py
|
||||||
include tox.ini
|
include tox.ini
|
||||||
include py.typed
|
|
||||||
|
|
32
Makefile
32
Makefile
|
@ -1,14 +1,6 @@
|
||||||
VERSION := $(shell python setup.py --version)
|
VERSION := $(shell python setup.py --version)
|
||||||
|
|
||||||
CYTHON_SRC := $(shell find src/dependency_injector -name '*.pyx')
|
export COVERAGE_RCFILE := pyproject.toml
|
||||||
|
|
||||||
CYTHON_DIRECTIVES = -Xlanguage_level=2
|
|
||||||
|
|
||||||
ifdef DEPENDENCY_INJECTOR_DEBUG_MODE
|
|
||||||
CYTHON_DIRECTIVES += -Xprofile=True
|
|
||||||
CYTHON_DIRECTIVES += -Xlinetrace=True
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
# Clean sources
|
# Clean sources
|
||||||
|
@ -25,21 +17,17 @@ clean:
|
||||||
find examples -name '*.py[co]' -delete
|
find examples -name '*.py[co]' -delete
|
||||||
find examples -name '__pycache__' -delete
|
find examples -name '__pycache__' -delete
|
||||||
|
|
||||||
cythonize:
|
build: clean
|
||||||
# Compile Cython to C
|
# Compile C extensions
|
||||||
cython -a $(CYTHON_DIRECTIVES) $(CYTHON_SRC)
|
python setup.py build_ext --inplace
|
||||||
# Move all Cython html reports
|
# Move all Cython html reports
|
||||||
mkdir -p reports/cython/
|
mkdir -p reports/cython/
|
||||||
find src -name '*.html' -exec mv {} reports/cython/ \;
|
find src -name '*.html' -exec mv {} reports/cython/ \;
|
||||||
|
|
||||||
build: clean cythonize
|
|
||||||
# Compile C extensions
|
|
||||||
python setup.py build_ext --inplace
|
|
||||||
|
|
||||||
docs-live:
|
docs-live:
|
||||||
sphinx-autobuild docs docs/_build/html
|
sphinx-autobuild docs docs/_build/html
|
||||||
|
|
||||||
install: uninstall clean cythonize
|
install: uninstall clean build
|
||||||
pip install -ve .
|
pip install -ve .
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
|
@ -48,9 +36,9 @@ uninstall:
|
||||||
test:
|
test:
|
||||||
# Unit tests with coverage report
|
# Unit tests with coverage report
|
||||||
coverage erase
|
coverage erase
|
||||||
coverage run --rcfile=./.coveragerc -m pytest -c tests/.configs/pytest.ini
|
coverage run -m pytest
|
||||||
coverage report --rcfile=./.coveragerc
|
coverage report
|
||||||
coverage html --rcfile=./.coveragerc
|
coverage html
|
||||||
|
|
||||||
check:
|
check:
|
||||||
flake8 src/dependency_injector/
|
flake8 src/dependency_injector/
|
||||||
|
@ -61,9 +49,9 @@ check:
|
||||||
|
|
||||||
mypy tests/typing
|
mypy tests/typing
|
||||||
|
|
||||||
test-publish: cythonize
|
test-publish: build
|
||||||
# Create distributions
|
# Create distributions
|
||||||
python setup.py sdist
|
python -m build --sdist
|
||||||
# Upload distributions to PyPI
|
# Upload distributions to PyPI
|
||||||
twine upload --repository testpypi dist/dependency-injector-$(VERSION)*
|
twine upload --repository testpypi dist/dependency-injector-$(VERSION)*
|
||||||
|
|
||||||
|
|
45
README.rst
45
README.rst
|
@ -35,7 +35,7 @@
|
||||||
:target: https://pypi.org/project/dependency-injector/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: Wheel
|
: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
|
:target: https://github.com/ets-labs/python-dependency-injector/actions
|
||||||
:alt: Build Status
|
:alt: Build Status
|
||||||
|
|
||||||
|
@ -48,26 +48,26 @@ What is ``Dependency Injector``?
|
||||||
|
|
||||||
``Dependency Injector`` is a dependency injection framework for Python.
|
``Dependency Injector`` is a dependency injection framework for Python.
|
||||||
|
|
||||||
It helps implementing the dependency injection principle.
|
It helps implement the dependency injection principle.
|
||||||
|
|
||||||
Key features of the ``Dependency Injector``:
|
Key features of the ``Dependency Injector``:
|
||||||
|
|
||||||
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||||
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
|
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
|
||||||
that help assembling your objects.
|
that help assemble your objects.
|
||||||
See `Providers <https://python-dependency-injector.ets-labs.org/providers/index.html>`_.
|
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
|
- **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>`_.
|
`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.
|
environment variables, and dictionaries.
|
||||||
See `Configuration provider <https://python-dependency-injector.ets-labs.org/providers/configuration.html>`_.
|
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
|
- **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.
|
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>`_.
|
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.
|
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc.
|
||||||
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
|
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
|
||||||
- **Asynchronous**. Supports asynchronous injections.
|
- **Asynchronous**. Supports asynchronous injections.
|
||||||
|
@ -75,7 +75,7 @@ Key features of the ``Dependency Injector``:
|
||||||
- **Typing**. Provides typing stubs, ``mypy``-friendly.
|
- **Typing**. Provides typing stubs, ``mypy``-friendly.
|
||||||
See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
|
See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
|
||||||
- **Performance**. Fast. Written in ``Cython``.
|
- **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
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ Key features of the ``Dependency Injector``:
|
||||||
|
|
||||||
|
|
||||||
@inject
|
@inject
|
||||||
def main(service: Service = Provide[Container.service]):
|
def main(service: Service = Provide[Container.service]) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,19 +115,18 @@ Key features of the ``Dependency Injector``:
|
||||||
with container.api_client.override(mock.Mock()):
|
with container.api_client.override(mock.Mock()):
|
||||||
main() # <-- overridden dependency is injected automatically
|
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
|
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.
|
client with a mock. When you call ``main()``, the mock is injected.
|
||||||
|
|
||||||
You can override any provider with another provider.
|
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 a stub on the dev or stage.
|
||||||
|
|
||||||
With the ``Dependency Injector`` objects assembling is consolidated in the container.
|
With the ``Dependency Injector``, object assembling is consolidated in a container. Dependency injections are defined explicitly.
|
||||||
Dependency injections are defined explicitly.
|
This makes it easier to understand and change how an application works.
|
||||||
This makes easier to understand and change how application works.
|
|
||||||
|
|
||||||
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
|
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
|
||||||
:target: https://github.com/ets-labs/python-dependency-injector
|
:target: https://github.com/ets-labs/python-dependency-injector
|
||||||
|
@ -185,27 +184,27 @@ 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.
|
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.
|
``Dependency Injector`` is a simple tool for the powerful concept.
|
||||||
|
|
||||||
Frequently asked questions
|
Frequently asked questions
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
What is the dependency injection?
|
What is dependency injection?
|
||||||
- dependency injection is a principle that decreases coupling and increases cohesion
|
- dependency injection is a principle that decreases coupling and increases cohesion
|
||||||
|
|
||||||
Why should I do the dependency injection?
|
Why should I do the dependency injection?
|
||||||
- your code becomes more flexible, testable, and clear 😎
|
- your code becomes more flexible, testable, and clear 😎
|
||||||
|
|
||||||
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 start writing the code following the dependency injection principle
|
||||||
- you register all of your application components and their dependencies in the container
|
- you register all of your application components and their dependencies in the container
|
||||||
- when you need a component, you specify where to inject it or get it from the container
|
- when you need a component, you specify where to inject it or get it from the container
|
||||||
|
|
||||||
What price do I pay and what do I get?
|
What price do I pay and what do I get?
|
||||||
- you need to explicitly specify the dependencies
|
- you need to explicitly specify the dependencies
|
||||||
- it will be an extra work in the beginning
|
- it will be extra work in the beginning
|
||||||
- it will payoff as the project grows
|
- it will payoff as project grows
|
||||||
|
|
||||||
Have a question?
|
Have a question?
|
||||||
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_
|
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_
|
||||||
|
|
9
docs/_static/custom.css
vendored
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);
|
|
||||||
});
|
|
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>
|
9
docs/api/asgi-lifespan.rst
Normal file
9
docs/api/asgi-lifespan.rst
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
dependency_injector.ext.starlette
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: dependency_injector.ext.starlette
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. disqus::
|
|
@ -2,10 +2,11 @@ API Documentation
|
||||||
=================
|
=================
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
top-level
|
top-level
|
||||||
providers
|
providers
|
||||||
containers
|
containers
|
||||||
wiring
|
wiring
|
||||||
errors
|
errors
|
||||||
|
asgi-lifespan
|
||||||
|
|
10
docs/conf.py
10
docs/conf.py
|
@ -33,7 +33,7 @@ sys.path.insert(0, os.path.abspath(".."))
|
||||||
extensions = [
|
extensions = [
|
||||||
"alabaster",
|
"alabaster",
|
||||||
"sphinx.ext.autodoc",
|
"sphinx.ext.autodoc",
|
||||||
"sphinxcontrib.disqus",
|
"sphinx_disqus.disqus",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
@ -52,7 +52,7 @@ master_doc = "index"
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = "Dependency Injector"
|
project = "Dependency Injector"
|
||||||
copyright = "2021, Roman Mogylatov"
|
copyright = "2024, Roman Mogylatov"
|
||||||
author = "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
|
||||||
|
@ -72,7 +72,7 @@ release = version
|
||||||
#
|
#
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
# Usually you set "language" from the command line for these cases.
|
# Usually you set "language" from the command line for these cases.
|
||||||
language = None
|
language = "en"
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
# non-false value, then it is used:
|
# non-false value, then it is used:
|
||||||
|
@ -147,6 +147,9 @@ html_favicon = "favicon.ico"
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ["_static"]
|
html_static_path = ["_static"]
|
||||||
|
html_css_files = [
|
||||||
|
"custom.css",
|
||||||
|
]
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
# Add any extra paths that contain custom files (such as robots.txt or
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
# .htaccess) here, relative to this directory. These files are copied
|
||||||
|
@ -306,4 +309,5 @@ html_theme_options = {
|
||||||
"description": "Dependency injection framework for Python by Roman Mogylatov",
|
"description": "Dependency injection framework for Python by Roman Mogylatov",
|
||||||
"code_font_size": "10pt",
|
"code_font_size": "10pt",
|
||||||
"analytics_id": "UA-67012059-1",
|
"analytics_id": "UA-67012059-1",
|
||||||
|
"donate_url": "https://github.com/sponsors/rmk135",
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,4 +78,6 @@ Sources
|
||||||
|
|
||||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
|
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. 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>`_.
|
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::
|
.. 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>`_.
|
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::
|
.. disqus::
|
||||||
|
|
|
@ -17,4 +17,6 @@ Listing of ``boto3_session_example.py``:
|
||||||
.. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py
|
.. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py
|
||||||
:language: python
|
:language: python
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. 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>`_.
|
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::
|
.. disqus::
|
||||||
|
|
|
@ -78,7 +78,7 @@ Container is wired to the ``views`` module in the app config ``web/apps.py``:
|
||||||
|
|
||||||
.. literalinclude:: ../../examples/miniapps/django/web/apps.py
|
.. literalinclude:: ../../examples/miniapps/django/web/apps.py
|
||||||
:language: python
|
:language: python
|
||||||
:emphasize-lines: 13
|
:emphasize-lines: 12
|
||||||
|
|
||||||
Tests
|
Tests
|
||||||
-----
|
-----
|
||||||
|
@ -94,4 +94,6 @@ Sources
|
||||||
|
|
||||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
|
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -95,4 +95,6 @@ See also:
|
||||||
- Resource provider :ref:`resource-async-initializers`
|
- Resource provider :ref:`resource-async-initializers`
|
||||||
- Wiring :ref:`async-injections-wiring`
|
- Wiring :ref:`async-injections-wiring`
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. 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>`_.
|
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::
|
.. 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>`_.
|
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
48
docs/examples/fastdepends.rst
Normal file
48
docs/examples/fastdepends.rst
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
.. _fastdepends-example:
|
||||||
|
|
||||||
|
FastDepends example
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,Dependency Injection,FastDepends,Example
|
||||||
|
:description: This example demonstrates a usage of the FastDepends and Dependency Injector.
|
||||||
|
|
||||||
|
|
||||||
|
This example demonstrates how to use ``Dependency Injector`` with `FastDepends <https://github.com/Lancetnik/FastDepends>`_, a lightweight dependency injection framework inspired by FastAPI's dependency system, but without the web framework components.
|
||||||
|
|
||||||
|
Basic Usage
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The integration between FastDepends and Dependency Injector is straightforward. Simply use Dependency Injector's ``Provide`` marker within FastDepends' ``Depends`` function:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from dependency_injector.wiring import inject, Provide
|
||||||
|
from fast_depends import Depends
|
||||||
|
|
||||||
|
|
||||||
|
class CoefficientService:
|
||||||
|
@staticmethod
|
||||||
|
def get_coefficient() -> float:
|
||||||
|
return 1.2
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
service = providers.Factory(CoefficientService)
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def apply_coefficient(
|
||||||
|
a: int,
|
||||||
|
coefficient_provider: CoefficientService = Depends(Provide[Container.service]),
|
||||||
|
) -> float:
|
||||||
|
return a * coefficient_provider.get_coefficient()
|
||||||
|
|
||||||
|
|
||||||
|
container = Container()
|
||||||
|
container.wire(modules=[sys.modules[__name__]])
|
||||||
|
|
||||||
|
apply_coefficient(100) == 120.0
|
|
@ -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>`_.
|
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. 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>`_.
|
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -22,5 +22,6 @@ Explore the examples to see the ``Dependency Injector`` in action.
|
||||||
fastapi
|
fastapi
|
||||||
fastapi-redis
|
fastapi-redis
|
||||||
fastapi-sqlalchemy
|
fastapi-sqlalchemy
|
||||||
|
fastdepends
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -77,4 +77,6 @@ Sources
|
||||||
|
|
||||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
|
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -9,11 +9,11 @@ Dependency Injector --- Dependency injection framework for Python
|
||||||
:description: Dependency Injector is a dependency injection framework
|
:description: Dependency Injector is a dependency injection framework
|
||||||
for Python. It helps to maintain you application structure.
|
for Python. It helps to maintain you application structure.
|
||||||
It was designed to be unified, developer-friendly
|
It was designed to be unified, developer-friendly
|
||||||
tool that helps to implement dependency injection design
|
tool that helps to implement dependency injection design
|
||||||
pattern in formal, pretty, Pythonic way. Dependency Injector
|
pattern in formal, pretty, Pythonic way. Dependency Injector
|
||||||
provides implementations of such popular design patterns
|
provides implementations of such popular design patterns
|
||||||
like IoC container, Factory and Singleton. Dependency
|
like IoC container, Factory and Singleton. Dependency
|
||||||
Injector providers are implemented as C extension types
|
Injector providers are implemented as C extension types
|
||||||
using Cython.
|
using Cython.
|
||||||
|
|
||||||
.. _index:
|
.. _index:
|
||||||
|
@ -50,7 +50,7 @@ Dependency Injector --- Dependency injection framework for Python
|
||||||
:target: https://pypi.org/project/dependency-injector/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: Wheel
|
:alt: Wheel
|
||||||
|
|
||||||
.. image:: https://img.shields.io/github/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
|
:target: https://github.com/ets-labs/python-dependency-injector/actions
|
||||||
:alt: Build Status
|
:alt: Build Status
|
||||||
|
|
||||||
|
@ -65,23 +65,23 @@ It helps implementing the dependency injection principle.
|
||||||
Key features of the ``Dependency Injector``:
|
Key features of the ``Dependency Injector``:
|
||||||
|
|
||||||
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||||
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
|
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
|
||||||
that help assembling your objects. See :ref:`providers`.
|
that help assemble your objects. See :ref:`providers`.
|
||||||
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
|
- **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`.
|
: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`.
|
environment variables, and dictionaries. See :ref:`configuration-provider`.
|
||||||
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
|
- **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.
|
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
|
||||||
See :ref:`resource-provider`.
|
See :ref:`resource-provider`.
|
||||||
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
|
- **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`.
|
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
|
||||||
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
|
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
|
||||||
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
|
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
|
||||||
- **Performance**. Fast. Written in ``Cython``.
|
- **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
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ Key features of the ``Dependency Injector``:
|
||||||
|
|
||||||
|
|
||||||
@inject
|
@inject
|
||||||
def main(service: Service = Provide[Container.service]):
|
def main(service: Service = Provide[Container.service]) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,9 +121,9 @@ Key features of the ``Dependency Injector``:
|
||||||
with container.api_client.override(mock.Mock()):
|
with container.api_client.override(mock.Mock()):
|
||||||
main() # <-- overridden dependency is injected automatically
|
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.
|
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
|
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-readme.svg
|
||||||
:target: https://github.com/ets-labs/python-dependency-injector
|
:target: https://github.com/ets-labs/python-dependency-injector
|
||||||
|
|
|
@ -11,24 +11,24 @@ Dependency injection and inversion of control in Python
|
||||||
feature for testing or configuring project in different environments and explains
|
feature for testing or configuring project in different environments and explains
|
||||||
why it's better than monkey-patching.
|
why it's better than monkey-patching.
|
||||||
|
|
||||||
Originally dependency injection pattern got popular in the languages with a static typing,
|
Originally dependency injection pattern got popular in languages with static typing like Java.
|
||||||
like Java. Dependency injection is a principle that helps to achieve an inversion of control.
|
Dependency injection is a principle that helps to achieve an inversion of control. A
|
||||||
Dependency injection framework can significantly improve a flexibility of the language
|
dependency injection framework can significantly improve the flexibility of a language
|
||||||
with a static typing. Implementation of a dependency injection framework for a language
|
with 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
|
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.
|
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
|
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
|
Python developer rarely needs. Python developers say that dependency injection can be implemented
|
||||||
easily using language fundamentals.
|
easily using language fundamentals.
|
||||||
|
|
||||||
This page describes the advantages of the dependency injection usage in Python. It
|
This page describes the advantages of applying dependency injection in Python. It
|
||||||
contains Python examples that show how to implement dependency injection. It demonstrates a usage
|
contains Python examples that show how to implement dependency injection. It demonstrates the usage
|
||||||
of the dependency injection framework ``Dependency Injector``, its container, ``Factory``,
|
of the ``Dependency Injector`` framework, its container, ``Factory``, ``Singleton``,
|
||||||
``Singleton`` and ``Configuration`` providers. The example shows how to use ``Dependency Injector``
|
and ``Configuration`` providers. The example shows how to use providers' overriding feature
|
||||||
providers overriding feature for testing or configuring project in different environments and
|
of ``Dependency Injector`` for testing or re-configuring a project in different environments and
|
||||||
explains why it's better than monkey-patching.
|
explains why it's better than monkey-patching.
|
||||||
|
|
||||||
What is dependency injection?
|
What is dependency injection?
|
||||||
|
@ -44,14 +44,14 @@ What is coupling and cohesion?
|
||||||
|
|
||||||
Coupling and cohesion are about how tough the components are tied.
|
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.
|
to disassemble.
|
||||||
- **High cohesion**. High cohesion is like using the screws. Very easy to disassemble and
|
- **High cohesion**. High cohesion is like using screws. Quite easy to disassemble and
|
||||||
assemble back or assemble a different way. It is an opposite to high coupling.
|
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?
|
How to implement the dependency injection?
|
||||||
|
|
||||||
|
@ -66,14 +66,14 @@ Before:
|
||||||
|
|
||||||
class ApiClient:
|
class ApiClient:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.api_key = os.getenv("API_KEY") # <-- dependency
|
self.api_key = os.getenv("API_KEY") # <-- dependency
|
||||||
self.timeout = os.getenv("TIMEOUT") # <-- dependency
|
self.timeout = int(os.getenv("TIMEOUT")) # <-- dependency
|
||||||
|
|
||||||
|
|
||||||
class Service:
|
class Service:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.api_client = ApiClient() # <-- dependency
|
self.api_client = ApiClient() # <-- dependency
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,18 +94,18 @@ After:
|
||||||
|
|
||||||
class ApiClient:
|
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.api_key = api_key # <-- dependency is injected
|
||||||
self.timeout = timeout # <-- dependency is injected
|
self.timeout = timeout # <-- dependency is injected
|
||||||
|
|
||||||
|
|
||||||
class Service:
|
class Service:
|
||||||
|
|
||||||
def __init__(self, api_client: ApiClient):
|
def __init__(self, api_client: ApiClient) -> None:
|
||||||
self.api_client = api_client # <-- dependency is injected
|
self.api_client = api_client # <-- dependency is injected
|
||||||
|
|
||||||
|
|
||||||
def main(service: Service): # <-- dependency is injected
|
def main(service: Service) -> None: # <-- dependency is injected
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ After:
|
||||||
service=Service(
|
service=Service(
|
||||||
api_client=ApiClient(
|
api_client=ApiClient(
|
||||||
api_key=os.getenv("API_KEY"),
|
api_key=os.getenv("API_KEY"),
|
||||||
timeout=os.getenv("TIMEOUT"),
|
timeout=int(os.getenv("TIMEOUT")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -137,7 +137,7 @@ Now you need to assemble and inject the objects like this:
|
||||||
service=Service(
|
service=Service(
|
||||||
api_client=ApiClient(
|
api_client=ApiClient(
|
||||||
api_key=os.getenv("API_KEY"),
|
api_key=os.getenv("API_KEY"),
|
||||||
timeout=os.getenv("TIMEOUT"),
|
timeout=int(os.getenv("TIMEOUT")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -149,14 +149,14 @@ Here comes the ``Dependency Injector``.
|
||||||
What does the Dependency Injector do?
|
What does the Dependency Injector do?
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
With the dependency injection pattern objects loose the responsibility of assembling
|
With the dependency injection pattern, objects lose the responsibility of assembling
|
||||||
the dependencies. The ``Dependency Injector`` absorbs that responsibility.
|
the dependencies. The ``Dependency Injector`` absorbs that responsibility.
|
||||||
|
|
||||||
``Dependency Injector`` helps to assemble and inject the dependencies.
|
``Dependency Injector`` helps to assemble and inject the dependencies.
|
||||||
|
|
||||||
It provides a container and providers that help you with the objects assembly.
|
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
|
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.
|
the dependency.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -182,7 +182,7 @@ the dependency.
|
||||||
|
|
||||||
|
|
||||||
@inject
|
@inject
|
||||||
def main(service: Service = Provide[Container.service]):
|
def main(service: Service = Provide[Container.service]) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@ -197,79 +197,79 @@ the dependency.
|
||||||
with container.api_client.override(mock.Mock()):
|
with container.api_client.override(mock.Mock()):
|
||||||
main() # <-- overridden dependency is injected automatically
|
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
|
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.
|
client with a mock. When you call ``main()``, the mock is injected.
|
||||||
|
|
||||||
You can override any provider with another provider.
|
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 a stub on the dev or stage.
|
||||||
|
|
||||||
Objects assembling is consolidated in the container. Dependency injections are defined explicitly.
|
Objects assembling is consolidated in a 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 an application works.
|
||||||
|
|
||||||
Testing, Monkey-patching and dependency injection
|
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
|
In Python, you can monkey-patch anything, anytime. The problem with monkey-patching is
|
||||||
anything, anytime. The problem with a monkey-patching is that it's too fragile. The reason is that
|
that it's too fragile. The cause of it is that when you monkey-patch you do something that
|
||||||
when you monkey-patch you do something that wasn't intended to be done. You monkey-patch the
|
wasn't intended to be done. You monkey-patch the implementation details. When implementation
|
||||||
implementation details. When implementation changes the monkey-patching is broken.
|
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.
|
stable approach.
|
||||||
|
|
||||||
Also monkey-patching is a way too dirty to be used outside of the testing code for
|
Also, monkey-patching is way too dirty to be used outside of the testing code for
|
||||||
reconfiguring the project for the different environments.
|
re-configuring the project for the different environments.
|
||||||
|
|
||||||
Conclusion
|
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
|
- **Flexibility**. The components are loosely coupled. You can easily extend or change the
|
||||||
functionality of the system by combining the components different way. You even can do it on
|
functionality of a system by combining the components in a different way. You even can do it on
|
||||||
the fly.
|
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.
|
that use API or database, etc.
|
||||||
- **Clearness and maintainability**. Dependency injection helps you reveal the dependencies.
|
- **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).
|
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
|
You have all the components and dependencies defined explicitly in a container. This
|
||||||
provides an overview and control on the application structure. It is easy to understand and
|
provides an overview and control of the application structure. It is easier to understand and
|
||||||
change it.
|
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
|
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
|
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
|
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:
|
framework but using a framework is beneficial because the framework is:
|
||||||
|
|
||||||
- Already implemented
|
- Already implemented
|
||||||
- Tested on all platforms and versions of Python
|
- Tested on all platforms and versions of Python
|
||||||
- Documented
|
- Documented
|
||||||
- Supported
|
- 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
|
- **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
|
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
|
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
|
two weeks. This time will be enough for getting your own impression. If you don't like it you
|
||||||
won't lose too much.
|
won't lose too much.
|
||||||
- **Common sense first**. Use a common sense when apply dependency injection. It is a good
|
- **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 much of the
|
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.
|
implementation details. Experience comes with practice and time.
|
||||||
|
|
||||||
What's next?
|
What's next?
|
||||||
|
@ -303,12 +303,13 @@ Choose one of the following as a next step:
|
||||||
Useful links
|
Useful links
|
||||||
------------
|
------------
|
||||||
|
|
||||||
There are some useful links related to dependency injection design pattern
|
A few useful links related to a dependency injection design pattern for further reading:
|
||||||
that could be used for further reading:
|
|
||||||
|
|
||||||
+ https://en.wikipedia.org/wiki/Dependency_injection
|
+ https://en.wikipedia.org/wiki/Dependency_injection
|
||||||
+ https://martinfowler.com/articles/injection.html
|
+ https://martinfowler.com/articles/injection.html
|
||||||
+ https://github.com/ets-labs/python-dependency-injector
|
+ https://github.com/ets-labs/python-dependency-injector
|
||||||
+ https://pypi.org/project/dependency-injector/
|
+ https://pypi.org/project/dependency-injector/
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -7,8 +7,8 @@ Introduction
|
||||||
overview of the dependency injection, inversion of
|
overview of the dependency injection, inversion of
|
||||||
control and Dependency Injector framework.
|
control and Dependency Injector framework.
|
||||||
|
|
||||||
Current section of the documentation provides an overview of the
|
The current section of the documentation provides an overview of the
|
||||||
dependency injection, inversion of control and the ``Dependency Injector`` framework.
|
dependency injection, inversion of control, and the ``Dependency Injector`` framework.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
|
@ -2,7 +2,7 @@ Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
``Dependency Injector`` is available on `PyPI <https://pypi.org/project/dependency-injector/>`_.
|
``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
|
.. 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.
|
Some modules of the ``Dependency Injector`` are implemented as C extensions.
|
||||||
``Dependency Injector`` is distributed as a pre-compiled wheels. Wheels are
|
``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>`_.
|
Linux distribution uses `manylinux <https://github.com/pypa/manylinux>`_.
|
||||||
|
|
||||||
If there is no appropriate wheel for your environment (Python version and OS)
|
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
|
>>> import dependency_injector
|
||||||
>>> dependency_injector.__version__
|
>>> dependency_injector.__version__
|
||||||
'4.37.0'
|
'4.39.0'
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
When add ``Dependency Injector`` to the ``requirements.txt`` don't forget to pin version
|
When adding ``Dependency Injector`` to ``pyproject.toml`` or ``requirements.txt``
|
||||||
to the current major:
|
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>`_.
|
All releases are available on the `PyPI release history page <https://pypi.org/project/dependency-injector/#history>`_.
|
||||||
Each release has appropriate tag. The tags are available on
|
Each release has an appropriate tag. The tags are available on the
|
||||||
`GitHub releases page <https://github.com/ets-labs/python-dependency-injector/releases>`_.
|
`GitHub releases page <https://github.com/ets-labs/python-dependency-injector/releases>`_.
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -11,33 +11,33 @@ Key features
|
||||||
Key features of the ``Dependency Injector``:
|
Key features of the ``Dependency Injector``:
|
||||||
|
|
||||||
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
|
||||||
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
|
``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency``, and ``Selector`` providers
|
||||||
that help assembling your objects. See :ref:`providers`.
|
that help assemble your objects. See :ref:`providers`.
|
||||||
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing
|
- **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`.
|
: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`.
|
environment variables, and dictionaries. See :ref:`configuration-provider`.
|
||||||
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
|
- **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.
|
or process pool, etc. Can be used for per-function execution scope in tandem with wiring.
|
||||||
See :ref:`resource-provider`.
|
See :ref:`resource-provider`.
|
||||||
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
|
- **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`.
|
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
|
||||||
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
|
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
|
||||||
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
|
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
|
||||||
- **Performance**. Fast. Written in ``Cython``.
|
- **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:
|
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
|
||||||
|
|
||||||
.. code-block:: plain
|
.. code-block:: text
|
||||||
|
|
||||||
Explicit is better than implicit
|
Explicit is better than implicit
|
||||||
|
|
||||||
You need to specify how to assemble and where to inject the dependencies explicitly.
|
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.
|
``Dependency Injector`` is a simple tool for the powerful concept.
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -1,12 +1,150 @@
|
||||||
Changelog
|
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.
|
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`_
|
follows `Semantic versioning`_
|
||||||
|
|
||||||
|
4.48.1
|
||||||
|
------
|
||||||
|
|
||||||
|
* Improve performance of ``dependency_injector._cwiring.DependencyResolver``
|
||||||
|
* Add ``typing-extensions`` as a dependency for older Python versions (<3.11)
|
||||||
|
* Produce warning on ``@inject``s without ``Provide[...]`` marks
|
||||||
|
* Add support for `resource_type` in ``Lifespan``s
|
||||||
|
|
||||||
|
4.48.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- Improve performance of wiring (`#897 <https://github.com/ets-labs/python-dependency-injector/pull/897>`_)
|
||||||
|
- Add Context Manager support to Resource provider (`#899 <https://github.com/ets-labs/python-dependency-injector/pull/899>`_)
|
||||||
|
- Add support for async generator injections (`#900 <https://github.com/ets-labs/python-dependency-injector/pull/900>`_)
|
||||||
|
- Fix unintended dependency on ``typing_extensions`` (`#902 <https://github.com/ets-labs/python-dependency-injector/pull/902>`_)
|
||||||
|
- Add support for Fast Depends (`#898 <https://github.com/ets-labs/python-dependency-injector/pull/898>`_)
|
||||||
|
- Add ``resource_type`` parameter to init and shutdown resources using specialized providers (`#858 <https://github.com/ets-labs/python-dependency-injector/pull/858>`_)
|
||||||
|
|
||||||
|
4.47.1
|
||||||
|
------
|
||||||
|
|
||||||
|
- Fix typing for wiring marker (`#892 <https://github.com/ets-labs/python-dependency-injector/pull/896>`_)
|
||||||
|
- Strip debug symbols in wheels
|
||||||
|
|
||||||
|
4.47.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- Add support for ``Annotated`` type for module and class attribute injection in wiring,
|
||||||
|
with updated documentation and examples.
|
||||||
|
See discussion:
|
||||||
|
https://github.com/ets-labs/python-dependency-injector/pull/721#issuecomment-2025263718
|
||||||
|
- Fix ``root`` property shadowing in ``ConfigurationOption`` (`#875 <https://github.com/ets-labs/python-dependency-injector/pull/875>`_)
|
||||||
|
- Fix incorrect monkeypatching during ``wire()`` that could violate MRO in some classes (`#886 <https://github.com/ets-labs/python-dependency-injector/pull/886>`_)
|
||||||
|
- ABI3 wheels are now published for CPython.
|
||||||
|
- Drop support of Python 3.7.
|
||||||
|
|
||||||
|
4.46.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- Add option to disable env var interpolation in configs (`#861 <https://github.com/ets-labs/python-dependency-injector/pull/861>`_)
|
||||||
|
- Fix ``Closing`` dependency resolution (`#852 <https://github.com/ets-labs/python-dependency-injector/pull/852>`_)
|
||||||
|
- Add support for ``inspect.iscoroutinefunction()`` in ``Coroutine`` provider (`#830 <https://github.com/ets-labs/python-dependency-injector/pull/830>`_)
|
||||||
|
- Fix broken wiring of sync inject-decorated methods (`#673 <https://github.com/ets-labs/python-dependency-injector/pull/673>`_)
|
||||||
|
- Add support for ``typing.Annotated`` (`#721 <https://github.com/ets-labs/python-dependency-injector/pull/721>`_, `#853 <https://github.com/ets-labs/python-dependency-injector/pull/853>`_)
|
||||||
|
- Documentation updates for movie-lister example (`#747 <https://github.com/ets-labs/python-dependency-injector/pull/747>`_)
|
||||||
|
- Fix type propagation in ``Provider.provider`` (`#744 <https://github.com/ets-labs/python-dependency-injector/pull/744>`_)
|
||||||
|
|
||||||
|
Many thanks for the contributions to:
|
||||||
|
- `ZipFile <https://github.com/ZipFile>`_
|
||||||
|
- `Yegor Statkevich <https://github.com/jazzthief>`_
|
||||||
|
- `Federico Tomasi <https://github.com/federinik>`_
|
||||||
|
- `Martin Lafrance <https://github.com/martlaf>`_
|
||||||
|
- `Philip Bjorge <https://github.com/philipbjorge>`_
|
||||||
|
- `Ilya Kazakov <https://github.com/mrKazzila>`_
|
||||||
|
|
||||||
|
4.45.0
|
||||||
|
--------
|
||||||
|
- Add Starlette lifespan handler implementation (`#683 <https://github.com/ets-labs/python-dependency-injector/pull/683>`_).
|
||||||
|
- Raise exception in ``ThreadLocalSingleton`` instead of hiding it in finally (`#845 <https://github.com/ets-labs/python-dependency-injector/pull/845>`_).
|
||||||
|
- Improve debuggability of ``deepcopy`` errors (`#839 <https://github.com/ets-labs/python-dependency-injector/pull/839>`_).
|
||||||
|
- Update examples (`#838 <https://github.com/ets-labs/python-dependency-injector/pull/838>`_).
|
||||||
|
- Upgrade testing dependencies (`#837 <https://github.com/ets-labs/python-dependency-injector/pull/837>`_).
|
||||||
|
- Add minor fixes to the documentation (`#709 <https://github.com/ets-labs/python-dependency-injector/pull/709>`_).
|
||||||
|
- Remove ``six`` from the dependencies (`3ba4704 <https://github.com/ets-labs/python-dependency-injector/commit/3ba4704bc1cb00310749fd2eda0c8221167c313c>`_).
|
||||||
|
|
||||||
|
Many thanks for the contributions to:
|
||||||
|
- `ZipFile <https://github.com/ZipFile>`_
|
||||||
|
- `František Trebuňa <https://github.com/gortibaldik>`_
|
||||||
|
- `JC (Jonathan Chen) <https://github.com/dijonkitchen>`_
|
||||||
|
|
||||||
|
4.44.0
|
||||||
|
--------
|
||||||
|
- Implement support for Pydantic 2. PR: `#832 <https://github.com/ets-labs/python-dependency-injector/pull/832>`_.
|
||||||
|
- Implement `PEP-517 <https://peps.python.org/pep-0517/>`_, `PEP-518 <https://peps.python.org/pep-0518/>`_, and
|
||||||
|
`PEP-621 <https://peps.python.org/pep-0621/>`_. PR: `#829 <https://github.com/ets-labs/python-dependency-injector/pull/829>`_.
|
||||||
|
|
||||||
|
Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
|
||||||
|
|
||||||
|
4.43.0
|
||||||
|
--------
|
||||||
|
- Add support for Python 3.13.
|
||||||
|
- Migrate to Cython 3 (version 3.0.11). Many thanks to `ZipFile <https://github.com/ZipFile>`_ for
|
||||||
|
this contribution `#813 <https://github.com/ets-labs/python-dependency-injector/pull/813>`_.
|
||||||
|
|
||||||
|
4.42.0
|
||||||
|
--------
|
||||||
|
- Promote release ``4.42.0b1`` to a production release.
|
||||||
|
- Fix the Disqus comment widget.
|
||||||
|
|
||||||
|
4.42.0b1
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Add support of Python 3.12.
|
||||||
|
- Drop support of Python 2.7, 3.5, and 3.6.
|
||||||
|
- Regenerate C sources using Cython 0.29.37.
|
||||||
|
- Update ``cibuildwheel`` to version ``2.20.0``.
|
||||||
|
|
||||||
|
4.41.0
|
||||||
|
------
|
||||||
|
- Add support of Python 3.11.
|
||||||
|
- Allow Closing to detect dependent resources `#633 <https://github.com/ets-labs/python-dependency-injector/issues/633>`_,
|
||||||
|
`#636 <https://github.com/ets-labs/python-dependency-injector/pull/636>`_. Thanks `Jamie Stumme @StummeJ <https://github.com/StummeJ>`_
|
||||||
|
for the contribution.
|
||||||
|
- Update CI/CD to use Ubuntu 22.04.
|
||||||
|
- Update CI/CD to ``actions/checkout@v3``, ``actions/setup-python@v4``, ``actions/upload-artifact@v3``, ``pypa/cibuildwheel@v2.11.3``,
|
||||||
|
and ``actions/download-artifact@v3``.
|
||||||
|
- Fix install crash on non-utf8 systems `#644 <https://github.com/ets-labs/python-dependency-injector/pull/644>`_.
|
||||||
|
- Fix a bug in Windows build with default charset `#635 <https://github.com/ets-labs/python-dependency-injector/pull/635>`_.
|
||||||
|
- Update FastAPI Redis example to use ``aioredis`` version 2 `#613 <https://github.com/ets-labs/python-dependency-injector/pull/613>`_.
|
||||||
|
- Update documentation on creating custom providers `#598 <https://github.com/ets-labs/python-dependency-injector/pull/598>`_.
|
||||||
|
- Regenerate C sources using Cython 0.29.32.
|
||||||
|
- Fix builds badge.
|
||||||
|
|
||||||
|
4.40.0
|
||||||
|
------
|
||||||
|
- Add ``Configuration.from_json()`` method to load configuration from a json file.
|
||||||
|
- Fix bug with wiring not working properly with functions double wrapped by ``@functools.wraps`` decorator.
|
||||||
|
See issue: `#454 <https://github.com/ets-labs/python-dependency-injector/issues/454>`_.
|
||||||
|
Many thanks to: `@platipo <https://github.com/platipo>`_, `@MatthieuMoreau0 <https://github.com/MatthieuMoreau0>`_,
|
||||||
|
`@fabiocerqueira <https://github.com/fabiocerqueira>`_, `@Jitesh-Khuttan <https://github.com/Jitesh-Khuttan>`_.
|
||||||
|
- Refactor wiring module to store all patched callable data in the ``PatchedRegistry``.
|
||||||
|
- Improve wording on the "Dependency injection and inversion of control in Python" docs page.
|
||||||
|
- Add documentation on the ``@inject`` decorator.
|
||||||
|
- Update typing in the main example and cohesion/coupling correlation definition in
|
||||||
|
"Dependency injection and inversion of control in Python".
|
||||||
|
Thanks to `@illia-v (Illia Volochii) <https://github.com/illia-v>`_ for the
|
||||||
|
PR (`#580 <https://github.com/ets-labs/python-dependency-injector/pull/580>`_).
|
||||||
|
- Update copyright year.
|
||||||
|
- Enable skipped test ``test_schema_with_boto3_session()``.
|
||||||
|
- Update pytest configuration.
|
||||||
|
- Regenerate C sources using Cython 0.29.30.
|
||||||
|
|
||||||
|
4.39.1
|
||||||
|
------
|
||||||
|
- Fix bug `#574 <https://github.com/ets-labs/python-dependency-injector/issues/574>`_:
|
||||||
|
"``@inject`` breaks ``inspect.iscoroutinefunction``". Thanks to
|
||||||
|
`@burritoatspoton (Rafał Burczyński) <https://github.com/burritoatspoton>`_ for reporting the issue.
|
||||||
|
|
||||||
4.39.0
|
4.39.0
|
||||||
------
|
------
|
||||||
- Optimize injections and wiring from x1.5 to x7 times depending on the use case.
|
- Optimize injections and wiring from x1.5 to x7 times depending on the use case.
|
||||||
|
@ -69,7 +207,7 @@ follows `Semantic versioning`_
|
||||||
- Fix a typo in ``Factory`` provider docs ``service.add_attributes(clent=client)``
|
- Fix a typo in ``Factory`` provider docs ``service.add_attributes(clent=client)``
|
||||||
`#499 <https://github.com/ets-labs/python-dependency-injector/issues/499>`_.
|
`#499 <https://github.com/ets-labs/python-dependency-injector/issues/499>`_.
|
||||||
Thanks to `@rajanjha786 <https://github.com/rajanjha786>`_ for the contribution.
|
Thanks to `@rajanjha786 <https://github.com/rajanjha786>`_ for the contribution.
|
||||||
- Fix a typo in ``boto3`` example
|
- Fix a typo in ``boto3`` example
|
||||||
`#511 <https://github.com/ets-labs/python-dependency-injector/issues/511>`_.
|
`#511 <https://github.com/ets-labs/python-dependency-injector/issues/511>`_.
|
||||||
Thanks to `@whysage <https://github.com/whysage>`_ for the contribution.
|
Thanks to `@whysage <https://github.com/whysage>`_ for the contribution.
|
||||||
|
|
||||||
|
@ -257,8 +395,8 @@ follows `Semantic versioning`_
|
||||||
- Make refactoring of wiring module and tests.
|
- Make refactoring of wiring module and tests.
|
||||||
See PR # `#406 <https://github.com/ets-labs/python-dependency-injector/issues/406>`_.
|
See PR # `#406 <https://github.com/ets-labs/python-dependency-injector/issues/406>`_.
|
||||||
Thanks to `@withshubh <https://github.com/withshubh>`_ for the contribution:
|
Thanks to `@withshubh <https://github.com/withshubh>`_ for the contribution:
|
||||||
- Remove unused imports in tests.
|
- Remove unused imports in tests.
|
||||||
- Use literal syntax to create data structure in tests.
|
- Use literal syntax to create data structure in tests.
|
||||||
- Add integration with a static analysis tool `DeepSource <https://deepsource.io/>`_.
|
- Add integration with a static analysis tool `DeepSource <https://deepsource.io/>`_.
|
||||||
|
|
||||||
4.26.0
|
4.26.0
|
||||||
|
@ -1269,24 +1407,24 @@ Misc:
|
||||||
------
|
------
|
||||||
- Add ``DependenciesContainer`` provider.
|
- Add ``DependenciesContainer`` provider.
|
||||||
- Add "use_cases" example miniapp.
|
- Add "use_cases" example miniapp.
|
||||||
- Update documentation requirements to use fixed version of
|
- Update documentation requirements to use fixed version of
|
||||||
``sphinxcontrib-disqus``.
|
``sphinxcontrib-disqus``.
|
||||||
|
|
||||||
|
|
||||||
3.9.1
|
3.9.1
|
||||||
-----
|
-----
|
||||||
- Fix docs build problem (``sphinx`` is frozen on ``1.5.6`` version because of
|
- 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.
|
- Add badge for docs.
|
||||||
|
|
||||||
3.9.0
|
3.9.0
|
||||||
-----
|
-----
|
||||||
- Change initialization of declarative container, so it accepts overriding
|
- Change initialization of declarative container, so it accepts overriding
|
||||||
providers as keyword arguments -
|
providers as keyword arguments -
|
||||||
``DeclarativeContainer(**overriding_providers)``.
|
``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)``.
|
``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)``.
|
``DynamicContainer.set_providers(**overriding_providers)``.
|
||||||
- Rename ``ExternalDependency`` provider to ``Dependency``.
|
- Rename ``ExternalDependency`` provider to ``Dependency``.
|
||||||
- Add default value for ``instance_of`` argument of ``Dependency`` provider -
|
- Add default value for ``instance_of`` argument of ``Dependency`` provider -
|
||||||
|
@ -1318,7 +1456,7 @@ Misc:
|
||||||
3.7.0
|
3.7.0
|
||||||
-----
|
-----
|
||||||
- Add ``FactoryAggregate`` provider.
|
- 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()``).
|
delegate (alias of method ``Provider.delegate()``).
|
||||||
- Add support of six 1.11.0.
|
- Add support of six 1.11.0.
|
||||||
- Regenerate C sources using Cython 0.27.1.
|
- Regenerate C sources using Cython 0.27.1.
|
||||||
|
@ -1335,7 +1473,7 @@ Misc:
|
||||||
|
|
||||||
3.5.0
|
3.5.0
|
||||||
-----
|
-----
|
||||||
- Add functionality for initializing ``Configuration`` provider with default
|
- Add functionality for initializing ``Configuration`` provider with default
|
||||||
values.
|
values.
|
||||||
|
|
||||||
3.4.8
|
3.4.8
|
||||||
|
@ -1358,7 +1496,7 @@ Misc:
|
||||||
|
|
||||||
3.4.4
|
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``
|
overriding provider, if any. If target provider is not overridden, ``None``
|
||||||
would be returned.
|
would be returned.
|
||||||
- Update example of writing custom providers.
|
- Update example of writing custom providers.
|
||||||
|
@ -1372,7 +1510,7 @@ Misc:
|
||||||
3.4.2
|
3.4.2
|
||||||
-----
|
-----
|
||||||
- Make ``Provider`` overriding methods thread safe:
|
- Make ``Provider`` overriding methods thread safe:
|
||||||
``Provider.override(provider)``, ``Provider.reset_last_overriding()``,
|
``Provider.override(provider)``, ``Provider.reset_last_overriding()``,
|
||||||
``Provider.reset_override()``.
|
``Provider.reset_override()``.
|
||||||
- Refactor storage locking of ``ThreadSafeSingleton`` provider.
|
- Refactor storage locking of ``ThreadSafeSingleton`` provider.
|
||||||
- Fix few ``pydocstyle`` errors in examples.
|
- Fix few ``pydocstyle`` errors in examples.
|
||||||
|
@ -1444,8 +1582,8 @@ Misc:
|
||||||
|
|
||||||
3.2.4
|
3.2.4
|
||||||
-----
|
-----
|
||||||
- Switch to single version of documentation for getting shorter urls (without
|
- Switch to single version of documentation for getting shorter urls (without
|
||||||
``/en/stable/``). Add appropriate redirects for compatibility with previous
|
``/en/stable/``). Add appropriate redirects for compatibility with previous
|
||||||
links.
|
links.
|
||||||
- Update copyright date.
|
- Update copyright date.
|
||||||
|
|
||||||
|
@ -1464,7 +1602,7 @@ Misc:
|
||||||
|
|
||||||
3.2.0
|
3.2.0
|
||||||
-----
|
-----
|
||||||
- Add ``Configuration`` provider for late static binding of configuration
|
- Add ``Configuration`` provider for late static binding of configuration
|
||||||
options.
|
options.
|
||||||
|
|
||||||
3.1.5
|
3.1.5
|
||||||
|
@ -1474,7 +1612,7 @@ Misc:
|
||||||
|
|
||||||
3.1.4
|
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.
|
from virtual table and enable inlining.
|
||||||
|
|
||||||
3.1.3
|
3.1.3
|
||||||
|
@ -1506,34 +1644,34 @@ Misc:
|
||||||
|
|
||||||
- **Providers**
|
- **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.
|
implemented as C extension types using Cython.
|
||||||
2. Add ``BaseSingleton`` super class for all singleton providers.
|
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.
|
``Singleton`` provider 10x times faster.
|
||||||
4. Add ``ThreadSafeSingleton`` provider - thread-safe version of
|
4. Add ``ThreadSafeSingleton`` provider - thread-safe version of
|
||||||
``Singleton`` provider.
|
``Singleton`` provider.
|
||||||
5. Add ``ThreadLocalSingleton`` provider - ``Singleton`` provider that uses
|
5. Add ``ThreadLocalSingleton`` provider - ``Singleton`` provider that uses
|
||||||
thread-local storage.
|
thread-local storage.
|
||||||
6. Remove ``provides`` attribute from ``Factory`` and ``Singleton``
|
6. Remove ``provides`` attribute from ``Factory`` and ``Singleton``
|
||||||
providers.
|
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.
|
``Factory`` and ``Singleton`` providers.
|
||||||
|
|
||||||
- **Containers**
|
- **Containers**
|
||||||
|
|
||||||
1. Module ``dependency_injector.containers`` was split into submodules
|
1. Module ``dependency_injector.containers`` was split into submodules
|
||||||
without any functional changes.
|
without any functional changes.
|
||||||
|
|
||||||
- **Utils**
|
- **Utils**
|
||||||
|
|
||||||
1. Module ``dependency_injector.utils`` is split into
|
1. Module ``dependency_injector.utils`` is split into
|
||||||
``dependency_injector.containers`` and ``dependency_injector.providers``.
|
``dependency_injector.containers`` and ``dependency_injector.providers``.
|
||||||
|
|
||||||
- **Miscellaneous**
|
- **Miscellaneous**
|
||||||
|
|
||||||
1. Remove ``@inject`` decorator.
|
1. Remove ``@inject`` decorator.
|
||||||
2. Add makefile (``clean``, ``test``, ``build``, ``install``, ``uninstall``
|
2. Add makefile (``clean``, ``test``, ``build``, ``install``, ``uninstall``
|
||||||
& ``publish`` commands).
|
& ``publish`` commands).
|
||||||
3. Update repository structure:
|
3. Update repository structure:
|
||||||
|
|
||||||
|
@ -1600,7 +1738,7 @@ Misc:
|
||||||
|
|
||||||
2.0.0
|
2.0.0
|
||||||
------
|
------
|
||||||
- Introduce new injections style for ``Callable``, ``Factory`` &
|
- Introduce new injections style for ``Callable``, ``Factory`` &
|
||||||
``Singleton`` providers.
|
``Singleton`` providers.
|
||||||
- Drop providers: ``Static``, ``Value``, ``Function``, ``Class``, ``Config``.
|
- Drop providers: ``Static``, ``Value``, ``Function``, ``Class``, ``Config``.
|
||||||
- Increase performance of making injections in 2 times (+100%).
|
- Increase performance of making injections in 2 times (+100%).
|
||||||
|
@ -1613,8 +1751,8 @@ Misc:
|
||||||
|
|
||||||
1.17.0
|
1.17.0
|
||||||
------
|
------
|
||||||
- Add ``add_injections()`` method to ``Callable``, ``DelegatedCallable``,
|
- Add ``add_injections()`` method to ``Callable``, ``DelegatedCallable``,
|
||||||
``Factory``, ``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton``
|
``Factory``, ``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton``
|
||||||
providers.
|
providers.
|
||||||
- Fix bug with accessing to declarative catalog attributes from instance level.
|
- Fix bug with accessing to declarative catalog attributes from instance level.
|
||||||
|
|
||||||
|
@ -1642,14 +1780,14 @@ Misc:
|
||||||
- Add "Examples" section into documentation.
|
- Add "Examples" section into documentation.
|
||||||
- Add "Movie Lister" example.
|
- Add "Movie Lister" example.
|
||||||
- Add "Services" example.
|
- Add "Services" example.
|
||||||
- Move project documentation into organisation's domain
|
- Move project documentation into organisation's domain
|
||||||
(dependency-injector.ets-labs.org).
|
(dependency-injector.ets-labs.org).
|
||||||
|
|
||||||
1.15.2
|
1.15.2
|
||||||
------
|
------
|
||||||
- [Refactoring] split ``catalogs`` module into smaller modules,
|
- [Refactoring] split ``catalogs`` module into smaller modules,
|
||||||
``catalogs`` module become a package.
|
``catalogs`` module become a package.
|
||||||
- [Refactoring] split ``providers`` module into smaller modules,
|
- [Refactoring] split ``providers`` module into smaller modules,
|
||||||
``providers`` module become a package.
|
``providers`` module become a package.
|
||||||
- Update introduction documentation.
|
- Update introduction documentation.
|
||||||
|
|
||||||
|
@ -1659,7 +1797,7 @@ Misc:
|
||||||
|
|
||||||
1.15.0
|
1.15.0
|
||||||
------
|
------
|
||||||
- Add ``Provider.provide()`` method. ``Provider.__call__()`` become a
|
- Add ``Provider.provide()`` method. ``Provider.__call__()`` become a
|
||||||
reference to ``Provider.provide()``.
|
reference to ``Provider.provide()``.
|
||||||
- Add provider overriding context.
|
- Add provider overriding context.
|
||||||
- Update main examples and README.
|
- Update main examples and README.
|
||||||
|
@ -1689,7 +1827,7 @@ Misc:
|
||||||
|
|
||||||
1.14.6
|
1.14.6
|
||||||
------
|
------
|
||||||
- Add ``cls`` alias for ``provides`` attributes of ``Factory``,
|
- Add ``cls`` alias for ``provides`` attributes of ``Factory``,
|
||||||
``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton`` providers.
|
``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton`` providers.
|
||||||
|
|
||||||
1.14.5
|
1.14.5
|
||||||
|
@ -1748,27 +1886,27 @@ Misc:
|
||||||
|
|
||||||
1.11.1
|
1.11.1
|
||||||
------
|
------
|
||||||
Previous state of *Dependency Injector* framework (0.11.0 version) is
|
Previous state of *Dependency Injector* framework (0.11.0 version) is
|
||||||
considered to be production ready / stable, so current release is considered
|
considered to be production ready / stable, so current release is considered
|
||||||
to be the first major release.
|
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.
|
- Backward compatibility with all previous versions above 0.7.6 has been saved.
|
||||||
|
|
||||||
0.11.0
|
0.11.0
|
||||||
------
|
------
|
||||||
- Rename ``AbstractCatalog`` to ``DeclarativeCatalog``
|
- Rename ``AbstractCatalog`` to ``DeclarativeCatalog``
|
||||||
(with backward compatibility).
|
(with backward compatibility).
|
||||||
- Rename ``catalog`` module to ``catalogs`` with backward compatibility.
|
- Rename ``catalog`` module to ``catalogs`` with backward compatibility.
|
||||||
- Implement dynamic binding of providers for ``DeclarativeCatalog``.
|
- Implement dynamic binding of providers for ``DeclarativeCatalog``.
|
||||||
- Add ``DynamicCatalog``.
|
- 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.
|
bound to several catalogs with different names.
|
||||||
- Restrict overriding of providers by themselves.
|
- Restrict overriding of providers by themselves.
|
||||||
- Restrict overriding of catalogs 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.
|
default.
|
||||||
- Make ``Provider.last_overriding`` attribute to be ``None`` by
|
- Make ``Provider.last_overriding`` attribute to be ``None`` by
|
||||||
default.
|
default.
|
||||||
- Refactor catalogs and providers modules.
|
- Refactor catalogs and providers modules.
|
||||||
- Add API documentation
|
- Add API documentation
|
||||||
|
@ -1776,7 +1914,7 @@ to be the first major release.
|
||||||
|
|
||||||
0.10.5
|
0.10.5
|
||||||
------
|
------
|
||||||
- Add more representable implementation for ``AbstractCatalog`` and
|
- Add more representable implementation for ``AbstractCatalog`` and
|
||||||
``AbstractCatalog.Bundle``.
|
``AbstractCatalog.Bundle``.
|
||||||
|
|
||||||
0.10.4
|
0.10.4
|
||||||
|
@ -1800,17 +1938,17 @@ to be the first major release.
|
||||||
- Add functionality for creating ``AbstractCatalog`` provider bundles.
|
- Add functionality for creating ``AbstractCatalog`` provider bundles.
|
||||||
- Improve ``AbstractCatalog`` inheritance.
|
- Improve ``AbstractCatalog`` inheritance.
|
||||||
- Improve ``AbstractCatalog`` overriding.
|
- Improve ``AbstractCatalog`` overriding.
|
||||||
- Add images for catalog "Writing catalogs" and "Operating with catalogs"
|
- Add images for catalog "Writing catalogs" and "Operating with catalogs"
|
||||||
examples.
|
examples.
|
||||||
- Add functionality for using positional argument injections with
|
- Add functionality for using positional argument injections with
|
||||||
``Factory``, ``Singleton``, ``Callable`` providers and
|
``Factory``, ``Singleton``, ``Callable`` providers and
|
||||||
``inject`` decorator.
|
``inject`` decorator.
|
||||||
- Add functionality for decorating classes with ``@inject``.
|
- 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).
|
``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).
|
``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.
|
type of injection once, instead of doing this on every call.
|
||||||
- Add ``VERSION`` constant for verification of currently installed version.
|
- Add ``VERSION`` constant for verification of currently installed version.
|
||||||
- Add support of Python 3.5.
|
- Add support of Python 3.5.
|
||||||
|
@ -1820,7 +1958,7 @@ to be the first major release.
|
||||||
0.9.5
|
0.9.5
|
||||||
-----
|
-----
|
||||||
- Change provider attributes scope to public.
|
- 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).
|
``Factory`` injections (including kwargs, attributes and methods).
|
||||||
|
|
||||||
0.9.4
|
0.9.4
|
||||||
|
@ -1837,14 +1975,14 @@ to be the first major release.
|
||||||
|
|
||||||
0.9.1
|
0.9.1
|
||||||
-----
|
-----
|
||||||
- Add simplified syntax of kwarg injections for ``di.Factory`` and
|
- Add simplified syntax of kwarg injections for ``di.Factory`` and
|
||||||
``di.Singleton`` providers:
|
``di.Singleton`` providers:
|
||||||
``di.Factory(SomeClass, dependency1=injectable_provider_or_value)``.
|
``di.Factory(SomeClass, dependency1=injectable_provider_or_value)``.
|
||||||
- Add simplified syntax of kwarg injections for ``di.Callable`` provider:
|
- Add simplified syntax of kwarg injections for ``di.Callable`` provider:
|
||||||
``di.Callable(some_callable, dependency1=injectable_provider_or_value)``
|
``di.Callable(some_callable, dependency1=injectable_provider_or_value)``
|
||||||
- Add simplified syntax of kwarg injections for ``@di.inject`` decorator:
|
- Add simplified syntax of kwarg injections for ``@di.inject`` decorator:
|
||||||
``@di.inject(dependency1=injectable_provider_or_value)``.
|
``@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.
|
the same callback.
|
||||||
- Add minor refactorings.
|
- Add minor refactorings.
|
||||||
- Fix of minor documentation issues.
|
- Fix of minor documentation issues.
|
||||||
|
@ -1864,21 +2002,21 @@ to be the first major release.
|
||||||
0.7.6
|
0.7.6
|
||||||
-----
|
-----
|
||||||
|
|
||||||
- Adding support of six from 1.7.0 to 1.9.0.
|
- Adding support of six from 1.7.0 to 1.9.0.
|
||||||
- Factory / Singleton providers are free from restriction to operate with
|
- Factory / Singleton providers are free from restriction to operate with
|
||||||
classes only. This feature gives a change to use factory method and
|
classes only. This feature gives a change to use factory method and
|
||||||
functions with Factory / Singleton providers.
|
functions with Factory / Singleton providers.
|
||||||
- All attributes of all entities that have to be protected was renamed using
|
- All attributes of all entities that have to be protected was renamed using
|
||||||
``_protected`` manner.
|
``_protected`` manner.
|
||||||
- Providers extending was improved by implementing overriding logic in
|
- Providers extending was improved by implementing overriding logic in
|
||||||
``Provider.__call__()`` and moving providing logic into
|
``Provider.__call__()`` and moving providing logic into
|
||||||
``Provider._provide()``.
|
``Provider._provide()``.
|
||||||
- ``NewInstance`` provider was renamed to ``Factory`` provider.
|
- ``NewInstance`` provider was renamed to ``Factory`` provider.
|
||||||
``NewInstance`` still can be used, but it considered to be deprecated and
|
``NewInstance`` still can be used, but it considered to be deprecated and
|
||||||
will be removed in further releases.
|
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
|
``_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.
|
boost due minimizing number of calls for doing injections.
|
||||||
- A lot of documentation updates were made.
|
- A lot of documentation updates were made.
|
||||||
- A lot of examples were added.
|
- A lot of examples were added.
|
||||||
|
|
|
@ -136,25 +136,69 @@ To use another loader use ``loader`` argument:
|
||||||
|
|
||||||
*Don't forget to mirror the changes in the requirements file.*
|
*Don't forget to mirror the changes in the requirements file.*
|
||||||
|
|
||||||
|
Loading from a JSON file
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
``Configuration`` provider can load configuration from a ``json`` file using the
|
||||||
|
:py:meth:`Configuration.from_json` method:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/configuration_json.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 12
|
||||||
|
|
||||||
|
where ``examples/providers/configuration/config.json`` is:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/configuration/config.json
|
||||||
|
:language: json
|
||||||
|
|
||||||
|
Alternatively, you can provide a path to a json file over the configuration provider argument. In that case,
|
||||||
|
the container will call ``config.from_json()`` automatically:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 3
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
config = providers.Configuration(json_files=["./config.json"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container() # Config is loaded from ./config.json
|
||||||
|
|
||||||
|
:py:meth:`Configuration.from_json` method supports environment variables interpolation.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"section": {
|
||||||
|
"option1": "${ENV_VAR}",
|
||||||
|
"option2": "${ENV_VAR}/path",
|
||||||
|
"option3": "${ENV_VAR:default}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
See also: :ref:`configuration-envs-interpolation`.
|
||||||
|
|
||||||
Loading from a Pydantic settings
|
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:
|
:py:meth:`Configuration.from_pydantic` method:
|
||||||
|
|
||||||
.. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py
|
.. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py
|
||||||
:language: python
|
:language: python
|
||||||
:lines: 3-
|
: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.
|
If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
container.config.from_pydantic(Settings(), exclude={"optional"})
|
container.config.from_pydantic(Settings(), exclude={"optional"})
|
||||||
|
|
||||||
Alternatively, you can provide a ``pydantic`` settings object over the configuration provider argument. In that case,
|
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:
|
the container will call ``config.from_pydantic()`` automatically:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -171,18 +215,23 @@ the container will call ``config.from_pydantic()`` automatically:
|
||||||
|
|
||||||
.. note::
|
.. 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::
|
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.*
|
*Don't forget to mirror the changes in the requirements file.*
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
For backward-compatibility, Pydantic v1 is still supported.
|
||||||
|
Passing ``pydantic.BaseSettings`` instances will work just as fine as ``pydantic_settings.BaseSettings``.
|
||||||
|
|
||||||
Loading from a dictionary
|
Loading from a dictionary
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
@ -317,6 +366,19 @@ See also: :ref:`configuration-strict-mode`.
|
||||||
|
|
||||||
assert container.config.section.option() is None
|
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
|
Mandatory and optional sources
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,11 @@ To create a custom provider you need to follow these rules:
|
||||||
1. New provider class should inherit :py:class:`Provider`.
|
1. New provider class should inherit :py:class:`Provider`.
|
||||||
2. You need to implement the ``Provider._provide()`` method.
|
2. You need to implement the ``Provider._provide()`` method.
|
||||||
3. You need to implement the ``Provider.__deepcopy__()`` method. It should return an
|
3. You need to implement the ``Provider.__deepcopy__()`` method. It should return an
|
||||||
equivalent copy of a provider. All providers must be copied with a ``deepcopy()`` function
|
equivalent copy of a provider. All providers must be copied with the ``deepcopy()`` function
|
||||||
from the ``providers`` module. After the a new provider object is created use
|
from the ``providers`` module. It's essential to pass ``memo`` into ``deepcopy`` in order to keep
|
||||||
``Provider._copy_overriding()`` method to copy all overriding providers. See the example
|
the preconfigured ``args`` and ``kwargs`` of stored providers. After the a new provider object
|
||||||
below.
|
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
|
4. If new provider has a ``__init__()`` method, it should call the parent
|
||||||
``Provider.__init__()``.
|
``Provider.__init__()``.
|
||||||
5. If new provider stores any other providers, these providers should be listed in
|
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::
|
.. note::
|
||||||
1. Prefer delegation over inheritance. If you choose between inheriting a ``Factory`` or
|
1. Prefer delegation over inheritance. If you choose between inheriting a ``Factory`` or
|
||||||
inheriting a ``Provider`` and use ``Factory`` internally - the last is better.
|
inheriting a ``Provider`` and use ``Factory`` internally - the last is better.
|
||||||
2. When 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
|
3. Use the ``__slots__`` attribute to make sure nothing could be attached to your provider. You
|
||||||
will also save some memory.
|
will also save some memory.
|
||||||
|
|
||||||
|
|
|
@ -61,11 +61,12 @@ When you call ``.shutdown()`` method on a resource provider, it will remove the
|
||||||
if any, and switch to uninitialized state. Some of resource initializer types support specifying custom
|
if any, and switch to uninitialized state. Some of resource initializer types support specifying custom
|
||||||
resource shutdown.
|
resource shutdown.
|
||||||
|
|
||||||
Resource provider supports 3 types of initializers:
|
Resource provider supports 4 types of initializers:
|
||||||
|
|
||||||
- Function
|
- Function
|
||||||
- Generator
|
- Context Manager
|
||||||
- Subclass of ``resources.Resource``
|
- Generator (legacy)
|
||||||
|
- Subclass of ``resources.Resource`` (legacy)
|
||||||
|
|
||||||
Function initializer
|
Function initializer
|
||||||
--------------------
|
--------------------
|
||||||
|
@ -103,8 +104,44 @@ you configure global resource:
|
||||||
|
|
||||||
Function initializer does not provide a way to specify custom resource shutdown.
|
Function initializer does not provide a way to specify custom resource shutdown.
|
||||||
|
|
||||||
Generator initializer
|
Context Manager initializer
|
||||||
---------------------
|
---------------------------
|
||||||
|
|
||||||
|
This is an extension to the Function initializer. Resource provider automatically detects if the initializer returns a
|
||||||
|
context manager and uses it to manage the resource lifecycle.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
class DatabaseConnection:
|
||||||
|
def __init__(self, host, port, user, password):
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.user = user
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
print(f"Connecting to {self.host}:{self.port} as {self.user}")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
print("Closing connection")
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
config = providers.Configuration()
|
||||||
|
db = providers.Resource(
|
||||||
|
DatabaseConnection,
|
||||||
|
host=config.db.host,
|
||||||
|
port=config.db.port,
|
||||||
|
user=config.db.user,
|
||||||
|
password=config.db.password,
|
||||||
|
)
|
||||||
|
|
||||||
|
Generator initializer (legacy)
|
||||||
|
------------------------------
|
||||||
|
|
||||||
Resource provider can use 2-step generators:
|
Resource provider can use 2-step generators:
|
||||||
|
|
||||||
|
@ -154,8 +191,13 @@ object is not mandatory. You can leave ``yield`` statement empty:
|
||||||
argument2=...,
|
argument2=...,
|
||||||
)
|
)
|
||||||
|
|
||||||
Subclass initializer
|
.. note::
|
||||||
--------------------
|
|
||||||
|
Generator initializers are automatically wrapped with ``contextmanager`` or ``asynccontextmanager`` decorator when
|
||||||
|
provided to a ``Resource`` provider.
|
||||||
|
|
||||||
|
Subclass initializer (legacy)
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
You can create resource initializer by implementing a subclass of the ``resources.Resource``:
|
You can create resource initializer by implementing a subclass of the ``resources.Resource``:
|
||||||
|
|
||||||
|
@ -210,6 +252,72 @@ first argument.
|
||||||
|
|
||||||
.. _resource-provider-wiring-closing:
|
.. _resource-provider-wiring-closing:
|
||||||
|
|
||||||
|
Scoping Resources using specialized subclasses
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
You can use specialized subclasses of ``Resource`` provider to initialize and shutdown resources by type.
|
||||||
|
Allowing for example to only initialize a subgroup of resources.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class ScopedResource(resources.Resource):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def init_service(name) -> Service:
|
||||||
|
print(f"Init {name}")
|
||||||
|
yield Service()
|
||||||
|
print(f"Shutdown {name}")
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
scoped = ScopedResource(
|
||||||
|
init_service,
|
||||||
|
"scoped",
|
||||||
|
)
|
||||||
|
|
||||||
|
generic = providers.Resource(
|
||||||
|
init_service,
|
||||||
|
"generic",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
To initialize resources by type you can use ``init_resources(resource_type)`` and ``shutdown_resources(resource_type)``
|
||||||
|
methods adding the resource type as an argument:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def main():
|
||||||
|
container = Container()
|
||||||
|
container.init_resources(ScopedResource)
|
||||||
|
# Generates:
|
||||||
|
# >>> Init scoped
|
||||||
|
|
||||||
|
container.shutdown_resources(ScopedResource)
|
||||||
|
# Generates:
|
||||||
|
# >>> Shutdown scoped
|
||||||
|
|
||||||
|
|
||||||
|
And to initialize all resources you can use ``init_resources()`` and ``shutdown_resources()`` without arguments:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def main():
|
||||||
|
container = Container()
|
||||||
|
container.init_resources()
|
||||||
|
# Generates:
|
||||||
|
# >>> Init scoped
|
||||||
|
# >>> Init generic
|
||||||
|
|
||||||
|
container.shutdown_resources()
|
||||||
|
# Generates:
|
||||||
|
# >>> Shutdown scoped
|
||||||
|
# >>> Shutdown generic
|
||||||
|
|
||||||
|
|
||||||
|
It works using the ``traverse()`` method to find all resources of the specified type, selecting all resources
|
||||||
|
which are instances of the specified type.
|
||||||
|
|
||||||
|
|
||||||
Resources, wiring, and per-function execution scope
|
Resources, wiring, and per-function execution scope
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
|
@ -263,10 +371,11 @@ Asynchronous function initializer:
|
||||||
argument2=...,
|
argument2=...,
|
||||||
)
|
)
|
||||||
|
|
||||||
Asynchronous generator initializer:
|
Asynchronous Context Manager initializer:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
async def init_async_resource(argument1=..., argument2=...):
|
async def init_async_resource(argument1=..., argument2=...):
|
||||||
connection = await connect()
|
connection = await connect()
|
||||||
yield connection
|
yield connection
|
||||||
|
@ -358,5 +467,54 @@ See also:
|
||||||
- Wiring :ref:`async-injections-wiring`
|
- Wiring :ref:`async-injections-wiring`
|
||||||
- :ref:`fastapi-redis-example`
|
- :ref:`fastapi-redis-example`
|
||||||
|
|
||||||
|
ASGI Lifespan Protocol Support
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
The :mod:`dependency_injector.ext.starlette` module provides a :class:`~dependency_injector.ext.starlette.Lifespan`
|
||||||
|
class that integrates resource providers with ASGI applications using the `Lifespan Protocol`_. This allows resources to
|
||||||
|
be automatically initialized at application startup and properly shut down when the application stops.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from dependency_injector.wiring import Provide, inject
|
||||||
|
from dependency_injector.ext.starlette import Lifespan
|
||||||
|
from fastapi import FastAPI, Request, Depends, APIRouter
|
||||||
|
|
||||||
|
class Connection: ...
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def init_database():
|
||||||
|
print("opening database connection")
|
||||||
|
yield Connection()
|
||||||
|
print("closing database connection")
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
@inject
|
||||||
|
async def index(request: Request, db: Connection = Depends(Provide["db"])):
|
||||||
|
# use the database connection here
|
||||||
|
return "OK!"
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
__self__ = providers.Self()
|
||||||
|
db = providers.Resource(init_database)
|
||||||
|
lifespan = providers.Singleton(Lifespan, __self__)
|
||||||
|
app = providers.Singleton(FastAPI, lifespan=lifespan)
|
||||||
|
_include_router = providers.Resource(
|
||||||
|
app.provided.include_router.call(),
|
||||||
|
router,
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
container = Container()
|
||||||
|
app = container.app()
|
||||||
|
uvicorn.run(app, host="localhost", port=8000)
|
||||||
|
|
||||||
|
.. _Lifespan Protocol: https://asgi.readthedocs.io/en/latest/specs/lifespan.html
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -24,7 +24,7 @@ returns it on the rest of the calls.
|
||||||
|
|
||||||
.. note::
|
.. 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.
|
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
|
Specialization of the provided type and abstract singletons work the same like like for the
|
||||||
|
|
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
|
|
@ -257,7 +257,7 @@ Let's check that it works. Open another terminal session and use ``httpie``:
|
||||||
|
|
||||||
You should see:
|
You should see:
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: http
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Content-Length: 844
|
Content-Length: 844
|
||||||
|
@ -596,7 +596,7 @@ and make a request to the API in the terminal:
|
||||||
|
|
||||||
You should see:
|
You should see:
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: http
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Content-Length: 492
|
Content-Length: 492
|
||||||
|
@ -859,4 +859,6 @@ What's next?
|
||||||
- Know more about the :ref:`providers`
|
- Know more about the :ref:`providers`
|
||||||
- Go to the :ref:`contents`
|
- Go to the :ref:`contents`
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -18,7 +18,7 @@ In this tutorial we will use:
|
||||||
|
|
||||||
- Python 3
|
- Python 3
|
||||||
- Docker
|
- Docker
|
||||||
- Docker-compose
|
- Docker Compose
|
||||||
|
|
||||||
Start from the scratch or jump to the section:
|
Start from the scratch or jump to the section:
|
||||||
|
|
||||||
|
@ -47,28 +47,27 @@ response it will log:
|
||||||
Prerequisites
|
Prerequisites
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
We will use `Docker <https://www.docker.com/>`_ and
|
We will use `docker compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
|
||||||
`docker-compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker --version
|
docker --version
|
||||||
docker-compose --version
|
docker compose version
|
||||||
|
|
||||||
The output should look something like:
|
The output should look something like:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
Docker version 20.10.5, build 55c4c88
|
Docker version 27.3.1, build ce12230
|
||||||
docker-compose version 1.29.0, build 07737305
|
Docker Compose version v2.29.7
|
||||||
|
|
||||||
.. note::
|
.. 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:
|
Follow these installation guides:
|
||||||
|
|
||||||
- `Install Docker <https://docs.docker.com/get-docker/>`_
|
- `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.
|
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
|
pytest-cov
|
||||||
|
|
||||||
Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and
|
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.9-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:
|
Put next lines into the ``Dockerfile`` file:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
FROM python:3.10-buster
|
FROM python:3.13-bookworm
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
@ -155,8 +154,6 @@ Put next lines into the ``docker-compose.yml`` file:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
version: "3.7"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
monitor:
|
monitor:
|
||||||
|
@ -171,7 +168,7 @@ Run in the terminal:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. 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:
|
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
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose up
|
docker compose up
|
||||||
|
|
||||||
The output should look like:
|
The output should look like:
|
||||||
|
|
||||||
|
@ -461,7 +458,7 @@ Run in the terminal:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose up
|
docker compose up
|
||||||
|
|
||||||
The output should look like:
|
The output should look like:
|
||||||
|
|
||||||
|
@ -705,7 +702,7 @@ Run in the terminal:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose up
|
docker compose up
|
||||||
|
|
||||||
You should see:
|
You should see:
|
||||||
|
|
||||||
|
@ -813,7 +810,7 @@ Run in the terminal:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose up
|
docker compose up
|
||||||
|
|
||||||
You should see:
|
You should see:
|
||||||
|
|
||||||
|
@ -965,15 +962,16 @@ Run in the terminal:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. 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:
|
You should see:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||||
rootdir: /code
|
rootdir: /code
|
||||||
plugins: asyncio-0.16.0, cov-3.0.0
|
plugins: cov-6.0.0, asyncio-0.24.0
|
||||||
|
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||||
collected 2 items
|
collected 2 items
|
||||||
|
|
||||||
monitoringdaemon/tests.py .. [100%]
|
monitoringdaemon/tests.py .. [100%]
|
||||||
|
@ -1028,4 +1026,6 @@ What's next?
|
||||||
- Know more about the :ref:`providers`
|
- Know more about the :ref:`providers`
|
||||||
- Go to the :ref:`contents`
|
- Go to the :ref:`contents`
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -84,7 +84,7 @@ Create next structure in the project root directory. All files are empty. That's
|
||||||
|
|
||||||
Initial project layout:
|
Initial project layout:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
|
|
||||||
./
|
./
|
||||||
├── movies/
|
├── movies/
|
||||||
|
@ -109,7 +109,7 @@ Now it's time to install the project requirements. We will use next packages:
|
||||||
|
|
||||||
Put next lines into the ``requirements.txt`` file:
|
Put next lines into the ``requirements.txt`` file:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
|
|
||||||
dependency-injector
|
dependency-injector
|
||||||
pyyaml
|
pyyaml
|
||||||
|
@ -134,7 +134,7 @@ We will create a script that creates database files.
|
||||||
First add the folder ``data/`` in the root of the project and then add the file
|
First add the folder ``data/`` in the root of the project and then add the file
|
||||||
``fixtures.py`` inside of it:
|
``fixtures.py`` inside of it:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
:emphasize-lines: 2-3
|
:emphasize-lines: 2-3
|
||||||
|
|
||||||
./
|
./
|
||||||
|
@ -205,13 +205,13 @@ Now run in the terminal:
|
||||||
|
|
||||||
You should see:
|
You should see:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
|
|
||||||
OK
|
OK
|
||||||
|
|
||||||
Check that files ``movies.csv`` and ``movies.db`` have appeared in the ``data/`` folder:
|
Check that files ``movies.csv`` and ``movies.db`` have appeared in the ``data/`` folder:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
:emphasize-lines: 4-5
|
:emphasize-lines: 4-5
|
||||||
|
|
||||||
./
|
./
|
||||||
|
@ -289,7 +289,7 @@ After each step we will add the provider to the container.
|
||||||
|
|
||||||
Create the ``entities.py`` in the ``movies`` package:
|
Create the ``entities.py`` in the ``movies`` package:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
:emphasize-lines: 10
|
:emphasize-lines: 10
|
||||||
|
|
||||||
./
|
./
|
||||||
|
@ -356,7 +356,7 @@ Let's move on to the finders.
|
||||||
|
|
||||||
Create the ``finders.py`` in the ``movies`` package:
|
Create the ``finders.py`` in the ``movies`` package:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
:emphasize-lines: 11
|
:emphasize-lines: 11
|
||||||
|
|
||||||
./
|
./
|
||||||
|
@ -465,7 +465,7 @@ The configuration file is ready. Move on to the lister.
|
||||||
|
|
||||||
Create the ``listers.py`` in the ``movies`` package:
|
Create the ``listers.py`` in the ``movies`` package:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
:emphasize-lines: 12
|
:emphasize-lines: 12
|
||||||
|
|
||||||
./
|
./
|
||||||
|
@ -613,7 +613,7 @@ Run in the terminal:
|
||||||
|
|
||||||
You should see:
|
You should see:
|
||||||
|
|
||||||
.. code-block:: plain
|
.. code-block:: text
|
||||||
|
|
||||||
Francis Lawrence movies:
|
Francis Lawrence movies:
|
||||||
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
|
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
|
||||||
|
@ -752,7 +752,7 @@ Run in the terminal:
|
||||||
|
|
||||||
You should see:
|
You should see:
|
||||||
|
|
||||||
.. code-block:: plain
|
.. code-block:: text
|
||||||
|
|
||||||
Francis Lawrence movies:
|
Francis Lawrence movies:
|
||||||
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
|
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
|
||||||
|
@ -868,7 +868,7 @@ Run in the terminal line by line:
|
||||||
|
|
||||||
The output should be similar for each command:
|
The output should be similar for each command:
|
||||||
|
|
||||||
.. code-block:: plain
|
.. code-block:: text
|
||||||
|
|
||||||
Francis Lawrence movies:
|
Francis Lawrence movies:
|
||||||
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
|
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
|
||||||
|
@ -888,7 +888,7 @@ We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
|
||||||
|
|
||||||
Create ``tests.py`` in the ``movies`` package:
|
Create ``tests.py`` in the ``movies`` package:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
:emphasize-lines: 13
|
:emphasize-lines: 13
|
||||||
|
|
||||||
./
|
./
|
||||||
|
@ -911,7 +911,7 @@ Create ``tests.py`` in the ``movies`` package:
|
||||||
and put next into it:
|
and put next into it:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:emphasize-lines: 36,51
|
:emphasize-lines: 41,50
|
||||||
|
|
||||||
"""Tests module."""
|
"""Tests module."""
|
||||||
|
|
||||||
|
@ -941,13 +941,18 @@ and put next into it:
|
||||||
return container
|
return container
|
||||||
|
|
||||||
|
|
||||||
def test_movies_directed_by(container):
|
@pytest.fixture
|
||||||
|
def finder_mock(container):
|
||||||
finder_mock = mock.Mock()
|
finder_mock = mock.Mock()
|
||||||
finder_mock.find_all.return_value = [
|
finder_mock.find_all.return_value = [
|
||||||
container.movie("The 33", 2015, "Patricia Riggen"),
|
container.movie("The 33", 2015, "Patricia Riggen"),
|
||||||
container.movie("The Jungle Book", 2016, "Jon Favreau"),
|
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):
|
with container.finder.override(finder_mock):
|
||||||
lister = container.lister()
|
lister = container.lister()
|
||||||
movies = lister.movies_directed_by("Jon Favreau")
|
movies = lister.movies_directed_by("Jon Favreau")
|
||||||
|
@ -956,13 +961,7 @@ and put next into it:
|
||||||
assert movies[0].title == "The Jungle Book"
|
assert movies[0].title == "The Jungle Book"
|
||||||
|
|
||||||
|
|
||||||
def test_movies_released_in(container):
|
def test_movies_released_in(container, finder_mock):
|
||||||
finder_mock = mock.Mock()
|
|
||||||
finder_mock.find_all.return_value = [
|
|
||||||
container.movie("The 33", 2015, "Patricia Riggen"),
|
|
||||||
container.movie("The Jungle Book", 2016, "Jon Favreau"),
|
|
||||||
]
|
|
||||||
|
|
||||||
with container.finder.override(finder_mock):
|
with container.finder.override(finder_mock):
|
||||||
lister = container.lister()
|
lister = container.lister()
|
||||||
movies = lister.movies_released_in(2015)
|
movies = lister.movies_released_in(2015)
|
||||||
|
@ -978,7 +977,7 @@ Run in the terminal:
|
||||||
|
|
||||||
You should see:
|
You should see:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block:: text
|
||||||
|
|
||||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||||
plugins: cov-3.0.0
|
plugins: cov-3.0.0
|
||||||
|
@ -995,9 +994,9 @@ You should see:
|
||||||
movies/entities.py 7 1 86%
|
movies/entities.py 7 1 86%
|
||||||
movies/finders.py 26 13 50%
|
movies/finders.py 26 13 50%
|
||||||
movies/listers.py 8 0 100%
|
movies/listers.py 8 0 100%
|
||||||
movies/tests.py 23 0 100%
|
movies/tests.py 24 0 100%
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
TOTAL 89 30 66%
|
TOTAL 90 30 67%
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -1034,4 +1033,6 @@ What's next?
|
||||||
- Know more about the :ref:`providers`
|
- Know more about the :ref:`providers`
|
||||||
- Go to the :ref:`contents`
|
- Go to the :ref:`contents`
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -280,7 +280,7 @@ Now let's fill in the layout.
|
||||||
|
|
||||||
Put next into the ``base.html``:
|
Put next into the ``base.html``:
|
||||||
|
|
||||||
.. code-block:: html
|
.. code-block:: jinja
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -313,7 +313,7 @@ And put something to the index page.
|
||||||
|
|
||||||
Put next into the ``index.html``:
|
Put next into the ``index.html``:
|
||||||
|
|
||||||
.. code-block:: html
|
.. code-block:: jinja
|
||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
@ -998,5 +998,6 @@ What's next?
|
||||||
- Know more about the :ref:`providers`
|
- Know more about the :ref:`providers`
|
||||||
- Go to the :ref:`contents`
|
- Go to the :ref:`contents`
|
||||||
|
|
||||||
|
.. include:: ../sponsor.rst
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
140
docs/wiring.rst
140
docs/wiring.rst
|
@ -22,6 +22,82 @@ To use wiring you need:
|
||||||
:local:
|
:local:
|
||||||
:backlinks: none
|
: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
|
Markers
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -51,6 +127,7 @@ To inject the provider itself use ``Provide[foo.provider]``:
|
||||||
def foo(bar_provider: Factory[Bar] = Provide[Container.bar.provider]):
|
def foo(bar_provider: Factory[Bar] = Provide[Container.bar.provider]):
|
||||||
bar = bar_provider(argument="baz")
|
bar = bar_provider(argument="baz")
|
||||||
...
|
...
|
||||||
|
|
||||||
You can also use ``Provider[foo]`` for injecting the provider itself:
|
You can also use ``Provider[foo]`` for injecting the provider itself:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -178,13 +255,43 @@ To inject a container use special identifier ``<container>``:
|
||||||
Making injections into modules and class attributes
|
Making injections into modules and class attributes
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
You can use wiring to make injections into modules and class attributes.
|
You can use wiring to make injections into modules and class attributes. Both the classic marker
|
||||||
|
syntax and the ``Annotated`` form are supported.
|
||||||
|
|
||||||
|
Classic marker syntax:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
service: Service = Provide[Container.service]
|
||||||
|
|
||||||
|
class Main:
|
||||||
|
service: Service = Provide[Container.service]
|
||||||
|
|
||||||
|
Full example of the classic marker syntax:
|
||||||
|
|
||||||
.. literalinclude:: ../examples/wiring/example_attribute.py
|
.. literalinclude:: ../examples/wiring/example_attribute.py
|
||||||
:language: python
|
:language: python
|
||||||
:lines: 3-
|
:lines: 3-
|
||||||
:emphasize-lines: 14,19
|
:emphasize-lines: 14,19
|
||||||
|
|
||||||
|
Annotated form (Python 3.9+):
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
|
||||||
|
class Main:
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
|
||||||
|
Full example of the annotated form:
|
||||||
|
|
||||||
|
.. literalinclude:: ../examples/wiring/example_attribute_annotated.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 16,21
|
||||||
|
|
||||||
You could also use string identifiers to avoid a dependency on a container:
|
You could also use string identifiers to avoid a dependency on a container:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -525,6 +632,36 @@ or with a single container ``register_loader_containers(container)`` multiple ti
|
||||||
To unregister a container use ``unregister_loader_containers(container)``.
|
To unregister a container use ``unregister_loader_containers(container)``.
|
||||||
Wiring module will uninstall the import hook when unregister last container.
|
Wiring module will uninstall the import hook when unregister last container.
|
||||||
|
|
||||||
|
Few notes on performance
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
``.wire()`` utilize caching to speed up the wiring process. At the end it clears the cache to avoid memory leaks.
|
||||||
|
But this may not always be desirable, when you want to keep the cache for the next wiring
|
||||||
|
(e.g. due to usage of multiple containers or during unit tests).
|
||||||
|
|
||||||
|
To keep the cache after wiring, you can set flag ``keep_cache=True`` (works with ``WiringConfiguration`` too):
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
container1.wire(
|
||||||
|
modules=["yourapp.module1", "yourapp.module2"],
|
||||||
|
keep_cache=True,
|
||||||
|
)
|
||||||
|
container2.wire(
|
||||||
|
modules=["yourapp.module2", "yourapp.module3"],
|
||||||
|
keep_cache=True,
|
||||||
|
)
|
||||||
|
...
|
||||||
|
|
||||||
|
and then clear it manually when you need it:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from dependency_injector.wiring import clear_cache
|
||||||
|
|
||||||
|
clear_cache()
|
||||||
|
|
||||||
|
|
||||||
Integration with other frameworks
|
Integration with other frameworks
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
@ -556,5 +693,6 @@ Take a look at other application examples:
|
||||||
- :ref:`fastapi-example`
|
- :ref:`fastapi-example`
|
||||||
- :ref:`fastapi-redis-example`
|
- :ref:`fastapi-redis-example`
|
||||||
- :ref:`fastapi-sqlalchemy-example`
|
- :ref:`fastapi-sqlalchemy-example`
|
||||||
|
- :ref:`fastdepends-example`
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -3,18 +3,18 @@ import os
|
||||||
|
|
||||||
class ApiClient:
|
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.api_key = api_key # <-- dependency is injected
|
||||||
self.timeout = timeout # <-- dependency is injected
|
self.timeout = timeout # <-- dependency is injected
|
||||||
|
|
||||||
|
|
||||||
class Service:
|
class Service:
|
||||||
|
|
||||||
def __init__(self, api_client: ApiClient):
|
def __init__(self, api_client: ApiClient) -> None:
|
||||||
self.api_client = api_client # <-- dependency is injected
|
self.api_client = api_client # <-- dependency is injected
|
||||||
|
|
||||||
|
|
||||||
def main(service: Service): # <-- dependency is injected
|
def main(service: Service) -> None: # <-- dependency is injected
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ if __name__ == "__main__":
|
||||||
service=Service(
|
service=Service(
|
||||||
api_client=ApiClient(
|
api_client=ApiClient(
|
||||||
api_key=os.getenv("API_KEY"),
|
api_key=os.getenv("API_KEY"),
|
||||||
timeout=os.getenv("TIMEOUT"),
|
timeout=int(os.getenv("TIMEOUT")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,7 +23,7 @@ class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
|
||||||
@inject
|
@inject
|
||||||
def main(service: Service = Provide[Container.service]):
|
def main(service: Service = Provide[Container.service]) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -98,8 +98,9 @@ The output should be something like:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||||
plugins: asyncio-0.16.0, anyio-3.3.4, aiohttp-0.3.0, cov-3.0.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
|
collected 3 items
|
||||||
|
|
||||||
giphynavigator/tests.py ... [100%]
|
giphynavigator/tests.py ... [100%]
|
||||||
|
|
|
@ -3,11 +3,15 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import pytest_asyncio
|
||||||
|
|
||||||
from giphynavigator.application import create_app
|
from giphynavigator.application import create_app
|
||||||
from giphynavigator.giphy import GiphyClient
|
from giphynavigator.giphy import GiphyClient
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.asyncio
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app():
|
def app():
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
@ -15,9 +19,9 @@ def app():
|
||||||
app.container.unwire()
|
app.container.unwire()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest_asyncio.fixture
|
||||||
def client(app, aiohttp_client, loop):
|
async def client(app, aiohttp_client):
|
||||||
return loop.run_until_complete(aiohttp_client(app))
|
return await aiohttp_client(app)
|
||||||
|
|
||||||
|
|
||||||
async def test_index(client, app):
|
async def test_index(client, app):
|
||||||
|
|
|
@ -2,4 +2,5 @@ dependency-injector
|
||||||
aiohttp
|
aiohttp
|
||||||
pyyaml
|
pyyaml
|
||||||
pytest-aiohttp
|
pytest-aiohttp
|
||||||
|
pytest-asyncio
|
||||||
pytest-cov
|
pytest-cov
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.10-buster
|
FROM python:3.13-bookworm
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,13 @@ Build the Docker image:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose build
|
docker compose build
|
||||||
|
|
||||||
Run the docker-compose environment:
|
Run the docker-compose environment:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose up
|
docker compose up
|
||||||
|
|
||||||
The output should be something like:
|
The output should be something like:
|
||||||
|
|
||||||
|
@ -59,15 +59,16 @@ To run the tests do:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. 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:
|
The output should be something like:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||||
rootdir: /code
|
rootdir: /code
|
||||||
plugins: asyncio-0.16.0, cov-3.0.0
|
plugins: cov-6.0.0, asyncio-0.24.0
|
||||||
|
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||||
collected 2 items
|
collected 2 items
|
||||||
|
|
||||||
monitoringdaemon/tests.py .. [100%]
|
monitoringdaemon/tests.py .. [100%]
|
||||||
|
|
|
@ -61,7 +61,7 @@ async def test_example_monitor(container, caplog):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatcher(container, caplog, event_loop):
|
async def test_dispatcher(container, caplog):
|
||||||
caplog.set_level("INFO")
|
caplog.set_level("INFO")
|
||||||
|
|
||||||
example_monitor_mock = mock.AsyncMock()
|
example_monitor_mock = mock.AsyncMock()
|
||||||
|
@ -72,6 +72,7 @@ async def test_dispatcher(container, caplog, event_loop):
|
||||||
httpbin_monitor=httpbin_monitor_mock,
|
httpbin_monitor=httpbin_monitor_mock,
|
||||||
):
|
):
|
||||||
dispatcher = container.dispatcher()
|
dispatcher = container.dispatcher()
|
||||||
|
event_loop = asyncio.get_running_loop()
|
||||||
event_loop.create_task(dispatcher.start())
|
event_loop.create_task(dispatcher.start())
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
dispatcher.stop()
|
dispatcher.stop()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.9-buster
|
FROM python:3.13-bookworm
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
|
|
@ -12,47 +12,38 @@ Build the Docker image:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose build
|
docker compose build
|
||||||
|
|
||||||
Run the docker-compose environment:
|
Run the docker-compose environment:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose up
|
docker compose up
|
||||||
|
|
||||||
The output should be something like:
|
The output should be something like:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
redis_1 | 1:C 04 Jan 2021 02:42:14.115 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
|
fastapi-redis-redis-1 | 1:C 19 Dec 2022 02:33:02.484 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
|
||||||
redis_1 | 1:C 04 Jan 2021 02:42:14.115 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
|
fastapi-redis-redis-1 | 1:C 19 Dec 2022 02:33:02.484 # Redis version=7.0.5, bits=64, commit=00000000, modified=0, pid=1, just started
|
||||||
redis_1 | 1:C 04 Jan 2021 02:42:14.115 # Configuration loaded
|
fastapi-redis-redis-1 | 1:C 19 Dec 2022 02:33:02.484 # Configuration loaded
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:14.116 * Running mode=standalone, port=6379.
|
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.485 * monotonic clock: POSIX clock_gettime
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:14.116 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
|
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.485 * Running mode=standalone, port=6379.
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:14.116 # Server initialized
|
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.485 # Server initialized
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:14.117 * Loading RDB produced by version 6.0.9
|
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.487 * Loading RDB produced by version 7.0.5
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:14.117 * RDB age 1 seconds
|
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.487 * RDB age 58 seconds
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:14.117 * RDB memory usage when created 0.77 Mb
|
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.487 * RDB memory usage when created 0.85 Mb
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:14.117 * DB loaded from disk: 0.000 seconds
|
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.487 * Done loading RDB, keys loaded: 0, keys expired: 0.
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:14.117 * Ready to accept connections
|
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.487 * DB loaded from disk: 0.000 seconds
|
||||||
redis_1 | 1:signal-handler (1609728137) Received SIGTERM scheduling shutdown...
|
fastapi-redis-redis-1 | 1:M 19 Dec 2022 02:33:02.488 * Ready to accept connections
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:17.984 # User requested shutdown...
|
fastapi-redis-example-1 | INFO: Started server process [1]
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:17.984 # Redis is now ready to exit, bye bye...
|
fastapi-redis-example-1 | INFO: Waiting for application startup.
|
||||||
redis_1 | 1:C 04 Jan 2021 02:42:22.035 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
|
fastapi-redis-example-1 | INFO: Application startup complete.
|
||||||
redis_1 | 1:C 04 Jan 2021 02:42:22.035 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
|
fastapi-redis-example-1 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
|
||||||
redis_1 | 1:C 04 Jan 2021 02:42:22.035 # Configuration loaded
|
fastapi-redis-example-1 | INFO: 172.18.0.1:63998 - "GET / HTTP/1.1" 200 OK
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * Running mode=standalone, port=6379.
|
fastapi-redis-example-1 | INFO: 172.18.0.1:63998 - "GET /favicon.ico HTTP/1.1" 404 Not Found
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:22.037 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
|
fastapi-redis-example-1 | INFO: 172.18.0.1:63998 - "GET / HTTP/1.1" 200 OK
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:22.037 # Server initialized
|
fastapi-redis-example-1 | INFO: 172.18.0.1:63998 - "GET / HTTP/1.1" 200 OK
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * Loading RDB produced by version 6.0.9
|
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * RDB age 9 seconds
|
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * RDB memory usage when created 0.77 Mb
|
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * DB loaded from disk: 0.000 seconds
|
|
||||||
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * Ready to accept connections
|
|
||||||
example_1 | INFO: Started server process [1]
|
|
||||||
example_1 | INFO: Waiting for application startup.
|
|
||||||
example_1 | INFO: Application startup complete.
|
|
||||||
example_1 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
|
|
||||||
|
|
||||||
Test
|
Test
|
||||||
----
|
----
|
||||||
|
@ -63,16 +54,16 @@ To run the tests do:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose run --rm example py.test fastapiredis/tests.py --cov=fastapiredis
|
docker compose run --rm example py.test fastapiredis/tests.py --cov=fastapiredis
|
||||||
|
|
||||||
The output should be something like:
|
The output should be something like:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
platform linux -- Python 3.9, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
|
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||||
rootdir: /code
|
rootdir: /code
|
||||||
plugins: cov-2.10.1, asyncio-0.14.0
|
plugins: cov-6.0.0, asyncio-0.24.0, anyio-4.7.0
|
||||||
collected 1 item
|
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||||
|
|
||||||
fastapiredis/tests.py . [100%]
|
fastapiredis/tests.py . [100%]
|
||||||
|
|
||||||
|
@ -80,10 +71,10 @@ The output should be something like:
|
||||||
Name Stmts Miss Cover
|
Name Stmts Miss Cover
|
||||||
-------------------------------------------------
|
-------------------------------------------------
|
||||||
fastapiredis/__init__.py 0 0 100%
|
fastapiredis/__init__.py 0 0 100%
|
||||||
fastapiredis/application.py 15 0 100%
|
fastapiredis/application.py 14 0 100%
|
||||||
fastapiredis/containers.py 6 0 100%
|
fastapiredis/containers.py 6 0 100%
|
||||||
fastapiredis/redis.py 7 4 43%
|
fastapiredis/redis.py 7 4 43%
|
||||||
fastapiredis/services.py 7 3 57%
|
fastapiredis/services.py 7 3 57%
|
||||||
fastapiredis/tests.py 18 0 100%
|
fastapiredis/tests.py 18 0 100%
|
||||||
-------------------------------------------------
|
-------------------------------------------------
|
||||||
TOTAL 53 7 87%
|
TOTAL 52 7 87%
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
"""Application module."""
|
"""Application module."""
|
||||||
|
|
||||||
from dependency_injector.wiring import inject, Provide
|
from typing import Annotated
|
||||||
from fastapi import FastAPI, Depends
|
|
||||||
|
from fastapi import Depends, FastAPI
|
||||||
|
|
||||||
|
from dependency_injector.wiring import Provide, inject
|
||||||
|
|
||||||
from .containers import Container
|
from .containers import Container
|
||||||
from .services import Service
|
from .services import Service
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
@app.api_route("/")
|
@app.api_route("/")
|
||||||
@inject
|
@inject
|
||||||
async def index(service: Service = Depends(Provide[Container.service])):
|
async def index(
|
||||||
|
service: Annotated[Service, Depends(Provide[Container.service])]
|
||||||
|
) -> dict[str, str]:
|
||||||
value = await service.process()
|
value = await service.process()
|
||||||
return {"result": value}
|
return {"result": value}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
"""Redis client module."""
|
|
||||||
|
|
||||||
from typing import AsyncIterator
|
from typing import AsyncIterator
|
||||||
|
|
||||||
from aioredis import create_redis_pool, Redis
|
from redis.asyncio import from_url, Redis
|
||||||
|
|
||||||
|
|
||||||
async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]:
|
async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]:
|
||||||
pool = await create_redis_pool(f"redis://{host}", password=password)
|
session = from_url(f"redis://{host}", password=password, encoding="utf-8", decode_responses=True)
|
||||||
yield pool
|
yield session
|
||||||
pool.close()
|
session.close()
|
||||||
await pool.wait_closed()
|
await session.wait_closed()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Services module."""
|
"""Services module."""
|
||||||
|
|
||||||
from aioredis import Redis
|
from redis.asyncio import Redis
|
||||||
|
|
||||||
|
|
||||||
class Service:
|
class Service:
|
||||||
|
@ -9,4 +9,4 @@ class Service:
|
||||||
|
|
||||||
async def process(self) -> str:
|
async def process(self) -> str:
|
||||||
await self._redis.set("my-key", "value")
|
await self._redis.set("my-key", "value")
|
||||||
return await self._redis.get("my-key", encoding="utf-8")
|
return await self._redis.get("my-key")
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from httpx import AsyncClient
|
from httpx import ASGITransport, AsyncClient
|
||||||
|
|
||||||
from .application import app, container
|
from .application import app, container
|
||||||
from .services import Service
|
from .services import Service
|
||||||
|
@ -11,7 +11,10 @@ from .services import Service
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client(event_loop):
|
def client(event_loop):
|
||||||
client = AsyncClient(app=app, base_url="http://test")
|
client = AsyncClient(
|
||||||
|
transport=ASGITransport(app=app),
|
||||||
|
base_url="http://test",
|
||||||
|
)
|
||||||
yield client
|
yield client
|
||||||
event_loop.run_until_complete(client.aclose())
|
event_loop.run_until_complete(client.aclose())
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
dependency-injector
|
dependency-injector
|
||||||
fastapi
|
fastapi
|
||||||
uvicorn
|
uvicorn
|
||||||
aioredis<2 # TODO: Update example to work with aioredis >= 2.0
|
redis>=4.2
|
||||||
|
|
||||||
# For testing:
|
# For testing:
|
||||||
pytest
|
pytest
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
from fastapi import FastAPI, Depends
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends, FastAPI
|
||||||
|
|
||||||
from dependency_injector import containers, providers
|
from dependency_injector import containers, providers
|
||||||
from dependency_injector.wiring import Provide, inject
|
from dependency_injector.wiring import Provide, inject
|
||||||
|
|
||||||
|
@ -18,7 +21,9 @@ app = FastAPI()
|
||||||
|
|
||||||
@app.api_route("/")
|
@app.api_route("/")
|
||||||
@inject
|
@inject
|
||||||
async def index(service: Service = Depends(Provide[Container.service])):
|
async def index(
|
||||||
|
service: Annotated[Service, Depends(Provide[Container.service])]
|
||||||
|
) -> dict[str, str]:
|
||||||
result = await service.process()
|
result = await service.process()
|
||||||
return {"result": result}
|
return {"result": result}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from httpx import AsyncClient
|
import pytest_asyncio
|
||||||
|
from httpx import ASGITransport, AsyncClient
|
||||||
|
|
||||||
from fastapi_di_example import app, container, Service
|
from fastapi_di_example import app, container, Service
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest_asyncio.fixture
|
||||||
async def client(event_loop):
|
async def client():
|
||||||
async with AsyncClient(app=app, base_url="http://test") as client:
|
async with AsyncClient(
|
||||||
|
transport=ASGITransport(app=app),
|
||||||
|
base_url="http://test",
|
||||||
|
) as client:
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.10-buster
|
FROM python:3.13-bookworm
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
|
|
|
@ -15,13 +15,13 @@ Build the Docker image:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose build
|
docker compose build
|
||||||
|
|
||||||
Run the docker-compose environment:
|
Run the docker-compose environment:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose up
|
docker compose up
|
||||||
|
|
||||||
The output should be something like:
|
The output should be something like:
|
||||||
|
|
||||||
|
@ -29,15 +29,15 @@ The output should be something like:
|
||||||
|
|
||||||
Starting fastapi-sqlalchemy_webapp_1 ... done
|
Starting fastapi-sqlalchemy_webapp_1 ... done
|
||||||
Attaching to fastapi-sqlalchemy_webapp_1
|
Attaching to fastapi-sqlalchemy_webapp_1
|
||||||
webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
|
webapp_1 | 2022-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
|
||||||
webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine ()
|
webapp_1 | 2022-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine ()
|
||||||
webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
|
webapp_1 | 2022-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
|
||||||
webapp_1 | 2021-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine ()
|
webapp_1 | 2022-02-04 22:07:19,804 INFO sqlalchemy.engine.base.Engine ()
|
||||||
webapp_1 | 2021-02-04 22:07:19,805 INFO sqlalchemy.engine.base.Engine PRAGMA main.table_info("users")
|
webapp_1 | 2022-02-04 22:07:19,805 INFO sqlalchemy.engine.base.Engine PRAGMA main.table_info("users")
|
||||||
webapp_1 | 2021-02-04 22:07:19,805 INFO sqlalchemy.engine.base.Engine ()
|
webapp_1 | 2022-02-04 22:07:19,805 INFO sqlalchemy.engine.base.Engine ()
|
||||||
webapp_1 | 2021-02-04 22:07:19,808 INFO sqlalchemy.engine.base.Engine PRAGMA temp.table_info("users")
|
webapp_1 | 2022-02-04 22:07:19,808 INFO sqlalchemy.engine.base.Engine PRAGMA temp.table_info("users")
|
||||||
webapp_1 | 2021-02-04 22:07:19,808 INFO sqlalchemy.engine.base.Engine ()
|
webapp_1 | 2022-02-04 22:07:19,808 INFO sqlalchemy.engine.base.Engine ()
|
||||||
webapp_1 | 2021-02-04 22:07:19,809 INFO sqlalchemy.engine.base.Engine
|
webapp_1 | 2022-02-04 22:07:19,809 INFO sqlalchemy.engine.base.Engine
|
||||||
webapp_1 | CREATE TABLE users (
|
webapp_1 | CREATE TABLE users (
|
||||||
webapp_1 | id INTEGER NOT NULL,
|
webapp_1 | id INTEGER NOT NULL,
|
||||||
webapp_1 | email VARCHAR,
|
webapp_1 | email VARCHAR,
|
||||||
|
@ -49,8 +49,8 @@ The output should be something like:
|
||||||
webapp_1 | )
|
webapp_1 | )
|
||||||
webapp_1 |
|
webapp_1 |
|
||||||
webapp_1 |
|
webapp_1 |
|
||||||
webapp_1 | 2021-02-04 22:07:19,810 INFO sqlalchemy.engine.base.Engine ()
|
webapp_1 | 2022-02-04 22:07:19,810 INFO sqlalchemy.engine.base.Engine ()
|
||||||
webapp_1 | 2021-02-04 22:07:19,821 INFO sqlalchemy.engine.base.Engine COMMIT
|
webapp_1 | 2022-02-04 22:07:19,821 INFO sqlalchemy.engine.base.Engine COMMIT
|
||||||
webapp_1 | INFO: Started server process [8]
|
webapp_1 | INFO: Started server process [8]
|
||||||
webapp_1 | INFO: Waiting for application startup.
|
webapp_1 | INFO: Waiting for application startup.
|
||||||
webapp_1 | INFO: Application startup complete.
|
webapp_1 | INFO: Application startup complete.
|
||||||
|
@ -67,15 +67,15 @@ To run the tests do:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
docker-compose run --rm webapp py.test webapp/tests.py --cov=webapp
|
docker compose run --rm webapp py.test webapp/tests.py --cov=webapp
|
||||||
|
|
||||||
The output should be something like:
|
The output should be something like:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||||
rootdir: /code
|
rootdir: /code
|
||||||
plugins: cov-3.0.0
|
plugins: cov-6.0.0, anyio-4.7.0
|
||||||
collected 7 items
|
collected 7 items
|
||||||
|
|
||||||
webapp/tests.py ....... [100%]
|
webapp/tests.py ....... [100%]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
dependency-injector
|
dependency-injector
|
||||||
fastapi
|
fastapi[standard]
|
||||||
uvicorn
|
uvicorn
|
||||||
pyyaml
|
pyyaml
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
"""Endpoints module."""
|
"""Endpoints module."""
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Response, status
|
from fastapi import APIRouter, Depends, Response, status
|
||||||
from dependency_injector.wiring import inject, Provide
|
|
||||||
|
from dependency_injector.wiring import Provide, inject
|
||||||
|
|
||||||
from .containers import Container
|
from .containers import Container
|
||||||
from .services import UserService
|
|
||||||
from .repositories import NotFoundError
|
from .repositories import NotFoundError
|
||||||
|
from .services import UserService
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
@ -13,7 +16,7 @@ router = APIRouter()
|
||||||
@router.get("/users")
|
@router.get("/users")
|
||||||
@inject
|
@inject
|
||||||
def get_list(
|
def get_list(
|
||||||
user_service: UserService = Depends(Provide[Container.user_service]),
|
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
|
||||||
):
|
):
|
||||||
return user_service.get_users()
|
return user_service.get_users()
|
||||||
|
|
||||||
|
@ -21,8 +24,8 @@ def get_list(
|
||||||
@router.get("/users/{user_id}")
|
@router.get("/users/{user_id}")
|
||||||
@inject
|
@inject
|
||||||
def get_by_id(
|
def get_by_id(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
user_service: UserService = Depends(Provide[Container.user_service]),
|
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return user_service.get_user_by_id(user_id)
|
return user_service.get_user_by_id(user_id)
|
||||||
|
@ -33,7 +36,7 @@ def get_by_id(
|
||||||
@router.post("/users", status_code=status.HTTP_201_CREATED)
|
@router.post("/users", status_code=status.HTTP_201_CREATED)
|
||||||
@inject
|
@inject
|
||||||
def add(
|
def add(
|
||||||
user_service: UserService = Depends(Provide[Container.user_service]),
|
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
|
||||||
):
|
):
|
||||||
return user_service.create_user()
|
return user_service.create_user()
|
||||||
|
|
||||||
|
@ -41,9 +44,9 @@ def add(
|
||||||
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
@inject
|
@inject
|
||||||
def remove(
|
def remove(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
user_service: UserService = Depends(Provide[Container.user_service]),
|
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
|
||||||
):
|
) -> Response:
|
||||||
try:
|
try:
|
||||||
user_service.delete_user_by_id(user_id)
|
user_service.delete_user_by_id(user_id)
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
|
|
|
@ -101,9 +101,9 @@ The output should be something like:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||||
plugins: asyncio-0.16.0, cov-3.0.0
|
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0, aiohttp-1.0.5
|
||||||
collected 3 items
|
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||||
|
|
||||||
giphynavigator/tests.py ... [100%]
|
giphynavigator/tests.py ... [100%]
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
"""Endpoints module."""
|
"""Endpoints module."""
|
||||||
|
|
||||||
from typing import Optional, List
|
from typing import Annotated, List
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from dependency_injector.wiring import inject, Provide
|
|
||||||
|
|
||||||
from .services import SearchService
|
from dependency_injector.wiring import Provide, inject
|
||||||
|
|
||||||
from .containers import Container
|
from .containers import Container
|
||||||
|
from .services import SearchService
|
||||||
|
|
||||||
|
|
||||||
class Gif(BaseModel):
|
class Gif(BaseModel):
|
||||||
|
@ -26,11 +27,15 @@ router = APIRouter()
|
||||||
@router.get("/", response_model=Response)
|
@router.get("/", response_model=Response)
|
||||||
@inject
|
@inject
|
||||||
async def index(
|
async def index(
|
||||||
query: Optional[str] = None,
|
default_query: Annotated[str, Depends(Provide[Container.config.default.query])],
|
||||||
limit: Optional[str] = None,
|
default_limit: Annotated[
|
||||||
default_query: str = Depends(Provide[Container.config.default.query]),
|
int, Depends(Provide[Container.config.default.limit.as_int()])
|
||||||
default_limit: int = Depends(Provide[Container.config.default.limit.as_int()]),
|
],
|
||||||
search_service: SearchService = Depends(Provide[Container.search_service]),
|
search_service: Annotated[
|
||||||
|
SearchService, Depends(Provide[Container.search_service])
|
||||||
|
],
|
||||||
|
query: str | None = None,
|
||||||
|
limit: int | None = None,
|
||||||
):
|
):
|
||||||
query = query or default_query
|
query = query or default_query
|
||||||
limit = limit or default_limit
|
limit = limit or default_limit
|
||||||
|
|
|
@ -3,15 +3,19 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from httpx import AsyncClient
|
import pytest_asyncio
|
||||||
|
from httpx import ASGITransport, AsyncClient
|
||||||
|
|
||||||
from giphynavigator.application import app
|
from giphynavigator.application import app
|
||||||
from giphynavigator.giphy import GiphyClient
|
from giphynavigator.giphy import GiphyClient
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest_asyncio.fixture
|
||||||
async def client():
|
async def client():
|
||||||
async with AsyncClient(app=app, base_url="http://test") as client:
|
async with AsyncClient(
|
||||||
|
transport=ASGITransport(app=app),
|
||||||
|
base_url="http://test",
|
||||||
|
) as client:
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -81,8 +81,9 @@ The output should be something like:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||||
plugins: cov-3.0.0, flask-1.2.0
|
plugins: cov-6.0.0, flask-1.3.0
|
||||||
|
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||||
collected 2 items
|
collected 2 items
|
||||||
|
|
||||||
githubnavigator/tests.py .. [100%]
|
githubnavigator/tests.py .. [100%]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Application module."""
|
"""Application module."""
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_bootstrap import Bootstrap
|
from flask_bootstrap import Bootstrap4
|
||||||
|
|
||||||
from .containers import Container
|
from .containers import Container
|
||||||
from .blueprints import example
|
from .blueprints import example
|
||||||
|
@ -15,7 +15,7 @@ def create_app() -> Flask:
|
||||||
app.container = container
|
app.container = container
|
||||||
app.register_blueprint(example.blueprint)
|
app.register_blueprint(example.blueprint)
|
||||||
|
|
||||||
bootstrap = Bootstrap()
|
bootstrap = Bootstrap4()
|
||||||
bootstrap.init_app(app)
|
bootstrap.init_app(app)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -81,8 +81,9 @@ The output should be something like:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||||
plugins: cov-3.0.0, flask-1.2.0
|
plugins: cov-6.0.0, flask-1.3.0
|
||||||
|
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||||
collected 2 items
|
collected 2 items
|
||||||
|
|
||||||
githubnavigator/tests.py .. [100%]
|
githubnavigator/tests.py .. [100%]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""Application module."""
|
"""Application module."""
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_bootstrap import Bootstrap
|
from flask_bootstrap import Bootstrap4
|
||||||
|
|
||||||
from .containers import Container
|
from .containers import Container
|
||||||
from . import views
|
from . import views
|
||||||
|
@ -15,7 +15,7 @@ def create_app() -> Flask:
|
||||||
app.container = container
|
app.container = container
|
||||||
app.add_url_rule("/", "index", views.index)
|
app.add_url_rule("/", "index", views.index)
|
||||||
|
|
||||||
bootstrap = Bootstrap()
|
bootstrap = Bootstrap4()
|
||||||
bootstrap.init_app(app)
|
bootstrap.init_app(app)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -58,8 +58,8 @@ The output should be something like:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||||
plugins: cov-3.0.0
|
plugins: cov-6.0.0
|
||||||
collected 2 items
|
collected 2 items
|
||||||
|
|
||||||
movies/tests.py .. [100%]
|
movies/tests.py .. [100%]
|
||||||
|
|
|
@ -18,10 +18,9 @@ SQLITE_FILE = DIR / "movies.db"
|
||||||
|
|
||||||
|
|
||||||
def create_csv(movies_data, path):
|
def create_csv(movies_data, path):
|
||||||
with open(path, "w") as opened_file:
|
with open(path, "w", newline="") as opened_file:
|
||||||
writer = csv.writer(opened_file)
|
writer = csv.writer(opened_file)
|
||||||
for row in movies_data:
|
writer.writerows(movies_data)
|
||||||
writer.writerow(row)
|
|
||||||
|
|
||||||
|
|
||||||
def create_sqlite(movies_data, path):
|
def create_sqlite(movies_data, path):
|
||||||
|
|
|
@ -29,7 +29,7 @@ class CsvMovieFinder(MovieFinder):
|
||||||
super().__init__(movie_factory)
|
super().__init__(movie_factory)
|
||||||
|
|
||||||
def find_all(self) -> List[Movie]:
|
def find_all(self) -> List[Movie]:
|
||||||
with open(self._csv_file_path) as csv_file:
|
with open(self._csv_file_path, newline="") as csv_file:
|
||||||
csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
|
csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
|
||||||
return [self._movie_factory(*row) for row in csv_reader]
|
return [self._movie_factory(*row) for row in csv_reader]
|
||||||
|
|
||||||
|
|
|
@ -26,13 +26,18 @@ def container():
|
||||||
return container
|
return container
|
||||||
|
|
||||||
|
|
||||||
def test_movies_directed_by(container):
|
@pytest.fixture
|
||||||
|
def finder_mock(container):
|
||||||
finder_mock = mock.Mock()
|
finder_mock = mock.Mock()
|
||||||
finder_mock.find_all.return_value = [
|
finder_mock.find_all.return_value = [
|
||||||
container.movie("The 33", 2015, "Patricia Riggen"),
|
container.movie("The 33", 2015, "Patricia Riggen"),
|
||||||
container.movie("The Jungle Book", 2016, "Jon Favreau"),
|
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):
|
with container.finder.override(finder_mock):
|
||||||
lister = container.lister()
|
lister = container.lister()
|
||||||
movies = lister.movies_directed_by("Jon Favreau")
|
movies = lister.movies_directed_by("Jon Favreau")
|
||||||
|
@ -41,13 +46,7 @@ def test_movies_directed_by(container):
|
||||||
assert movies[0].title == "The Jungle Book"
|
assert movies[0].title == "The Jungle Book"
|
||||||
|
|
||||||
|
|
||||||
def test_movies_released_in(container):
|
def test_movies_released_in(container, finder_mock):
|
||||||
finder_mock = mock.Mock()
|
|
||||||
finder_mock.find_all.return_value = [
|
|
||||||
container.movie("The 33", 2015, "Patricia Riggen"),
|
|
||||||
container.movie("The Jungle Book", 2016, "Jon Favreau"),
|
|
||||||
]
|
|
||||||
|
|
||||||
with container.finder.override(finder_mock):
|
with container.finder.override(finder_mock):
|
||||||
lister = container.lister()
|
lister = container.lister()
|
||||||
movies = lister.movies_released_in(2015)
|
movies = lister.movies_released_in(2015)
|
||||||
|
|
|
@ -27,7 +27,7 @@ To run the application do:
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0
|
export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0
|
||||||
python -m giphynavigator
|
sanic giphynavigator.application:create_app
|
||||||
|
|
||||||
The output should be something like:
|
The output should be something like:
|
||||||
|
|
||||||
|
@ -98,8 +98,9 @@ The output should be something like:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||||
plugins: sanic-1.9.1, anyio-3.3.4, cov-3.0.0
|
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0
|
||||||
|
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||||
collected 3 items
|
collected 3 items
|
||||||
|
|
||||||
giphynavigator/tests.py ... [100%]
|
giphynavigator/tests.py ... [100%]
|
||||||
|
|
|
@ -8,6 +8,8 @@ from sanic import Sanic
|
||||||
from giphynavigator.application import create_app
|
from giphynavigator.application import create_app
|
||||||
from giphynavigator.giphy import GiphyClient
|
from giphynavigator.giphy import GiphyClient
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.asyncio
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app():
|
def app():
|
||||||
|
@ -17,12 +19,7 @@ def app():
|
||||||
app.ctx.container.unwire()
|
app.ctx.container.unwire()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
async def test_index(app):
|
||||||
def test_client(loop, app, sanic_client):
|
|
||||||
return loop.run_until_complete(sanic_client(app))
|
|
||||||
|
|
||||||
|
|
||||||
async def test_index(app, test_client):
|
|
||||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||||
giphy_client_mock.search.return_value = {
|
giphy_client_mock.search.return_value = {
|
||||||
"data": [
|
"data": [
|
||||||
|
@ -32,7 +29,7 @@ async def test_index(app, test_client):
|
||||||
}
|
}
|
||||||
|
|
||||||
with app.ctx.container.giphy_client.override(giphy_client_mock):
|
with app.ctx.container.giphy_client.override(giphy_client_mock):
|
||||||
response = await test_client.get(
|
_, response = await app.asgi_client.get(
|
||||||
"/",
|
"/",
|
||||||
params={
|
params={
|
||||||
"query": "test",
|
"query": "test",
|
||||||
|
@ -41,7 +38,7 @@ async def test_index(app, test_client):
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json
|
||||||
assert data == {
|
assert data == {
|
||||||
"query": "test",
|
"query": "test",
|
||||||
"limit": 10,
|
"limit": 10,
|
||||||
|
@ -52,30 +49,30 @@ async def test_index(app, test_client):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_index_no_data(app, test_client):
|
async def test_index_no_data(app):
|
||||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||||
giphy_client_mock.search.return_value = {
|
giphy_client_mock.search.return_value = {
|
||||||
"data": [],
|
"data": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
with app.ctx.container.giphy_client.override(giphy_client_mock):
|
with app.ctx.container.giphy_client.override(giphy_client_mock):
|
||||||
response = await test_client.get("/")
|
_, response = await app.asgi_client.get("/")
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json
|
||||||
assert data["gifs"] == []
|
assert data["gifs"] == []
|
||||||
|
|
||||||
|
|
||||||
async def test_index_default_params(app, test_client):
|
async def test_index_default_params(app):
|
||||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||||
giphy_client_mock.search.return_value = {
|
giphy_client_mock.search.return_value = {
|
||||||
"data": [],
|
"data": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
with app.ctx.container.giphy_client.override(giphy_client_mock):
|
with app.ctx.container.giphy_client.override(giphy_client_mock):
|
||||||
response = await test_client.get("/")
|
_, response = await app.asgi_client.get("/")
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json
|
||||||
assert data["query"] == app.ctx.container.config.default.query()
|
assert data["query"] == app.ctx.container.config.default.query()
|
||||||
assert data["limit"] == app.ctx.container.config.default.limit()
|
assert data["limit"] == app.ctx.container.config.default.limit()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
dependency-injector
|
dependency-injector
|
||||||
sanic<=21.6
|
sanic
|
||||||
|
sanic-testing
|
||||||
aiohttp
|
aiohttp
|
||||||
pyyaml
|
pyyaml
|
||||||
pytest-sanic
|
|
||||||
pytest-cov
|
pytest-cov
|
||||||
|
|
39
examples/miniapps/starlette-lifespan/README.rst
Normal file
39
examples/miniapps/starlette-lifespan/README.rst
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
Integration With Starlette-based Frameworks
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
This is a `Starlette <https://www.starlette.io/>`_ +
|
||||||
|
`Dependency Injector <https://python-dependency-injector.ets-labs.org/>`_ example application
|
||||||
|
utilizing `lifespan API <https://www.starlette.io/lifespan/>`_.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Pretty much `any framework built on top of Starlette <https://www.starlette.io/third-party-packages/#frameworks>`_
|
||||||
|
supports this feature (`FastAPI <https://fastapi.tiangolo.com/advanced/events/#lifespan>`_,
|
||||||
|
`Xpresso <https://xpresso-api.dev/latest/tutorial/lifespan/>`_, etc...).
|
||||||
|
|
||||||
|
Run
|
||||||
|
---
|
||||||
|
|
||||||
|
Create virtual environment:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python -m venv env
|
||||||
|
. env/bin/activate
|
||||||
|
|
||||||
|
Install requirements:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
To run the application do:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python example.py
|
||||||
|
# or (logging won't be configured):
|
||||||
|
uvicorn --factory example:container.app
|
||||||
|
|
||||||
|
After that visit http://127.0.0.1:8000/ in your browser or use CLI command (``curl``, ``httpie``,
|
||||||
|
etc).
|
59
examples/miniapps/starlette-lifespan/example.py
Executable file
59
examples/miniapps/starlette-lifespan/example.py
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from logging import basicConfig, getLogger
|
||||||
|
|
||||||
|
from dependency_injector.containers import DeclarativeContainer
|
||||||
|
from dependency_injector.ext.starlette import Lifespan
|
||||||
|
from dependency_injector.providers import Factory, Resource, Self, Singleton
|
||||||
|
from starlette.applications import Starlette
|
||||||
|
from starlette.requests import Request
|
||||||
|
from starlette.responses import JSONResponse
|
||||||
|
from starlette.routing import Route
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
log = getLogger(__name__)
|
||||||
|
log.info("Inittializing resources")
|
||||||
|
yield
|
||||||
|
log.info("Cleaning up resources")
|
||||||
|
|
||||||
|
|
||||||
|
async def homepage(request: Request) -> JSONResponse:
|
||||||
|
global count
|
||||||
|
response = JSONResponse({"hello": "world", "count": count})
|
||||||
|
count += 1
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class Container(DeclarativeContainer):
|
||||||
|
__self__ = Self()
|
||||||
|
lifespan = Singleton(Lifespan, __self__)
|
||||||
|
logging = Resource(
|
||||||
|
basicConfig,
|
||||||
|
level="DEBUG",
|
||||||
|
datefmt="%Y-%m-%d %H:%M",
|
||||||
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||||
|
)
|
||||||
|
init = Resource(init)
|
||||||
|
app = Factory(
|
||||||
|
Starlette,
|
||||||
|
debug=True,
|
||||||
|
lifespan=lifespan,
|
||||||
|
routes=[Route("/", homepage)],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
container.app,
|
||||||
|
factory=True,
|
||||||
|
# NOTE: `None` prevents uvicorn from configuring logging, which is
|
||||||
|
# impossible via CLI
|
||||||
|
log_config=None,
|
||||||
|
)
|
3
examples/miniapps/starlette-lifespan/requirements.txt
Normal file
3
examples/miniapps/starlette-lifespan/requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
dependency-injector
|
||||||
|
starlette
|
||||||
|
uvicorn
|
6
examples/providers/configuration/config.json
Normal file
6
examples/providers/configuration/config.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"aws": {
|
||||||
|
"access_key_id": "KEY",
|
||||||
|
"secret_access_key": "SECRET"
|
||||||
|
}
|
||||||
|
}
|
27
examples/providers/configuration/configuration_json.py
Normal file
27
examples/providers/configuration/configuration_json.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
"""`Configuration` provider values loading example."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
config = providers.Configuration()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
container.config.from_json("./config.json")
|
||||||
|
|
||||||
|
assert container.config() == {
|
||||||
|
"aws": {
|
||||||
|
"access_key_id": "KEY",
|
||||||
|
"secret_access_key": "SECRET",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert container.config.aws() == {
|
||||||
|
"access_key_id": "KEY",
|
||||||
|
"secret_access_key": "SECRET",
|
||||||
|
}
|
||||||
|
assert container.config.aws.access_key_id() == "KEY"
|
||||||
|
assert container.config.aws.secret_access_key() == "SECRET"
|
25
examples/providers/configuration/configuration_json_init.py
Normal file
25
examples/providers/configuration/configuration_json_init.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
"""`Configuration` provider values loading example."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
config = providers.Configuration(json_files=["./config.json"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
assert container.config() == {
|
||||||
|
"aws": {
|
||||||
|
"access_key_id": "KEY",
|
||||||
|
"secret_access_key": "SECRET",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert container.config.aws() == {
|
||||||
|
"access_key_id": "KEY",
|
||||||
|
"secret_access_key": "SECRET",
|
||||||
|
}
|
||||||
|
assert container.config.aws.access_key_id() == "KEY"
|
||||||
|
assert container.config.aws.secret_access_key() == "SECRET"
|
|
@ -3,7 +3,7 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from dependency_injector import containers, providers
|
from dependency_injector import containers, providers
|
||||||
from pydantic import BaseSettings, Field
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
# Emulate environment variables
|
# Emulate environment variables
|
||||||
os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
|
os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
|
||||||
|
@ -11,15 +11,16 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
|
||||||
|
|
||||||
|
|
||||||
class AwsSettings(BaseSettings):
|
class AwsSettings(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(env_prefix="aws_")
|
||||||
|
|
||||||
access_key_id: str = Field(env="aws_access_key_id")
|
access_key_id: str
|
||||||
secret_access_key: str = Field(env="aws_secret_access_key")
|
secret_access_key: str
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
|
|
||||||
aws: AwsSettings = AwsSettings()
|
aws: AwsSettings = AwsSettings()
|
||||||
optional: str = Field(default="default_value")
|
optional: str = "default_value"
|
||||||
|
|
||||||
|
|
||||||
class Container(containers.DeclarativeContainer):
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from dependency_injector import containers, providers
|
from dependency_injector import containers, providers
|
||||||
from pydantic import BaseSettings, Field
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
# Emulate environment variables
|
# Emulate environment variables
|
||||||
os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
|
os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
|
||||||
|
@ -11,15 +11,16 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
|
||||||
|
|
||||||
|
|
||||||
class AwsSettings(BaseSettings):
|
class AwsSettings(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(env_prefix="aws_")
|
||||||
|
|
||||||
access_key_id: str = Field(env="aws_access_key_id")
|
access_key_id: str
|
||||||
secret_access_key: str = Field(env="aws_secret_access_key")
|
secret_access_key: str
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
|
|
||||||
aws: AwsSettings = AwsSettings()
|
aws: AwsSettings = AwsSettings()
|
||||||
optional: str = Field(default="default_value")
|
optional: str = "default_value"
|
||||||
|
|
||||||
|
|
||||||
class Container(containers.DeclarativeContainer):
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
|
@ -13,12 +13,12 @@ class Container(containers.DeclarativeContainer):
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
container = Container()
|
container = Container()
|
||||||
|
|
||||||
container.config.option1.from_value(date(2021, 6, 13))
|
container.config.option1.from_value(date(2022, 3, 13))
|
||||||
container.config.option2.from_value(date(2021, 6, 14))
|
container.config.option2.from_value(date(2022, 3, 14))
|
||||||
|
|
||||||
assert container.config() == {
|
assert container.config() == {
|
||||||
"option1": date(2021, 6, 13),
|
"option1": date(2022, 3, 13),
|
||||||
"option2": date(2021, 6, 14),
|
"option2": date(2022, 3, 14),
|
||||||
}
|
}
|
||||||
assert container.config.option1() == date(2021, 6, 13)
|
assert container.config.option1() == date(2022, 3, 13)
|
||||||
assert container.config.option2() == date(2021, 6, 14)
|
assert container.config.option2() == date(2022, 3, 14)
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from dependency_injector import containers, providers
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
def init_thread_pool(max_workers: int):
|
def init_thread_pool(max_workers: int):
|
||||||
thread_pool = ThreadPoolExecutor(max_workers=max_workers)
|
thread_pool = ThreadPoolExecutor(max_workers=max_workers)
|
||||||
yield thread_pool
|
yield thread_pool
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
from dependency_injector import containers, providers
|
from dependency_injector import containers, providers
|
||||||
from dependency_injector.wiring import Provide, inject
|
from dependency_injector.wiring import Provide, inject
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
|
||||||
class Service:
|
class Service: ...
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class Container(containers.DeclarativeContainer):
|
class Container(containers.DeclarativeContainer):
|
||||||
|
@ -13,9 +13,16 @@ class Container(containers.DeclarativeContainer):
|
||||||
service = providers.Factory(Service)
|
service = providers.Factory(Service)
|
||||||
|
|
||||||
|
|
||||||
|
# You can place marker on parameter default value
|
||||||
@inject
|
@inject
|
||||||
def main(service: Service = Provide[Container.service]) -> None:
|
def main(service: Service = Provide[Container.service]) -> None: ...
|
||||||
...
|
|
||||||
|
|
||||||
|
# Also, you can place marker with typing.Annotated
|
||||||
|
@inject
|
||||||
|
def main_with_annotated(
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
31
examples/wiring/example_attribute_annotated.py
Normal file
31
examples/wiring/example_attribute_annotated.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
"""Wiring attribute example with Annotated."""
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from dependency_injector.wiring import Provide
|
||||||
|
|
||||||
|
|
||||||
|
class Service:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
service = providers.Factory(Service)
|
||||||
|
|
||||||
|
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
|
||||||
|
|
||||||
|
class Main:
|
||||||
|
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container()
|
||||||
|
container.wire(modules=[__name__])
|
||||||
|
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
assert isinstance(Main.service, Service)
|
122
pyproject.toml
Normal file
122
pyproject.toml
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "Cython>=3.1.1"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "dependency-injector"
|
||||||
|
authors = [
|
||||||
|
{name = "Roman Mogylatov", email = "rmogilatov@gmail.com"},
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{name = "Roman Mogylatov", email = "rmogilatov@gmail.com"},
|
||||||
|
]
|
||||||
|
description = "Dependency injection framework for Python"
|
||||||
|
readme = {file = "README.rst", content-type = "text/x-rst"}
|
||||||
|
license = {file = "LICENSE.rst", content-type = "text/x-rst"}
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
keywords = [
|
||||||
|
"Dependency injection",
|
||||||
|
"DI",
|
||||||
|
"Inversion of Control",
|
||||||
|
"IoC",
|
||||||
|
"Factory",
|
||||||
|
"Singleton",
|
||||||
|
"Design patterns",
|
||||||
|
"Flask",
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: BSD License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
|
"Framework :: AsyncIO",
|
||||||
|
"Framework :: Bottle",
|
||||||
|
"Framework :: Django",
|
||||||
|
"Framework :: Flask",
|
||||||
|
"Framework :: Pylons",
|
||||||
|
"Framework :: Pyramid",
|
||||||
|
"Framework :: Pytest",
|
||||||
|
"Framework :: TurboGears",
|
||||||
|
"Topic :: Software Development",
|
||||||
|
"Topic :: Software Development :: Libraries",
|
||||||
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
|
]
|
||||||
|
dynamic = ["version"]
|
||||||
|
dependencies = [
|
||||||
|
# typing.Annotated since v3.9
|
||||||
|
# typing.Self since v3.11
|
||||||
|
"typing-extensions; python_version<'3.11'",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
yaml = ["pyyaml"]
|
||||||
|
pydantic = ["pydantic"]
|
||||||
|
pydantic2 = ["pydantic-settings"]
|
||||||
|
flask = ["flask"]
|
||||||
|
aiohttp = ["aiohttp"]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/ets-labs/python-dependency-injector"
|
||||||
|
Documentation = "https://python-dependency-injector.ets-labs.org/"
|
||||||
|
Download = "https://pypi.python.org/pypi/dependency_injector"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
package-dir = {"" = "src"}
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
dependency_injector = ["*.pxd", "*.pyi", "py.typed"]
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = {attr = "dependency_injector.__version__"}
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
branch = false
|
||||||
|
relative_files = true
|
||||||
|
source_pkgs = ["dependency_injector"]
|
||||||
|
plugins = ["Cython.Coverage"]
|
||||||
|
|
||||||
|
[tool.coverage.html]
|
||||||
|
directory = "reports/unittests/"
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
show_missing = true
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
combine_as_imports = true
|
||||||
|
|
||||||
|
[tool.pylint.main]
|
||||||
|
ignore = ["tests"]
|
||||||
|
|
||||||
|
[tool.pylint.design]
|
||||||
|
min-public-methods = 0
|
||||||
|
max-public-methods = 30
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests/unit/"]
|
||||||
|
asyncio_mode = "auto"
|
||||||
|
asyncio_default_fixture_loop_scope = "function"
|
||||||
|
markers = [
|
||||||
|
"pydantic: Tests with Pydantic as a dependency",
|
||||||
|
]
|
||||||
|
filterwarnings = [
|
||||||
|
"ignore::dependency_injector.wiring.DIWiringWarning",
|
||||||
|
"ignore:Module \"dependency_injector.ext.aiohttp\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
|
||||||
|
"ignore:Module \"dependency_injector.ext.flask\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
|
||||||
|
"ignore:Please use \\`.*?\\` from the \\`scipy.*?\\`(.*?)namespace is deprecated\\.:DeprecationWarning",
|
||||||
|
"ignore:Please import \\`.*?\\` from the \\`scipy(.*?)\\` namespace(.*):DeprecationWarning",
|
||||||
|
"ignore:\\`scipy(.*?)\\` is deprecated(.*):DeprecationWarning",
|
||||||
|
]
|
|
@ -1,9 +1,11 @@
|
||||||
cython==0.29.24
|
cython==3.1.1
|
||||||
|
setuptools
|
||||||
pytest
|
pytest
|
||||||
pytest-asyncio
|
pytest-asyncio
|
||||||
tox
|
tox
|
||||||
coverage
|
coverage
|
||||||
flake8
|
flake8
|
||||||
|
flake8-pyproject
|
||||||
pydocstyle
|
pydocstyle
|
||||||
sphinx_autobuild
|
sphinx_autobuild
|
||||||
pip
|
pip
|
||||||
|
@ -12,9 +14,12 @@ pyyaml
|
||||||
httpx
|
httpx
|
||||||
fastapi
|
fastapi
|
||||||
pydantic
|
pydantic
|
||||||
|
pydantic-settings
|
||||||
numpy
|
numpy
|
||||||
scipy
|
scipy
|
||||||
boto3
|
boto3
|
||||||
mypy_boto3_s3
|
mypy_boto3_s3
|
||||||
|
typing_extensions
|
||||||
|
fast-depends
|
||||||
|
|
||||||
-r requirements-ext.txt
|
-r requirements-ext.txt
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user