mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-07-14 18:12:21 +03:00
Compare commits
138 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 |
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
|
90
.github/workflows/publishing.yml
vendored
90
.github/workflows/publishing.yml
vendored
|
@ -10,20 +10,20 @@ jobs:
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.13
|
||||||
- run: pip install tox
|
- run: pip install tox
|
||||||
- run: tox
|
- run: tox
|
||||||
env:
|
env:
|
||||||
TOXENV: 3.11
|
TOXENV: 3.13
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
name: Run linters
|
name: Run linters
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
toxenv: [flake8, pydocstyle, mypy, pylint]
|
toxenv: [flake8, pydocstyle, mypy, pylint]
|
||||||
|
@ -31,7 +31,7 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.13
|
||||||
- run: pip install tox
|
- run: pip install tox
|
||||||
- run: tox
|
- run: tox
|
||||||
env:
|
env:
|
||||||
|
@ -40,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-22.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.13
|
||||||
- run: python setup.py sdist
|
- run: |
|
||||||
- uses: actions/upload-artifact@v3
|
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:
|
||||||
|
@ -57,62 +60,65 @@ jobs:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-22.04, windows-2019, macos-11]
|
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@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: pypa/cibuildwheel@v2.11.3
|
uses: pypa/cibuildwheel@v3.0.0
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
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-22.04-aarch64)
|
name: Upload release to TestPyPI
|
||||||
needs: [tests, linters]
|
needs: [build-sdist, build-wheels]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
|
environment: test-pypi
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/download-artifact@v4
|
||||||
- name: Set up QEMU
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Build wheels
|
|
||||||
uses: pypa/cibuildwheel@v2.11.3
|
|
||||||
env:
|
|
||||||
CIBW_ARCHS_LINUX: aarch64
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
with:
|
||||||
path: ./wheelhouse/*.whl
|
pattern: cibw-*
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
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-22.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-22.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
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: |
|
||||||
|
|
40
.github/workflows/tests-and-linters.yml
vendored
40
.github/workflows/tests-and-linters.yml
vendored
|
@ -4,28 +4,12 @@ on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
tests-on-legacy-versions:
|
|
||||||
name: Run tests on legacy versions
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: [2.7, 3.5, 3.6, 3.7, pypy2.7, pypy3.9]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
- run: pip install tox
|
|
||||||
- run: tox
|
|
||||||
env:
|
|
||||||
TOXENV: ${{ matrix.python-version }}
|
|
||||||
|
|
||||||
test-on-different-versions:
|
test-on-different-versions:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.8, 3.9, "3.10", 3.11]
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
|
@ -34,23 +18,35 @@ jobs:
|
||||||
- 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@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
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
|
||||||
|
|
||||||
|
@ -64,7 +60,7 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
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) 2022, 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)*
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
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::
|
|
@ -9,3 +9,4 @@ API Documentation
|
||||||
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 = "2022, 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::
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -310,4 +310,6 @@ A few useful links related to a dependency injection design pattern for further
|
||||||
+ 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::
|
||||||
|
|
|
@ -31,7 +31,7 @@ Key features of the ``Dependency Injector``:
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,118 @@ 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`_
|
||||||
|
|
||||||
Development version
|
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.
|
- 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 use Ubuntu 22.04.
|
||||||
- Update CI/CD to ``actions/checkout@v3``, ``actions/setup-python@v4``, ``actions/upload-artifact@v3``, and ``pypa/cibuildwheel@v2.11.3``.
|
- 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.
|
- Regenerate C sources using Cython 0.29.32.
|
||||||
|
- Fix builds badge.
|
||||||
|
|
||||||
4.40.0
|
4.40.0
|
||||||
------
|
------
|
||||||
|
|
|
@ -183,22 +183,22 @@ 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
|
||||||
|
@ -215,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
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
@ -361,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::
|
||||||
|
|
|
@ -64,7 +64,7 @@ FastAPI example:
|
||||||
|
|
||||||
@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])]):
|
||||||
value = await service.process()
|
value = await service.process()
|
||||||
return {"result": value}
|
return {"result": value}
|
||||||
|
|
||||||
|
@ -127,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
|
||||||
|
@ -254,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
|
||||||
|
@ -601,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
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
@ -632,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::
|
||||||
|
|
|
@ -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 2022 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 2022 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 2022 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 2022 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 2022 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 2022 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 2022 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 2022 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 2022 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 2022 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 2022 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 2022 02:42:17.984 # User requested shutdown...
|
fastapi-redis-example-1 | INFO: Started server process [1]
|
||||||
redis_1 | 1:M 04 Jan 2022 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 2022 02:42:22.035 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
|
fastapi-redis-example-1 | INFO: Application startup complete.
|
||||||
redis_1 | 1:C 04 Jan 2022 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 2022 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 2022 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 2022 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 2022 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 2022 02:42:22.037 * Loading RDB produced by version 6.0.9
|
|
||||||
redis_1 | 1:M 04 Jan 2022 02:42:22.037 * RDB age 9 seconds
|
|
||||||
redis_1 | 1:M 04 Jan 2022 02:42:22.037 * RDB memory usage when created 0.77 Mb
|
|
||||||
redis_1 | 1:M 04 Jan 2022 02:42:22.037 * DB loaded from disk: 0.000 seconds
|
|
||||||
redis_1 | 1:M 04 Jan 2022 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:
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -22,7 +25,7 @@ def get_list(
|
||||||
@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()
|
||||||
|
|
||||||
|
@ -42,8 +45,8 @@ def add(
|
||||||
@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
|
|
@ -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):
|
||||||
|
|
|
@ -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.32
|
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
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# TODO: unpin 3.5.0 when this bug is fixed: https://github.com/sphinx-doc/sphinx/issues/8885
|
# TODO: unpin 3.5.0 when this bug is fixed: https://github.com/sphinx-doc/sphinx/issues/8885
|
||||||
sphinx<3.5.0
|
sphinx
|
||||||
|
|
||||||
# TODO: unpin jinja2 after sphinx update to 4+
|
# TODO: unpin jinja2 after sphinx update to 4+
|
||||||
jinja2<3.1
|
jinja2
|
||||||
|
|
||||||
-e git+https://github.com/rmk135/sphinxcontrib-disqus.git#egg=sphinxcontrib-disqus
|
sphinx-disqus==1.3.0
|
||||||
|
|
||||||
-r requirements-ext.txt
|
-r requirements-ext.txt
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
flask
|
flask
|
||||||
|
werkzeug
|
||||||
aiohttp
|
aiohttp
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
six>=1.7.0,<=1.16.0
|
|
|
@ -1,13 +1,14 @@
|
||||||
[flake8]
|
[flake8]
|
||||||
max_line_length = 100
|
max_line_length = 120
|
||||||
max_complexity = 10
|
max_complexity = 10
|
||||||
exclude = types.py
|
exclude = types.py
|
||||||
|
extend-ignore = E203,E701
|
||||||
per-file-ignores =
|
per-file-ignores =
|
||||||
examples/demo/*: F841
|
examples/demo/*: F841
|
||||||
examples/containers/traverse.py: E501
|
examples/containers/traverse.py: E501
|
||||||
examples/providers/async.py: F841
|
examples/providers/async.py: F841
|
||||||
examples/providers/async_overriding.py: F841
|
examples/providers/async_overriding.py: F841
|
||||||
examples/wiring/*: F841
|
examples/wiring/*: F821,F841
|
||||||
|
|
||||||
[pydocstyle]
|
[pydocstyle]
|
||||||
ignore = D100,D101,D102,D105,D106,D107,D203,D213
|
ignore = D100,D101,D102,D105,D106,D107,D203,D213
|
||||||
|
|
158
setup.py
158
setup.py
|
@ -1,123 +1,55 @@
|
||||||
"""`Dependency injector` setup script."""
|
"""`Dependency injector` setup script."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import sys
|
||||||
|
|
||||||
from setuptools import setup, Extension
|
from Cython.Build import cythonize
|
||||||
|
from Cython.Compiler import Options
|
||||||
|
from setuptools import Extension, setup
|
||||||
|
|
||||||
|
debug = os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1"
|
||||||
# Defining setup variables:
|
limited_api = (
|
||||||
defined_macros = dict()
|
os.environ.get("DEPENDENCY_INJECTOR_LIMITED_API") == "1"
|
||||||
defined_macros["CYTHON_CLINE_IN_TRACEBACK"] = 0
|
and sys.implementation.name == "cpython"
|
||||||
|
)
|
||||||
# Getting description:
|
defined_macros = []
|
||||||
with open("README.rst") as readme_file:
|
options = {}
|
||||||
description = readme_file.read()
|
compiler_directives = {
|
||||||
|
"language_level": 3,
|
||||||
# Getting requirements:
|
"profile": debug,
|
||||||
with open("requirements.txt") as requirements_file:
|
"linetrace": debug,
|
||||||
requirements = requirements_file.readlines()
|
}
|
||||||
|
Options.annotate = debug
|
||||||
# Getting version:
|
|
||||||
with open("src/dependency_injector/__init__.py") as init_file:
|
|
||||||
version = re.search("__version__ = \"(.*?)\"", init_file.read()).group(1)
|
|
||||||
|
|
||||||
# Adding debug options:
|
# Adding debug options:
|
||||||
if os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1":
|
if debug:
|
||||||
defined_macros["CYTHON_TRACE"] = 1
|
limited_api = False # line tracing is not part of the Limited API
|
||||||
defined_macros["CYTHON_TRACE_NOGIL"] = 1
|
defined_macros.extend(
|
||||||
defined_macros["CYTHON_CLINE_IN_TRACEBACK"] = 1
|
[
|
||||||
|
("CYTHON_TRACE", "1"),
|
||||||
|
("CYTHON_TRACE_NOGIL", "1"),
|
||||||
|
("CYTHON_CLINE_IN_TRACEBACK", "1"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if limited_api:
|
||||||
|
options.setdefault("bdist_wheel", {})
|
||||||
|
options["bdist_wheel"]["py_limited_api"] = "cp38"
|
||||||
|
defined_macros.append(("Py_LIMITED_API", "0x03080000"))
|
||||||
|
|
||||||
setup(name="dependency-injector",
|
setup(
|
||||||
version=version,
|
options=options,
|
||||||
description="Dependency injection framework for Python",
|
ext_modules=cythonize(
|
||||||
long_description=description,
|
[
|
||||||
author="Roman Mogylatov",
|
Extension(
|
||||||
author_email="rmogilatov@gmail.com",
|
"*",
|
||||||
maintainer="Roman Mogylatov",
|
["src/**/*.pyx"],
|
||||||
maintainer_email="rmogilatov@gmail.com",
|
define_macros=defined_macros,
|
||||||
url="https://github.com/ets-labs/python-dependency-injector",
|
py_limited_api=limited_api,
|
||||||
download_url="https://pypi.python.org/pypi/dependency_injector",
|
),
|
||||||
packages=[
|
|
||||||
"dependency_injector",
|
|
||||||
"dependency_injector.ext",
|
|
||||||
],
|
],
|
||||||
package_dir={
|
annotate=debug,
|
||||||
"": "src",
|
show_all_warnings=True,
|
||||||
},
|
compiler_directives=compiler_directives,
|
||||||
package_data={
|
),
|
||||||
"dependency_injector": ["*.pxd", "*.pyi", "py.typed"],
|
)
|
||||||
},
|
|
||||||
ext_modules=[
|
|
||||||
Extension("dependency_injector.containers",
|
|
||||||
["src/dependency_injector/containers.c"],
|
|
||||||
define_macros=list(defined_macros.items()),
|
|
||||||
extra_compile_args=["-O2"]),
|
|
||||||
Extension("dependency_injector.providers",
|
|
||||||
["src/dependency_injector/providers.c"],
|
|
||||||
define_macros=list(defined_macros.items()),
|
|
||||||
extra_compile_args=["-O2"]),
|
|
||||||
Extension("dependency_injector._cwiring",
|
|
||||||
["src/dependency_injector/_cwiring.c"],
|
|
||||||
define_macros=list(defined_macros.items()),
|
|
||||||
extra_compile_args=["-O2"]),
|
|
||||||
],
|
|
||||||
install_requires=requirements,
|
|
||||||
extras_require={
|
|
||||||
"yaml": [
|
|
||||||
"pyyaml",
|
|
||||||
],
|
|
||||||
"pydantic": [
|
|
||||||
"pydantic",
|
|
||||||
],
|
|
||||||
"flask": [
|
|
||||||
"flask",
|
|
||||||
],
|
|
||||||
"aiohttp": [
|
|
||||||
"aiohttp",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
zip_safe=True,
|
|
||||||
license="BSD New",
|
|
||||||
platforms=["any"],
|
|
||||||
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 :: 2",
|
|
||||||
"Programming Language :: Python :: 2.7",
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Programming Language :: Python :: 3.5",
|
|
||||||
"Programming Language :: Python :: 3.6",
|
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
|
||||||
"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",
|
|
||||||
])
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Top-level package."""
|
"""Top-level package."""
|
||||||
|
|
||||||
__version__ = "4.40.0"
|
__version__ = "4.48.1"
|
||||||
"""Version number.
|
"""Version number.
|
||||||
|
|
||||||
:type: str
|
:type: str
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user