mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-07-15 02:22:28 +03:00
Compare commits
No commits in common. "master" and "4.42.0b1" have entirely different histories.
10
.coveragerc
Normal file
10
.coveragerc
Normal file
|
@ -0,0 +1,10 @@
|
|||
[run]
|
||||
source = dependency_injector
|
||||
omit = tests/unit
|
||||
plugins = Cython.Coverage
|
||||
|
||||
[report]
|
||||
show_missing = true
|
||||
|
||||
[html]
|
||||
directory=reports/unittests/
|
|
@ -1,29 +0,0 @@
|
|||
---
|
||||
description: Code in Python and Cython
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
- Follow PEP 8 rules
|
||||
- When you write imports, split system, 3rd-party, and local imports with a new line
|
||||
- Have two empty lines between the import block and the rest of the code
|
||||
- Have an empty line (\n) at the end of every file
|
||||
- If a file is supposed to be run, always add ``if __name__ == 'main'``
|
||||
- Always follow a consistent pattern of using double or single quotes
|
||||
- When there is a class without a docblock, leave one blank line before its members, e.g.:
|
||||
```python
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service = providers.Factory(Service)
|
||||
```
|
||||
|
||||
- Avoid shortcuts in names unless absolutely necessary, exceptions:
|
||||
```
|
||||
arg
|
||||
args
|
||||
kwarg
|
||||
kwargs
|
||||
obj
|
||||
cls
|
||||
```
|
||||
|
||||
- Avoid inline comments unless absolutely necessary
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
description: Build and run tests
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
- Use Makefile commands to build, test, lint and other similar operations when they are available.
|
||||
- Activate virtualenv before running any commands by ``. venv/bin/actvate``
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
description: Run examples
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
- When you run an example from the ``examples/`` folder, switch to the example folder and run it from there.
|
||||
- If there are instructions on running the examples or its tests in readme, follow them
|
||||
- Activate virtualenv before running any commands by ``. venv/bin/actvate``
|
|
@ -1,9 +0,0 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{py,pyi,pxd,pyx}]
|
||||
ij_visual_guides = 80,88
|
90
.github/workflows/publishing.yml
vendored
90
.github/workflows/publishing.yml
vendored
|
@ -10,20 +10,20 @@ jobs:
|
|||
|
||||
tests:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.12
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: 3.13
|
||||
TOXENV: 3.12
|
||||
|
||||
linters:
|
||||
name: Run linters
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
toxenv: [flake8, pydocstyle, mypy, pylint]
|
||||
|
@ -31,7 +31,7 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.12
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
|
@ -40,18 +40,17 @@ jobs:
|
|||
build-sdist:
|
||||
name: Build source tarball
|
||||
needs: [tests, linters]
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.12
|
||||
- run: |
|
||||
python -m pip install --upgrade build
|
||||
python -m build --sdist
|
||||
- uses: actions/upload-artifact@v4
|
||||
python -m pip install --upgrade pip setuptools
|
||||
python setup.py sdist
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cibw-sdist
|
||||
path: ./dist/*
|
||||
|
||||
build-wheels:
|
||||
|
@ -60,65 +59,64 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2022, macos-14]
|
||||
os: [ubuntu-22.04, windows-2019, macos-14]
|
||||
env:
|
||||
CIBW_ENABLE: pypy
|
||||
CIBW_ENVIRONMENT: >-
|
||||
PIP_CONFIG_SETTINGS="build_ext=-j4"
|
||||
DEPENDENCY_INJECTOR_LIMITED_API="1"
|
||||
CFLAGS="-g0"
|
||||
CIBW_SKIP: cp27-* cp313-*
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v3.0.0
|
||||
- uses: actions/upload-artifact@v4
|
||||
uses: pypa/cibuildwheel@v2.20.0
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
test-publish:
|
||||
name: Upload release to TestPyPI
|
||||
needs: [build-sdist, build-wheels]
|
||||
runs-on: ubuntu-latest
|
||||
environment: test-pypi
|
||||
permissions:
|
||||
id-token: write
|
||||
build-wheels-linux-aarch64:
|
||||
name: Build wheels (ubuntu-22.04-aarch64)
|
||||
needs: [tests, linters]
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CIBW_SKIP: cp27-* cp313-*
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
if: runner.os == 'Linux'
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v2.20.0
|
||||
env:
|
||||
CIBW_ARCHS_LINUX: aarch64
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
pattern: cibw-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
publish:
|
||||
name: Upload release to PyPI
|
||||
needs: [build-sdist, build-wheels, test-publish]
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
id-token: write
|
||||
name: Publish on PyPI
|
||||
needs: [build-sdist, build-wheels, build-wheels-linux-aarch64]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
pattern: cibw-*
|
||||
name: artifact
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
# For publishing to Test PyPI, uncomment next two lines:
|
||||
# password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||
# repository_url: https://test.pypi.org/legacy/
|
||||
|
||||
publish-docs:
|
||||
name: Publish docs
|
||||
needs: [publish]
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.13
|
||||
- run: pip install awscli
|
||||
python-version: 3.12
|
||||
- run: pip install -r requirements-doc.txt
|
||||
- run: pip install awscli
|
||||
- run: pip install -e .
|
||||
- run: (cd docs && make clean html)
|
||||
- run: |
|
||||
|
|
33
.github/workflows/tests-and-linters.yml
vendored
33
.github/workflows/tests-and-linters.yml
vendored
|
@ -4,12 +4,12 @@ on: [push, pull_request, workflow_dispatch]
|
|||
|
||||
jobs:
|
||||
|
||||
test-on-different-versions:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
tests-on-legacy-versions:
|
||||
name: Run tests on legacy versions
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
python-version: [3.7]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
|
@ -18,26 +18,30 @@ jobs:
|
|||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
DEPENDENCY_INJECTOR_LIMITED_API: 1
|
||||
TOXENV: ${{ matrix.python-version }}
|
||||
|
||||
test-different-pydantic-versions:
|
||||
name: Run tests with different pydantic versions
|
||||
test-on-different-versions:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8, 3.9, "3.10", 3.11, 3.12]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- run: pip install tox
|
||||
- run: tox -e pydantic-v1,pydantic-v2
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: ${{ matrix.python-version }}
|
||||
|
||||
test-coverage:
|
||||
name: Run tests with coverage
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DEPENDENCY_INJECTOR_DEBUG_MODE: 1
|
||||
PIP_VERBOSE: 1
|
||||
# Cython's version <3 issue with tracing: "error: no member named 'use_tracing' in 'struct _PyCFrame'"
|
||||
# DEPENDENCY_INJECTOR_DEBUG_MODE: 1
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
|
@ -45,8 +49,9 @@ jobs:
|
|||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.12
|
||||
- run: pip install tox
|
||||
- run: tox -vv
|
||||
- run: pip install tox cython==0.29.37
|
||||
- run: make cythonize
|
||||
- run: tox
|
||||
env:
|
||||
TOXENV: coveralls
|
||||
|
||||
|
@ -60,7 +65,7 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.12
|
||||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
|
|
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -15,7 +15,6 @@ lib64/
|
|||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheelhouse/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
@ -64,11 +63,13 @@ venv*/
|
|||
# Vim Rope
|
||||
.ropeproject/
|
||||
|
||||
# Cython artifacts
|
||||
src/**/*.c
|
||||
src/**/*.h
|
||||
src/**/*.so
|
||||
src/**/*.html
|
||||
# C extensions
|
||||
src/dependency_injector/*.h
|
||||
src/dependency_injector/*.so
|
||||
src/dependency_injector/containers/*.h
|
||||
src/dependency_injector/containers/*.so
|
||||
src/dependency_injector/providers/*.h
|
||||
src/dependency_injector/providers/*.so
|
||||
|
||||
# Workspace for samples
|
||||
.workspace/
|
||||
|
|
49
.pylintrc
Normal file
49
.pylintrc
Normal file
|
@ -0,0 +1,49 @@
|
|||
[MASTER]
|
||||
|
||||
# Add <file or directory> to the black list. It should be a base name, not a
|
||||
# path. You may set this option multiple times.
|
||||
ignore=utils,tests
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Disable the message(s) with the given id(s).
|
||||
# disable-msg=
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=5
|
||||
|
||||
[TYPECHECK]
|
||||
ignore-mixin-members=yes
|
||||
# ignored-classes=
|
||||
zope=no
|
||||
# generated-members=providedBy,implementedBy,rawDataReceived
|
||||
|
||||
[DESIGN]
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=10
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=20
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=10
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branchs=10
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=60
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=10
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=30
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=0
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=30
|
|
@ -21,4 +21,3 @@ Dependency Injector Contributors
|
|||
+ Thiago Hiromi (thiromi)
|
||||
+ Felipe Rubio (krouw)
|
||||
+ Anton Petrov (anton-petrov)
|
||||
+ ZipFile (ZipFile)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
recursive-include src/dependency_injector *.py* *.c py.typed
|
||||
recursive-include src/dependency_injector *.py* *.c
|
||||
recursive-include tests *.py
|
||||
include README.rst
|
||||
include CONTRIBUTORS.rst
|
||||
include LICENSE.rst
|
||||
include requirements.txt
|
||||
include setup.py
|
||||
include tox.ini
|
||||
include py.typed
|
||||
|
|
32
Makefile
32
Makefile
|
@ -1,6 +1,14 @@
|
|||
VERSION := $(shell python setup.py --version)
|
||||
|
||||
export COVERAGE_RCFILE := pyproject.toml
|
||||
CYTHON_SRC := $(shell find src/dependency_injector -name '*.pyx')
|
||||
|
||||
CYTHON_DIRECTIVES = -Xlanguage_level=2
|
||||
|
||||
ifdef DEPENDENCY_INJECTOR_DEBUG_MODE
|
||||
CYTHON_DIRECTIVES += -Xprofile=True
|
||||
CYTHON_DIRECTIVES += -Xlinetrace=True
|
||||
endif
|
||||
|
||||
|
||||
clean:
|
||||
# Clean sources
|
||||
|
@ -17,17 +25,21 @@ clean:
|
|||
find examples -name '*.py[co]' -delete
|
||||
find examples -name '__pycache__' -delete
|
||||
|
||||
build: clean
|
||||
# Compile C extensions
|
||||
python setup.py build_ext --inplace
|
||||
cythonize:
|
||||
# Compile Cython to C
|
||||
cython -a $(CYTHON_DIRECTIVES) $(CYTHON_SRC)
|
||||
# Move all Cython html reports
|
||||
mkdir -p reports/cython/
|
||||
find src -name '*.html' -exec mv {} reports/cython/ \;
|
||||
|
||||
build: clean cythonize
|
||||
# Compile C extensions
|
||||
python setup.py build_ext --inplace
|
||||
|
||||
docs-live:
|
||||
sphinx-autobuild docs docs/_build/html
|
||||
|
||||
install: uninstall clean build
|
||||
install: uninstall clean cythonize
|
||||
pip install -ve .
|
||||
|
||||
uninstall:
|
||||
|
@ -36,9 +48,9 @@ uninstall:
|
|||
test:
|
||||
# Unit tests with coverage report
|
||||
coverage erase
|
||||
coverage run -m pytest
|
||||
coverage report
|
||||
coverage html
|
||||
coverage run --rcfile=./.coveragerc -m pytest -c tests/.configs/pytest.ini
|
||||
coverage report --rcfile=./.coveragerc
|
||||
coverage html --rcfile=./.coveragerc
|
||||
|
||||
check:
|
||||
flake8 src/dependency_injector/
|
||||
|
@ -49,9 +61,9 @@ check:
|
|||
|
||||
mypy tests/typing
|
||||
|
||||
test-publish: build
|
||||
test-publish: cythonize
|
||||
# Create distributions
|
||||
python -m build --sdist
|
||||
python setup.py sdist
|
||||
# Upload distributions to PyPI
|
||||
twine upload --repository testpypi dist/dependency-injector-$(VERSION)*
|
||||
|
||||
|
|
9
docs/_static/custom.css
vendored
9
docs/_static/custom.css
vendored
|
@ -1,9 +0,0 @@
|
|||
.no-border {
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
}
|
||||
.no-border td {
|
||||
border: 0px !important;
|
||||
padding: 0px 10px 0px 0px !important;
|
||||
}
|
11
docs/_static/disqus.js
vendored
Normal file
11
docs/_static/disqus.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
var disqus_shortname;
|
||||
var disqus_identifier;
|
||||
|
||||
$(function() {
|
||||
var disqus_thread = $("#disqus_thread");
|
||||
disqus_shortname = disqus_thread.data('disqus-shortname');
|
||||
disqus_identifier = disqus_thread.data('disqus-identifier');
|
||||
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
|
||||
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
|
||||
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
|
||||
});
|
1
docs/_static/sponsor.html
vendored
1
docs/_static/sponsor.html
vendored
|
@ -1 +0,0 @@
|
|||
<iframe src="https://github.com/sponsors/rmk135/button" title="Sponsor Dependency Injector" height="32" width="114" style="border: 0; border-radius: 6px;"></iframe>
|
|
@ -1,9 +0,0 @@
|
|||
dependency_injector.ext.starlette
|
||||
=================================
|
||||
|
||||
.. automodule:: dependency_injector.ext.starlette
|
||||
:members:
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. disqus::
|
|
@ -9,4 +9,3 @@ API Documentation
|
|||
containers
|
||||
wiring
|
||||
errors
|
||||
asgi-lifespan
|
||||
|
|
10
docs/conf.py
10
docs/conf.py
|
@ -33,7 +33,7 @@ sys.path.insert(0, os.path.abspath(".."))
|
|||
extensions = [
|
||||
"alabaster",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx_disqus.disqus",
|
||||
"sphinxcontrib.disqus",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
@ -52,7 +52,7 @@ master_doc = "index"
|
|||
|
||||
# General information about the project.
|
||||
project = "Dependency Injector"
|
||||
copyright = "2024, Roman Mogylatov"
|
||||
copyright = "2022, Roman Mogylatov"
|
||||
author = "Roman Mogylatov"
|
||||
|
||||
# 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.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = "en"
|
||||
language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
|
@ -147,9 +147,6 @@ html_favicon = "favicon.ico"
|
|||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
html_css_files = [
|
||||
"custom.css",
|
||||
]
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
|
@ -309,5 +306,4 @@ html_theme_options = {
|
|||
"description": "Dependency injection framework for Python by Roman Mogylatov",
|
||||
"code_font_size": "10pt",
|
||||
"analytics_id": "UA-67012059-1",
|
||||
"donate_url": "https://github.com/sponsors/rmk135",
|
||||
}
|
||||
|
|
|
@ -78,6 +78,4 @@ Sources
|
|||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -84,6 +84,4 @@ Run the application
|
|||
|
||||
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-multiple-containers>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
|
@ -90,6 +90,4 @@ Run the application
|
|||
|
||||
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-single-container>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -17,6 +17,4 @@ Listing of ``boto3_session_example.py``:
|
|||
.. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py
|
||||
:language: python
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -129,6 +129,4 @@ Run the application
|
|||
|
||||
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -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
|
||||
:language: python
|
||||
:emphasize-lines: 12
|
||||
:emphasize-lines: 13
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
@ -94,6 +94,4 @@ Sources
|
|||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -95,6 +95,4 @@ See also:
|
|||
- Resource provider :ref:`resource-async-initializers`
|
||||
- Wiring :ref:`async-injections-wiring`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -116,6 +116,4 @@ Sources
|
|||
|
||||
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -76,6 +76,4 @@ Sources
|
|||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
.. _fastdepends-example:
|
||||
|
||||
FastDepends example
|
||||
===================
|
||||
|
||||
.. meta::
|
||||
:keywords: Python,Dependency Injection,FastDepends,Example
|
||||
:description: This example demonstrates a usage of the FastDepends and Dependency Injector.
|
||||
|
||||
|
||||
This example demonstrates how to use ``Dependency Injector`` with `FastDepends <https://github.com/Lancetnik/FastDepends>`_, a lightweight dependency injection framework inspired by FastAPI's dependency system, but without the web framework components.
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
|
||||
The integration between FastDepends and Dependency Injector is straightforward. Simply use Dependency Injector's ``Provide`` marker within FastDepends' ``Depends`` function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from fast_depends import Depends
|
||||
|
||||
|
||||
class CoefficientService:
|
||||
@staticmethod
|
||||
def get_coefficient() -> float:
|
||||
return 1.2
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
service = providers.Factory(CoefficientService)
|
||||
|
||||
|
||||
@inject
|
||||
def apply_coefficient(
|
||||
a: int,
|
||||
coefficient_provider: CoefficientService = Depends(Provide[Container.service]),
|
||||
) -> float:
|
||||
return a * coefficient_provider.get_coefficient()
|
||||
|
||||
|
||||
container = Container()
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
|
||||
apply_coefficient(100) == 120.0
|
|
@ -86,6 +86,4 @@ Sources
|
|||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -84,6 +84,4 @@ Sources
|
|||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -22,6 +22,5 @@ Explore the examples to see the ``Dependency Injector`` in action.
|
|||
fastapi
|
||||
fastapi-redis
|
||||
fastapi-sqlalchemy
|
||||
fastdepends
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -77,6 +77,4 @@ Sources
|
|||
|
||||
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -310,6 +310,4 @@ A few useful links related to a dependency injection design pattern for further
|
|||
+ https://github.com/ets-labs/python-dependency-injector
|
||||
+ https://pypi.org/project/dependency-injector/
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. 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:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: plain
|
||||
|
||||
Explicit is better than implicit
|
||||
|
||||
|
|
|
@ -7,95 +7,6 @@ that were made in every particular version.
|
|||
From version 0.7.6 *Dependency Injector* framework strictly
|
||||
follows `Semantic versioning`_
|
||||
|
||||
4.48.1
|
||||
------
|
||||
|
||||
* Improve performance of ``dependency_injector._cwiring.DependencyResolver``
|
||||
* Add ``typing-extensions`` as a dependency for older Python versions (<3.11)
|
||||
* Produce warning on ``@inject``s without ``Provide[...]`` marks
|
||||
* Add support for `resource_type` in ``Lifespan``s
|
||||
|
||||
4.48.0
|
||||
------
|
||||
|
||||
- Improve performance of wiring (`#897 <https://github.com/ets-labs/python-dependency-injector/pull/897>`_)
|
||||
- Add Context Manager support to Resource provider (`#899 <https://github.com/ets-labs/python-dependency-injector/pull/899>`_)
|
||||
- Add support for async generator injections (`#900 <https://github.com/ets-labs/python-dependency-injector/pull/900>`_)
|
||||
- Fix unintended dependency on ``typing_extensions`` (`#902 <https://github.com/ets-labs/python-dependency-injector/pull/902>`_)
|
||||
- Add support for Fast Depends (`#898 <https://github.com/ets-labs/python-dependency-injector/pull/898>`_)
|
||||
- Add ``resource_type`` parameter to init and shutdown resources using specialized providers (`#858 <https://github.com/ets-labs/python-dependency-injector/pull/858>`_)
|
||||
|
||||
4.47.1
|
||||
------
|
||||
|
||||
- Fix typing for wiring marker (`#892 <https://github.com/ets-labs/python-dependency-injector/pull/896>`_)
|
||||
- Strip debug symbols in wheels
|
||||
|
||||
4.47.0
|
||||
------
|
||||
|
||||
- Add support for ``Annotated`` type for module and class attribute injection in wiring,
|
||||
with updated documentation and examples.
|
||||
See discussion:
|
||||
https://github.com/ets-labs/python-dependency-injector/pull/721#issuecomment-2025263718
|
||||
- Fix ``root`` property shadowing in ``ConfigurationOption`` (`#875 <https://github.com/ets-labs/python-dependency-injector/pull/875>`_)
|
||||
- Fix incorrect monkeypatching during ``wire()`` that could violate MRO in some classes (`#886 <https://github.com/ets-labs/python-dependency-injector/pull/886>`_)
|
||||
- ABI3 wheels are now published for CPython.
|
||||
- Drop support of Python 3.7.
|
||||
|
||||
4.46.0
|
||||
------
|
||||
|
||||
- Add option to disable env var interpolation in configs (`#861 <https://github.com/ets-labs/python-dependency-injector/pull/861>`_)
|
||||
- Fix ``Closing`` dependency resolution (`#852 <https://github.com/ets-labs/python-dependency-injector/pull/852>`_)
|
||||
- Add support for ``inspect.iscoroutinefunction()`` in ``Coroutine`` provider (`#830 <https://github.com/ets-labs/python-dependency-injector/pull/830>`_)
|
||||
- Fix broken wiring of sync inject-decorated methods (`#673 <https://github.com/ets-labs/python-dependency-injector/pull/673>`_)
|
||||
- Add support for ``typing.Annotated`` (`#721 <https://github.com/ets-labs/python-dependency-injector/pull/721>`_, `#853 <https://github.com/ets-labs/python-dependency-injector/pull/853>`_)
|
||||
- Documentation updates for movie-lister example (`#747 <https://github.com/ets-labs/python-dependency-injector/pull/747>`_)
|
||||
- Fix type propagation in ``Provider.provider`` (`#744 <https://github.com/ets-labs/python-dependency-injector/pull/744>`_)
|
||||
|
||||
Many thanks for the contributions to:
|
||||
- `ZipFile <https://github.com/ZipFile>`_
|
||||
- `Yegor Statkevich <https://github.com/jazzthief>`_
|
||||
- `Federico Tomasi <https://github.com/federinik>`_
|
||||
- `Martin Lafrance <https://github.com/martlaf>`_
|
||||
- `Philip Bjorge <https://github.com/philipbjorge>`_
|
||||
- `Ilya Kazakov <https://github.com/mrKazzila>`_
|
||||
|
||||
4.45.0
|
||||
--------
|
||||
- Add Starlette lifespan handler implementation (`#683 <https://github.com/ets-labs/python-dependency-injector/pull/683>`_).
|
||||
- Raise exception in ``ThreadLocalSingleton`` instead of hiding it in finally (`#845 <https://github.com/ets-labs/python-dependency-injector/pull/845>`_).
|
||||
- Improve debuggability of ``deepcopy`` errors (`#839 <https://github.com/ets-labs/python-dependency-injector/pull/839>`_).
|
||||
- Update examples (`#838 <https://github.com/ets-labs/python-dependency-injector/pull/838>`_).
|
||||
- Upgrade testing dependencies (`#837 <https://github.com/ets-labs/python-dependency-injector/pull/837>`_).
|
||||
- Add minor fixes to the documentation (`#709 <https://github.com/ets-labs/python-dependency-injector/pull/709>`_).
|
||||
- Remove ``six`` from the dependencies (`3ba4704 <https://github.com/ets-labs/python-dependency-injector/commit/3ba4704bc1cb00310749fd2eda0c8221167c313c>`_).
|
||||
|
||||
Many thanks for the contributions to:
|
||||
- `ZipFile <https://github.com/ZipFile>`_
|
||||
- `František Trebuňa <https://github.com/gortibaldik>`_
|
||||
- `JC (Jonathan Chen) <https://github.com/dijonkitchen>`_
|
||||
|
||||
4.44.0
|
||||
--------
|
||||
- Implement support for Pydantic 2. PR: `#832 <https://github.com/ets-labs/python-dependency-injector/pull/832>`_.
|
||||
- Implement `PEP-517 <https://peps.python.org/pep-0517/>`_, `PEP-518 <https://peps.python.org/pep-0518/>`_, and
|
||||
`PEP-621 <https://peps.python.org/pep-0621/>`_. PR: `#829 <https://github.com/ets-labs/python-dependency-injector/pull/829>`_.
|
||||
|
||||
Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
|
||||
|
||||
4.43.0
|
||||
--------
|
||||
- Add support for Python 3.13.
|
||||
- Migrate to Cython 3 (version 3.0.11). Many thanks to `ZipFile <https://github.com/ZipFile>`_ for
|
||||
this contribution `#813 <https://github.com/ets-labs/python-dependency-injector/pull/813>`_.
|
||||
|
||||
4.42.0
|
||||
--------
|
||||
- Promote release ``4.42.0b1`` to a production release.
|
||||
- Fix the Disqus comment widget.
|
||||
|
||||
4.42.0b1
|
||||
--------
|
||||
|
||||
|
|
|
@ -183,22 +183,22 @@ See also: :ref:`configuration-envs-interpolation`.
|
|||
Loading from a Pydantic settings
|
||||
--------------------------------
|
||||
|
||||
``Configuration`` provider can load configuration from a ``pydantic_settings.BaseSettings`` object using the
|
||||
``Configuration`` provider can load configuration from a ``pydantic`` settings object using the
|
||||
:py:meth:`Configuration.from_pydantic` method:
|
||||
|
||||
.. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 32
|
||||
:emphasize-lines: 31
|
||||
|
||||
To get the data from pydantic settings ``Configuration`` provider calls its ``model_dump()`` method.
|
||||
To get the data from pydantic settings ``Configuration`` provider calls ``Settings.dict()`` method.
|
||||
If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
container.config.from_pydantic(Settings(), exclude={"optional"})
|
||||
|
||||
Alternatively, you can provide a ``pydantic_settings.BaseSettings`` object over the configuration provider argument. In that case,
|
||||
Alternatively, you can provide a ``pydantic`` settings object over the configuration provider argument. In that case,
|
||||
the container will call ``config.from_pydantic()`` automatically:
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -215,23 +215,18 @@ the container will call ``config.from_pydantic()`` automatically:
|
|||
|
||||
.. note::
|
||||
|
||||
``Dependency Injector`` doesn't install ``pydantic-settings`` by default.
|
||||
``Dependency Injector`` doesn't install ``pydantic`` by default.
|
||||
|
||||
You can install the ``Dependency Injector`` with an extra dependency::
|
||||
|
||||
pip install dependency-injector[pydantic2]
|
||||
pip install dependency-injector[pydantic]
|
||||
|
||||
or install ``pydantic-settings`` directly::
|
||||
or install ``pydantic`` directly::
|
||||
|
||||
pip install pydantic-settings
|
||||
pip install pydantic
|
||||
|
||||
*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
|
||||
-------------------------
|
||||
|
||||
|
@ -366,19 +361,6 @@ See also: :ref:`configuration-strict-mode`.
|
|||
|
||||
assert container.config.section.option() is None
|
||||
|
||||
If you want to disable environment variables interpolation, pass ``envs_required=None``:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: templates.yml
|
||||
|
||||
template_string: 'Hello, ${name}!'
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> container.config.from_yaml("templates.yml", envs_required=None)
|
||||
>>> container.config.template_string()
|
||||
'Hello, ${name}!'
|
||||
|
||||
Mandatory and optional sources
|
||||
------------------------------
|
||||
|
||||
|
|
|
@ -61,12 +61,11 @@ 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
|
||||
resource shutdown.
|
||||
|
||||
Resource provider supports 4 types of initializers:
|
||||
Resource provider supports 3 types of initializers:
|
||||
|
||||
- Function
|
||||
- Context Manager
|
||||
- Generator (legacy)
|
||||
- Subclass of ``resources.Resource`` (legacy)
|
||||
- Generator
|
||||
- Subclass of ``resources.Resource``
|
||||
|
||||
Function initializer
|
||||
--------------------
|
||||
|
@ -104,44 +103,8 @@ you configure global resource:
|
|||
|
||||
Function initializer does not provide a way to specify custom resource shutdown.
|
||||
|
||||
Context Manager initializer
|
||||
---------------------------
|
||||
|
||||
This is an extension to the Function initializer. Resource provider automatically detects if the initializer returns a
|
||||
context manager and uses it to manage the resource lifecycle.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
class DatabaseConnection:
|
||||
def __init__(self, host, port, user, password):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.password = password
|
||||
|
||||
def __enter__(self):
|
||||
print(f"Connecting to {self.host}:{self.port} as {self.user}")
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
print("Closing connection")
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
db = providers.Resource(
|
||||
DatabaseConnection,
|
||||
host=config.db.host,
|
||||
port=config.db.port,
|
||||
user=config.db.user,
|
||||
password=config.db.password,
|
||||
)
|
||||
|
||||
Generator initializer (legacy)
|
||||
------------------------------
|
||||
Generator initializer
|
||||
---------------------
|
||||
|
||||
Resource provider can use 2-step generators:
|
||||
|
||||
|
@ -191,13 +154,8 @@ object is not mandatory. You can leave ``yield`` statement empty:
|
|||
argument2=...,
|
||||
)
|
||||
|
||||
.. note::
|
||||
|
||||
Generator initializers are automatically wrapped with ``contextmanager`` or ``asynccontextmanager`` decorator when
|
||||
provided to a ``Resource`` provider.
|
||||
|
||||
Subclass initializer (legacy)
|
||||
-----------------------------
|
||||
Subclass initializer
|
||||
--------------------
|
||||
|
||||
You can create resource initializer by implementing a subclass of the ``resources.Resource``:
|
||||
|
||||
|
@ -252,72 +210,6 @@ first argument.
|
|||
|
||||
.. _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
|
||||
---------------------------------------------------
|
||||
|
||||
|
@ -371,11 +263,10 @@ Asynchronous function initializer:
|
|||
argument2=...,
|
||||
)
|
||||
|
||||
Asynchronous Context Manager initializer:
|
||||
Asynchronous generator initializer:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@asynccontextmanager
|
||||
async def init_async_resource(argument1=..., argument2=...):
|
||||
connection = await connect()
|
||||
yield connection
|
||||
|
@ -467,54 +358,5 @@ See also:
|
|||
- Wiring :ref:`async-injections-wiring`
|
||||
- :ref:`fastapi-redis-example`
|
||||
|
||||
ASGI Lifespan Protocol Support
|
||||
------------------------------
|
||||
|
||||
The :mod:`dependency_injector.ext.starlette` module provides a :class:`~dependency_injector.ext.starlette.Lifespan`
|
||||
class that integrates resource providers with ASGI applications using the `Lifespan Protocol`_. This allows resources to
|
||||
be automatically initialized at application startup and properly shut down when the application stops.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
from dependency_injector.ext.starlette import Lifespan
|
||||
from fastapi import FastAPI, Request, Depends, APIRouter
|
||||
|
||||
class Connection: ...
|
||||
|
||||
@asynccontextmanager
|
||||
async def init_database():
|
||||
print("opening database connection")
|
||||
yield Connection()
|
||||
print("closing database connection")
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/")
|
||||
@inject
|
||||
async def index(request: Request, db: Connection = Depends(Provide["db"])):
|
||||
# use the database connection here
|
||||
return "OK!"
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
__self__ = providers.Self()
|
||||
db = providers.Resource(init_database)
|
||||
lifespan = providers.Singleton(Lifespan, __self__)
|
||||
app = providers.Singleton(FastAPI, lifespan=lifespan)
|
||||
_include_router = providers.Resource(
|
||||
app.provided.include_router.call(),
|
||||
router,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
container = Container()
|
||||
app = container.app()
|
||||
uvicorn.run(app, host="localhost", port=8000)
|
||||
|
||||
.. _Lifespan Protocol: https://asgi.readthedocs.io/en/latest/specs/lifespan.html
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -24,7 +24,7 @@ returns it on the rest of the calls.
|
|||
|
||||
.. note::
|
||||
|
||||
``Singleton`` provider makes dependencies injection only when it creates an object. When an object
|
||||
``Singleton`` provider makes dependencies injection only when creates an object. When an object
|
||||
is created and memorized ``Singleton`` provider just returns it without applying injections.
|
||||
|
||||
Specialization of the provided type and abstract singletons work the same like like for the
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
.. 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:
|
||||
|
||||
.. code-block:: http
|
||||
.. code-block:: json
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 844
|
||||
|
@ -596,7 +596,7 @@ and make a request to the API in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: http
|
||||
.. code-block:: json
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 492
|
||||
|
@ -859,6 +859,4 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -18,7 +18,7 @@ In this tutorial we will use:
|
|||
|
||||
- Python 3
|
||||
- Docker
|
||||
- Docker Compose
|
||||
- Docker-compose
|
||||
|
||||
Start from the scratch or jump to the section:
|
||||
|
||||
|
@ -47,27 +47,28 @@ response it will log:
|
|||
Prerequisites
|
||||
-------------
|
||||
|
||||
We will use `docker compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
|
||||
We will use `Docker <https://www.docker.com/>`_ and
|
||||
`docker-compose <https://docs.docker.com/compose/>`_ in this tutorial. Let's check the versions:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker --version
|
||||
docker compose version
|
||||
docker-compose --version
|
||||
|
||||
The output should look something like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Docker version 27.3.1, build ce12230
|
||||
Docker Compose version v2.29.7
|
||||
Docker version 20.10.5, build 55c4c88
|
||||
docker-compose version 1.29.0, build 07737305
|
||||
|
||||
.. note::
|
||||
|
||||
If you don't have ``Docker`` or ``docker compose`` you need to install them before proceeding.
|
||||
If you don't have ``Docker`` or ``docker-compose`` you need to install them before proceeding.
|
||||
Follow these installation guides:
|
||||
|
||||
- `Install Docker <https://docs.docker.com/get-docker/>`_
|
||||
- `Install docker compose <https://docs.docker.com/compose/install/>`_
|
||||
- `Install docker-compose <https://docs.docker.com/compose/install/>`_
|
||||
|
||||
The prerequisites are satisfied. Let's get started with the project layout.
|
||||
|
||||
|
@ -128,13 +129,13 @@ Put next lines into the ``requirements.txt`` file:
|
|||
pytest-cov
|
||||
|
||||
Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and
|
||||
specify how to run it. We will use ``python:3.13-bookworm`` as a base image.
|
||||
specify how to run it. We will use ``python:3.9-buster`` as a base image.
|
||||
|
||||
Put next lines into the ``Dockerfile`` file:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM python:3.13-bookworm
|
||||
FROM python:3.10-buster
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
|
@ -154,6 +155,8 @@ Put next lines into the ``docker-compose.yml`` file:
|
|||
|
||||
.. code-block:: yaml
|
||||
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
|
||||
monitor:
|
||||
|
@ -168,7 +171,7 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose build
|
||||
docker-compose build
|
||||
|
||||
The build process may take a couple of minutes. You should see something like this in the end:
|
||||
|
||||
|
@ -181,7 +184,7 @@ After the build is done run the container:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose up
|
||||
docker-compose up
|
||||
|
||||
The output should look like:
|
||||
|
||||
|
@ -458,7 +461,7 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose up
|
||||
docker-compose up
|
||||
|
||||
The output should look like:
|
||||
|
||||
|
@ -702,7 +705,7 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose up
|
||||
docker-compose up
|
||||
|
||||
You should see:
|
||||
|
||||
|
@ -810,7 +813,7 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose up
|
||||
docker-compose up
|
||||
|
||||
You should see:
|
||||
|
||||
|
@ -962,16 +965,15 @@ Run in the terminal:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
|
||||
docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
|
||||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||
platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
rootdir: /code
|
||||
plugins: cov-6.0.0, asyncio-0.24.0
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
plugins: asyncio-0.16.0, cov-3.0.0
|
||||
collected 2 items
|
||||
|
||||
monitoringdaemon/tests.py .. [100%]
|
||||
|
@ -1026,6 +1028,4 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -84,7 +84,7 @@ Create next structure in the project root directory. All files are empty. That's
|
|||
|
||||
Initial project layout:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: bash
|
||||
|
||||
./
|
||||
├── 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:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: bash
|
||||
|
||||
dependency-injector
|
||||
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
|
||||
``fixtures.py`` inside of it:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 2-3
|
||||
|
||||
./
|
||||
|
@ -205,13 +205,13 @@ Now run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: bash
|
||||
|
||||
OK
|
||||
|
||||
Check that files ``movies.csv`` and ``movies.db`` have appeared in the ``data/`` folder:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: bash
|
||||
: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:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 10
|
||||
|
||||
./
|
||||
|
@ -356,7 +356,7 @@ Let's move on to the finders.
|
|||
|
||||
Create the ``finders.py`` in the ``movies`` package:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: bash
|
||||
: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:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 12
|
||||
|
||||
./
|
||||
|
@ -613,7 +613,7 @@ Run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: plain
|
||||
|
||||
Francis Lawrence movies:
|
||||
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
|
||||
|
@ -752,7 +752,7 @@ Run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: plain
|
||||
|
||||
Francis Lawrence movies:
|
||||
- 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:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: plain
|
||||
|
||||
Francis Lawrence movies:
|
||||
- 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:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: bash
|
||||
:emphasize-lines: 13
|
||||
|
||||
./
|
||||
|
@ -911,7 +911,7 @@ Create ``tests.py`` in the ``movies`` package:
|
|||
and put next into it:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 41,50
|
||||
:emphasize-lines: 36,51
|
||||
|
||||
"""Tests module."""
|
||||
|
||||
|
@ -941,18 +941,13 @@ and put next into it:
|
|||
return container
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def finder_mock(container):
|
||||
def test_movies_directed_by(container):
|
||||
finder_mock = mock.Mock()
|
||||
finder_mock.find_all.return_value = [
|
||||
container.movie("The 33", 2015, "Patricia Riggen"),
|
||||
container.movie("The Jungle Book", 2016, "Jon Favreau"),
|
||||
]
|
||||
|
||||
return finder_mock
|
||||
|
||||
|
||||
def test_movies_directed_by(container, finder_mock):
|
||||
with container.finder.override(finder_mock):
|
||||
lister = container.lister()
|
||||
movies = lister.movies_directed_by("Jon Favreau")
|
||||
|
@ -961,7 +956,13 @@ and put next into it:
|
|||
assert movies[0].title == "The Jungle Book"
|
||||
|
||||
|
||||
def test_movies_released_in(container, finder_mock):
|
||||
def test_movies_released_in(container):
|
||||
finder_mock = mock.Mock()
|
||||
finder_mock.find_all.return_value = [
|
||||
container.movie("The 33", 2015, "Patricia Riggen"),
|
||||
container.movie("The Jungle Book", 2016, "Jon Favreau"),
|
||||
]
|
||||
|
||||
with container.finder.override(finder_mock):
|
||||
lister = container.lister()
|
||||
movies = lister.movies_released_in(2015)
|
||||
|
@ -977,7 +978,7 @@ Run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block::
|
||||
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: cov-3.0.0
|
||||
|
@ -994,9 +995,9 @@ You should see:
|
|||
movies/entities.py 7 1 86%
|
||||
movies/finders.py 26 13 50%
|
||||
movies/listers.py 8 0 100%
|
||||
movies/tests.py 24 0 100%
|
||||
movies/tests.py 23 0 100%
|
||||
------------------------------------------
|
||||
TOTAL 90 30 67%
|
||||
TOTAL 89 30 66%
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -1033,6 +1034,4 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -280,7 +280,7 @@ Now let's fill in the layout.
|
|||
|
||||
Put next into the ``base.html``:
|
||||
|
||||
.. code-block:: jinja
|
||||
.. code-block:: html
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
@ -313,7 +313,7 @@ And put something to the index page.
|
|||
|
||||
Put next into the ``index.html``:
|
||||
|
||||
.. code-block:: jinja
|
||||
.. code-block:: html
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
|
@ -998,6 +998,5 @@ What's next?
|
|||
- Know more about the :ref:`providers`
|
||||
- Go to the :ref:`contents`
|
||||
|
||||
.. include:: ../sponsor.rst
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -64,7 +64,7 @@ FastAPI example:
|
|||
|
||||
@app.api_route("/")
|
||||
@inject
|
||||
async def index(service: Annotated[Service, Depends(Provide[Container.service])]):
|
||||
async def index(service: Service = Depends(Provide[Container.service])):
|
||||
value = await service.process()
|
||||
return {"result": value}
|
||||
|
||||
|
@ -127,7 +127,6 @@ To inject the provider itself use ``Provide[foo.provider]``:
|
|||
def foo(bar_provider: Factory[Bar] = Provide[Container.bar.provider]):
|
||||
bar = bar_provider(argument="baz")
|
||||
...
|
||||
|
||||
You can also use ``Provider[foo]`` for injecting the provider itself:
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -255,43 +254,13 @@ To inject a container use special identifier ``<container>``:
|
|||
Making 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:
|
||||
You can use wiring to make injections into modules and class attributes.
|
||||
|
||||
.. literalinclude:: ../examples/wiring/example_attribute.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
: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:
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -632,36 +601,6 @@ or with a single container ``register_loader_containers(container)`` multiple ti
|
|||
To unregister a container use ``unregister_loader_containers(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
|
||||
---------------------------------
|
||||
|
||||
|
@ -693,6 +632,5 @@ Take a look at other application examples:
|
|||
- :ref:`fastapi-example`
|
||||
- :ref:`fastapi-redis-example`
|
||||
- :ref:`fastapi-sqlalchemy-example`
|
||||
- :ref:`fastdepends-example`
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -98,9 +98,8 @@ The output should be something like:
|
|||
|
||||
.. code-block::
|
||||
|
||||
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0, aiohttp-1.0.5
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: asyncio-0.16.0, anyio-3.3.4, aiohttp-0.3.0, cov-3.0.0
|
||||
collected 3 items
|
||||
|
||||
giphynavigator/tests.py ... [100%]
|
||||
|
|
|
@ -3,15 +3,11 @@
|
|||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from giphynavigator.application import create_app
|
||||
from giphynavigator.giphy import GiphyClient
|
||||
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = create_app()
|
||||
|
@ -19,9 +15,9 @@ def app():
|
|||
app.container.unwire()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def client(app, aiohttp_client):
|
||||
return await aiohttp_client(app)
|
||||
@pytest.fixture
|
||||
def client(app, aiohttp_client, loop):
|
||||
return loop.run_until_complete(aiohttp_client(app))
|
||||
|
||||
|
||||
async def test_index(client, app):
|
||||
|
|
|
@ -2,5 +2,4 @@ dependency-injector
|
|||
aiohttp
|
||||
pyyaml
|
||||
pytest-aiohttp
|
||||
pytest-asyncio
|
||||
pytest-cov
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM python:3.13-bookworm
|
||||
FROM python:3.10-buster
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@ Build the Docker image:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose build
|
||||
docker-compose build
|
||||
|
||||
Run the docker-compose environment:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose up
|
||||
docker-compose up
|
||||
|
||||
The output should be something like:
|
||||
|
||||
|
@ -59,16 +59,15 @@ To run the tests do:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
|
||||
docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
|
||||
|
||||
The output should be something like:
|
||||
|
||||
.. code-block::
|
||||
|
||||
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||
platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
rootdir: /code
|
||||
plugins: cov-6.0.0, asyncio-0.24.0
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
plugins: asyncio-0.16.0, cov-3.0.0
|
||||
collected 2 items
|
||||
|
||||
monitoringdaemon/tests.py .. [100%]
|
||||
|
|
|
@ -61,7 +61,7 @@ async def test_example_monitor(container, caplog):
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dispatcher(container, caplog):
|
||||
async def test_dispatcher(container, caplog, event_loop):
|
||||
caplog.set_level("INFO")
|
||||
|
||||
example_monitor_mock = mock.AsyncMock()
|
||||
|
@ -72,7 +72,6 @@ async def test_dispatcher(container, caplog):
|
|||
httpbin_monitor=httpbin_monitor_mock,
|
||||
):
|
||||
dispatcher = container.dispatcher()
|
||||
event_loop = asyncio.get_running_loop()
|
||||
event_loop.create_task(dispatcher.start())
|
||||
await asyncio.sleep(0.1)
|
||||
dispatcher.stop()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM python:3.13-bookworm
|
||||
FROM python:3.10-buster
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
|
|
|
@ -12,13 +12,13 @@ Build the Docker image:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose build
|
||||
docker-compose build
|
||||
|
||||
Run the docker-compose environment:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose up
|
||||
docker-compose up
|
||||
|
||||
The output should be something like:
|
||||
|
||||
|
@ -54,16 +54,16 @@ To run the tests do:
|
|||
|
||||
.. 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:
|
||||
|
||||
.. code-block::
|
||||
|
||||
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||
platform linux -- Python 3.10.9, pytest-7.2.0, pluggy-1.0.0
|
||||
rootdir: /code
|
||||
plugins: cov-6.0.0, asyncio-0.24.0, anyio-4.7.0
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
plugins: cov-4.0.0, asyncio-0.20.3
|
||||
collected 1 item
|
||||
|
||||
fastapiredis/tests.py . [100%]
|
||||
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
"""Application module."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
from fastapi import FastAPI, Depends
|
||||
|
||||
from .containers import Container
|
||||
from .services import Service
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.api_route("/")
|
||||
@inject
|
||||
async def index(
|
||||
service: Annotated[Service, Depends(Provide[Container.service])]
|
||||
) -> dict[str, str]:
|
||||
async def index(service: Service = Depends(Provide[Container.service])):
|
||||
value = await service.process()
|
||||
return {"result": value}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from typing import AsyncIterator
|
||||
|
||||
from redis.asyncio import from_url, Redis
|
||||
from aioredis import from_url, Redis
|
||||
|
||||
|
||||
async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Services module."""
|
||||
|
||||
from redis.asyncio import Redis
|
||||
from aioredis import Redis
|
||||
|
||||
|
||||
class Service:
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from httpx import AsyncClient
|
||||
|
||||
from .application import app, container
|
||||
from .services import Service
|
||||
|
@ -11,10 +11,7 @@ from .services import Service
|
|||
|
||||
@pytest.fixture
|
||||
def client(event_loop):
|
||||
client = AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://test",
|
||||
)
|
||||
client = AsyncClient(app=app, base_url="http://test")
|
||||
yield client
|
||||
event_loop.run_until_complete(client.aclose())
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
dependency-injector
|
||||
fastapi
|
||||
uvicorn
|
||||
redis>=4.2
|
||||
aioredis
|
||||
|
||||
# For testing:
|
||||
pytest
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
|
||||
from fastapi import FastAPI, Depends
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
|
@ -21,9 +18,7 @@ app = FastAPI()
|
|||
|
||||
@app.api_route("/")
|
||||
@inject
|
||||
async def index(
|
||||
service: Annotated[Service, Depends(Provide[Container.service])]
|
||||
) -> dict[str, str]:
|
||||
async def index(service: Service = Depends(Provide[Container.service])):
|
||||
result = await service.process()
|
||||
return {"result": result}
|
||||
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from httpx import AsyncClient
|
||||
|
||||
from fastapi_di_example import app, container, Service
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def client():
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://test",
|
||||
) as client:
|
||||
@pytest.fixture
|
||||
async def client(event_loop):
|
||||
async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
yield client
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM python:3.13-bookworm
|
||||
FROM python:3.10-buster
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV HOST=0.0.0.0
|
||||
|
|
|
@ -15,13 +15,13 @@ Build the Docker image:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose build
|
||||
docker-compose build
|
||||
|
||||
Run the docker-compose environment:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker compose up
|
||||
docker-compose up
|
||||
|
||||
The output should be something like:
|
||||
|
||||
|
@ -67,15 +67,15 @@ To run the tests do:
|
|||
|
||||
.. 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:
|
||||
|
||||
.. code-block::
|
||||
|
||||
platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
|
||||
platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
rootdir: /code
|
||||
plugins: cov-6.0.0, anyio-4.7.0
|
||||
plugins: cov-3.0.0
|
||||
collected 7 items
|
||||
|
||||
webapp/tests.py ....... [100%]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
dependency-injector
|
||||
fastapi[standard]
|
||||
fastapi
|
||||
uvicorn
|
||||
pyyaml
|
||||
sqlalchemy
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
"""Endpoints module."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Response, status
|
||||
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from .containers import Container
|
||||
from .repositories import NotFoundError
|
||||
from .services import UserService
|
||||
from .repositories import NotFoundError
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
@ -16,7 +13,7 @@ router = APIRouter()
|
|||
@router.get("/users")
|
||||
@inject
|
||||
def get_list(
|
||||
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
|
||||
user_service: UserService = Depends(Provide[Container.user_service]),
|
||||
):
|
||||
return user_service.get_users()
|
||||
|
||||
|
@ -25,7 +22,7 @@ def get_list(
|
|||
@inject
|
||||
def get_by_id(
|
||||
user_id: int,
|
||||
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
|
||||
user_service: UserService = Depends(Provide[Container.user_service]),
|
||||
):
|
||||
try:
|
||||
return user_service.get_user_by_id(user_id)
|
||||
|
@ -36,7 +33,7 @@ def get_by_id(
|
|||
@router.post("/users", status_code=status.HTTP_201_CREATED)
|
||||
@inject
|
||||
def add(
|
||||
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
|
||||
user_service: UserService = Depends(Provide[Container.user_service]),
|
||||
):
|
||||
return user_service.create_user()
|
||||
|
||||
|
@ -45,8 +42,8 @@ def add(
|
|||
@inject
|
||||
def remove(
|
||||
user_id: int,
|
||||
user_service: Annotated[UserService, Depends(Provide[Container.user_service])],
|
||||
) -> Response:
|
||||
user_service: UserService = Depends(Provide[Container.user_service]),
|
||||
):
|
||||
try:
|
||||
user_service.delete_user_by_id(user_id)
|
||||
except NotFoundError:
|
||||
|
|
|
@ -101,9 +101,9 @@ The output should be something like:
|
|||
|
||||
.. code-block::
|
||||
|
||||
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0, aiohttp-1.0.5
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: asyncio-0.16.0, cov-3.0.0
|
||||
collected 3 items
|
||||
|
||||
giphynavigator/tests.py ... [100%]
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
"""Endpoints module."""
|
||||
|
||||
from typing import Annotated, List
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from .containers import Container
|
||||
from .services import SearchService
|
||||
from .containers import Container
|
||||
|
||||
|
||||
class Gif(BaseModel):
|
||||
|
@ -27,15 +26,11 @@ router = APIRouter()
|
|||
@router.get("/", response_model=Response)
|
||||
@inject
|
||||
async def index(
|
||||
default_query: Annotated[str, Depends(Provide[Container.config.default.query])],
|
||||
default_limit: Annotated[
|
||||
int, Depends(Provide[Container.config.default.limit.as_int()])
|
||||
],
|
||||
search_service: Annotated[
|
||||
SearchService, Depends(Provide[Container.search_service])
|
||||
],
|
||||
query: str | None = None,
|
||||
limit: int | None = None,
|
||||
query: Optional[str] = None,
|
||||
limit: Optional[str] = None,
|
||||
default_query: str = Depends(Provide[Container.config.default.query]),
|
||||
default_limit: int = Depends(Provide[Container.config.default.limit.as_int()]),
|
||||
search_service: SearchService = Depends(Provide[Container.search_service]),
|
||||
):
|
||||
query = query or default_query
|
||||
limit = limit or default_limit
|
||||
|
|
|
@ -3,19 +3,15 @@
|
|||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from httpx import AsyncClient
|
||||
|
||||
from giphynavigator.application import app
|
||||
from giphynavigator.giphy import GiphyClient
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
@pytest.fixture
|
||||
async def client():
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://test",
|
||||
) as client:
|
||||
async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
yield client
|
||||
|
||||
|
||||
|
|
|
@ -81,9 +81,8 @@ The output should be something like:
|
|||
|
||||
.. code-block::
|
||||
|
||||
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||
plugins: cov-6.0.0, flask-1.3.0
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: cov-3.0.0, flask-1.2.0
|
||||
collected 2 items
|
||||
|
||||
githubnavigator/tests.py .. [100%]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Application module."""
|
||||
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap4
|
||||
from flask_bootstrap import Bootstrap
|
||||
|
||||
from .containers import Container
|
||||
from .blueprints import example
|
||||
|
@ -15,7 +15,7 @@ def create_app() -> Flask:
|
|||
app.container = container
|
||||
app.register_blueprint(example.blueprint)
|
||||
|
||||
bootstrap = Bootstrap4()
|
||||
bootstrap = Bootstrap()
|
||||
bootstrap.init_app(app)
|
||||
|
||||
return app
|
||||
|
|
|
@ -81,9 +81,8 @@ The output should be something like:
|
|||
|
||||
.. code-block::
|
||||
|
||||
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||
plugins: cov-6.0.0, flask-1.3.0
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: cov-3.0.0, flask-1.2.0
|
||||
collected 2 items
|
||||
|
||||
githubnavigator/tests.py .. [100%]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Application module."""
|
||||
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap4
|
||||
from flask_bootstrap import Bootstrap
|
||||
|
||||
from .containers import Container
|
||||
from . import views
|
||||
|
@ -15,7 +15,7 @@ def create_app() -> Flask:
|
|||
app.container = container
|
||||
app.add_url_rule("/", "index", views.index)
|
||||
|
||||
bootstrap = Bootstrap4()
|
||||
bootstrap = Bootstrap()
|
||||
bootstrap.init_app(app)
|
||||
|
||||
return app
|
||||
|
|
|
@ -58,8 +58,8 @@ The output should be something like:
|
|||
|
||||
.. code-block::
|
||||
|
||||
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||
plugins: cov-6.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
|
||||
collected 2 items
|
||||
|
||||
movies/tests.py .. [100%]
|
||||
|
|
|
@ -18,9 +18,10 @@ SQLITE_FILE = DIR / "movies.db"
|
|||
|
||||
|
||||
def create_csv(movies_data, path):
|
||||
with open(path, "w", newline="") as opened_file:
|
||||
with open(path, "w") as opened_file:
|
||||
writer = csv.writer(opened_file)
|
||||
writer.writerows(movies_data)
|
||||
for row in movies_data:
|
||||
writer.writerow(row)
|
||||
|
||||
|
||||
def create_sqlite(movies_data, path):
|
||||
|
|
|
@ -29,7 +29,7 @@ class CsvMovieFinder(MovieFinder):
|
|||
super().__init__(movie_factory)
|
||||
|
||||
def find_all(self) -> List[Movie]:
|
||||
with open(self._csv_file_path, newline="") as csv_file:
|
||||
with open(self._csv_file_path) as csv_file:
|
||||
csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
|
||||
return [self._movie_factory(*row) for row in csv_reader]
|
||||
|
||||
|
|
|
@ -26,18 +26,13 @@ def container():
|
|||
return container
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def finder_mock(container):
|
||||
def test_movies_directed_by(container):
|
||||
finder_mock = mock.Mock()
|
||||
finder_mock.find_all.return_value = [
|
||||
container.movie("The 33", 2015, "Patricia Riggen"),
|
||||
container.movie("The Jungle Book", 2016, "Jon Favreau"),
|
||||
]
|
||||
|
||||
return finder_mock
|
||||
|
||||
|
||||
def test_movies_directed_by(container, finder_mock):
|
||||
with container.finder.override(finder_mock):
|
||||
lister = container.lister()
|
||||
movies = lister.movies_directed_by("Jon Favreau")
|
||||
|
@ -46,7 +41,13 @@ def test_movies_directed_by(container, finder_mock):
|
|||
assert movies[0].title == "The Jungle Book"
|
||||
|
||||
|
||||
def test_movies_released_in(container, finder_mock):
|
||||
def test_movies_released_in(container):
|
||||
finder_mock = mock.Mock()
|
||||
finder_mock.find_all.return_value = [
|
||||
container.movie("The 33", 2015, "Patricia Riggen"),
|
||||
container.movie("The Jungle Book", 2016, "Jon Favreau"),
|
||||
]
|
||||
|
||||
with container.finder.override(finder_mock):
|
||||
lister = container.lister()
|
||||
movies = lister.movies_released_in(2015)
|
||||
|
|
|
@ -27,7 +27,7 @@ To run the application do:
|
|||
.. code-block:: bash
|
||||
|
||||
export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0
|
||||
sanic giphynavigator.application:create_app
|
||||
python -m giphynavigator
|
||||
|
||||
The output should be something like:
|
||||
|
||||
|
@ -98,9 +98,8 @@ The output should be something like:
|
|||
|
||||
.. code-block::
|
||||
|
||||
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
|
||||
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0
|
||||
asyncio: mode=Mode.STRICT, default_loop_scope=None
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: sanic-1.9.1, anyio-3.3.4, cov-3.0.0
|
||||
collected 3 items
|
||||
|
||||
giphynavigator/tests.py ... [100%]
|
||||
|
|
|
@ -8,8 +8,6 @@ from sanic import Sanic
|
|||
from giphynavigator.application import create_app
|
||||
from giphynavigator.giphy import GiphyClient
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
|
@ -19,7 +17,12 @@ def app():
|
|||
app.ctx.container.unwire()
|
||||
|
||||
|
||||
async def test_index(app):
|
||||
@pytest.fixture
|
||||
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.search.return_value = {
|
||||
"data": [
|
||||
|
@ -29,7 +32,7 @@ async def test_index(app):
|
|||
}
|
||||
|
||||
with app.ctx.container.giphy_client.override(giphy_client_mock):
|
||||
_, response = await app.asgi_client.get(
|
||||
response = await test_client.get(
|
||||
"/",
|
||||
params={
|
||||
"query": "test",
|
||||
|
@ -38,7 +41,7 @@ async def test_index(app):
|
|||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
data = response.json()
|
||||
assert data == {
|
||||
"query": "test",
|
||||
"limit": 10,
|
||||
|
@ -49,30 +52,30 @@ async def test_index(app):
|
|||
}
|
||||
|
||||
|
||||
async def test_index_no_data(app):
|
||||
async def test_index_no_data(app, test_client):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
"data": [],
|
||||
}
|
||||
|
||||
with app.ctx.container.giphy_client.override(giphy_client_mock):
|
||||
_, response = await app.asgi_client.get("/")
|
||||
response = await test_client.get("/")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
data = response.json()
|
||||
assert data["gifs"] == []
|
||||
|
||||
|
||||
async def test_index_default_params(app):
|
||||
async def test_index_default_params(app, test_client):
|
||||
giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
|
||||
giphy_client_mock.search.return_value = {
|
||||
"data": [],
|
||||
}
|
||||
|
||||
with app.ctx.container.giphy_client.override(giphy_client_mock):
|
||||
_, response = await app.asgi_client.get("/")
|
||||
response = await test_client.get("/")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
data = response.json()
|
||||
assert data["query"] == app.ctx.container.config.default.query()
|
||||
assert data["limit"] == app.ctx.container.config.default.limit()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
dependency-injector
|
||||
sanic
|
||||
sanic-testing
|
||||
sanic<=21.6
|
||||
aiohttp
|
||||
pyyaml
|
||||
pytest-sanic
|
||||
pytest-cov
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
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).
|
|
@ -1,59 +0,0 @@
|
|||
#!/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,
|
||||
)
|
|
@ -1,3 +0,0 @@
|
|||
dependency-injector
|
||||
starlette
|
||||
uvicorn
|
|
@ -3,7 +3,7 @@
|
|||
import os
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from pydantic import BaseSettings, Field
|
||||
|
||||
# Emulate environment variables
|
||||
os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
|
||||
|
@ -11,16 +11,15 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
|
|||
|
||||
|
||||
class AwsSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="aws_")
|
||||
|
||||
access_key_id: str
|
||||
secret_access_key: str
|
||||
access_key_id: str = Field(env="aws_access_key_id")
|
||||
secret_access_key: str = Field(env="aws_secret_access_key")
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
||||
aws: AwsSettings = AwsSettings()
|
||||
optional: str = "default_value"
|
||||
optional: str = Field(default="default_value")
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import os
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from pydantic import BaseSettings, Field
|
||||
|
||||
# Emulate environment variables
|
||||
os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
|
||||
|
@ -11,16 +11,15 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
|
|||
|
||||
|
||||
class AwsSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="aws_")
|
||||
|
||||
access_key_id: str
|
||||
secret_access_key: str
|
||||
access_key_id: str = Field(env="aws_access_key_id")
|
||||
secret_access_key: str = Field(env="aws_secret_access_key")
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
||||
aws: AwsSettings = AwsSettings()
|
||||
optional: str = "default_value"
|
||||
optional: str = Field(default="default_value")
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
|
|
@ -3,12 +3,10 @@
|
|||
import sys
|
||||
import logging
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from contextlib import contextmanager
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
|
||||
|
||||
@contextmanager
|
||||
def init_thread_pool(max_workers: int):
|
||||
thread_pool = ThreadPoolExecutor(max_workers=max_workers)
|
||||
yield thread_pool
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
from typing import Annotated
|
||||
|
||||
|
||||
class Service: ...
|
||||
class Service:
|
||||
...
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
@ -13,16 +13,9 @@ class Container(containers.DeclarativeContainer):
|
|||
service = providers.Factory(Service)
|
||||
|
||||
|
||||
# You can place marker on parameter default value
|
||||
@inject
|
||||
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: ...
|
||||
def main(service: Service = Provide[Container.service]) -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
"""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
122
pyproject.toml
|
@ -1,122 +0,0 @@
|
|||
[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,11 +1,9 @@
|
|||
cython==3.1.1
|
||||
setuptools
|
||||
cython==0.29.37
|
||||
pytest
|
||||
pytest-asyncio
|
||||
tox
|
||||
coverage
|
||||
flake8
|
||||
flake8-pyproject
|
||||
pydocstyle
|
||||
sphinx_autobuild
|
||||
pip
|
||||
|
@ -13,13 +11,10 @@ mypy
|
|||
pyyaml
|
||||
httpx
|
||||
fastapi
|
||||
pydantic
|
||||
pydantic-settings
|
||||
pydantic==1.10.17
|
||||
numpy
|
||||
scipy
|
||||
boto3
|
||||
mypy_boto3_s3
|
||||
typing_extensions
|
||||
fast-depends
|
||||
|
||||
-r requirements-ext.txt
|
||||
|
|
|
@ -4,6 +4,6 @@ sphinx
|
|||
# TODO: unpin jinja2 after sphinx update to 4+
|
||||
jinja2
|
||||
|
||||
sphinx-disqus==1.3.0
|
||||
-e git+https://github.com/rmk135/sphinxcontrib-disqus.git#egg=sphinxcontrib-disqus
|
||||
|
||||
-r requirements-ext.txt
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
flask
|
||||
werkzeug
|
||||
aiohttp
|
||||
flask==2.1.3
|
||||
werkzeug==2.2.2
|
||||
aiohttp==3.9.0b1
|
||||
|
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
@ -0,0 +1 @@
|
|||
six>=1.7.0,<=1.16.0
|
|
@ -2,13 +2,12 @@
|
|||
max_line_length = 120
|
||||
max_complexity = 10
|
||||
exclude = types.py
|
||||
extend-ignore = E203,E701
|
||||
per-file-ignores =
|
||||
examples/demo/*: F841
|
||||
examples/containers/traverse.py: E501
|
||||
examples/providers/async.py: F841
|
||||
examples/providers/async_overriding.py: F841
|
||||
examples/wiring/*: F821,F841
|
||||
examples/wiring/*: F841
|
||||
|
||||
[pydocstyle]
|
||||
ignore = D100,D101,D102,D105,D106,D107,D203,D213
|
||||
|
|
160
setup.py
160
setup.py
|
@ -1,55 +1,127 @@
|
|||
"""`Dependency injector` setup script."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from Cython.Build import cythonize
|
||||
from Cython.Compiler import Options
|
||||
from setuptools import Extension, setup
|
||||
from setuptools import setup, Extension
|
||||
|
||||
debug = os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1"
|
||||
limited_api = (
|
||||
os.environ.get("DEPENDENCY_INJECTOR_LIMITED_API") == "1"
|
||||
and sys.implementation.name == "cpython"
|
||||
)
|
||||
defined_macros = []
|
||||
options = {}
|
||||
compiler_directives = {
|
||||
"language_level": 3,
|
||||
"profile": debug,
|
||||
"linetrace": debug,
|
||||
}
|
||||
Options.annotate = debug
|
||||
|
||||
def _open(filename):
|
||||
if sys.version_info[0] == 2:
|
||||
return open(filename)
|
||||
return open(filename, encoding="utf-8")
|
||||
|
||||
|
||||
# Defining setup variables:
|
||||
defined_macros = dict()
|
||||
defined_macros["CYTHON_CLINE_IN_TRACEBACK"] = 0
|
||||
|
||||
# Getting description:
|
||||
with _open("README.rst") as readme_file:
|
||||
description = readme_file.read()
|
||||
|
||||
# Getting requirements:
|
||||
with _open("requirements.txt") as requirements_file:
|
||||
requirements = requirements_file.readlines()
|
||||
|
||||
# Getting version:
|
||||
with _open("src/dependency_injector/__init__.py") as init_file:
|
||||
version = re.search("__version__ = \"(.*?)\"", init_file.read()).group(1)
|
||||
|
||||
# Adding debug options:
|
||||
if debug:
|
||||
limited_api = False # line tracing is not part of the Limited API
|
||||
defined_macros.extend(
|
||||
[
|
||||
("CYTHON_TRACE", "1"),
|
||||
("CYTHON_TRACE_NOGIL", "1"),
|
||||
("CYTHON_CLINE_IN_TRACEBACK", "1"),
|
||||
]
|
||||
)
|
||||
if os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1":
|
||||
defined_macros["CYTHON_TRACE"] = 1
|
||||
defined_macros["CYTHON_TRACE_NOGIL"] = 1
|
||||
defined_macros["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(
|
||||
options=options,
|
||||
ext_modules=cythonize(
|
||||
[
|
||||
Extension(
|
||||
"*",
|
||||
["src/**/*.pyx"],
|
||||
define_macros=defined_macros,
|
||||
py_limited_api=limited_api,
|
||||
),
|
||||
setup(name="dependency-injector",
|
||||
version=version,
|
||||
description="Dependency injection framework for Python",
|
||||
long_description=description,
|
||||
author="Roman Mogylatov",
|
||||
author_email="rmogilatov@gmail.com",
|
||||
maintainer="Roman Mogylatov",
|
||||
maintainer_email="rmogilatov@gmail.com",
|
||||
url="https://github.com/ets-labs/python-dependency-injector",
|
||||
download_url="https://pypi.python.org/pypi/dependency_injector",
|
||||
packages=[
|
||||
"dependency_injector",
|
||||
"dependency_injector.ext",
|
||||
],
|
||||
annotate=debug,
|
||||
show_all_warnings=True,
|
||||
compiler_directives=compiler_directives,
|
||||
),
|
||||
)
|
||||
package_dir={
|
||||
"": "src",
|
||||
},
|
||||
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 :: 3",
|
||||
"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 :: 3.12",
|
||||
"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."""
|
||||
|
||||
__version__ = "4.48.1"
|
||||
__version__ = "4.42.0b1"
|
||||
"""Version number.
|
||||
|
||||
:type: str
|
||||
|
|
0
src/dependency_injector/__init__.pyi
Normal file
0
src/dependency_injector/__init__.pyi
Normal file
18401
src/dependency_injector/_cwiring.c
Normal file
18401
src/dependency_injector/_cwiring.c
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,18 +0,0 @@
|
|||
from typing import Any, Dict
|
||||
|
||||
from .providers import Provider
|
||||
|
||||
class DependencyResolver:
|
||||
def __init__(
|
||||
self,
|
||||
kwargs: Dict[str, Any],
|
||||
injections: Dict[str, Provider[Any]],
|
||||
closings: Dict[str, Provider[Any]],
|
||||
/,
|
||||
) -> None: ...
|
||||
def __enter__(self) -> Dict[str, Any]: ...
|
||||
def __exit__(self, *exc_info: Any) -> None: ...
|
||||
async def __aenter__(self) -> Dict[str, Any]: ...
|
||||
async def __aexit__(self, *exc_info: Any) -> None: ...
|
||||
|
||||
def _isawaitable(instance: Any) -> bool: ...
|
|
@ -1,93 +1,88 @@
|
|||
"""Wiring optimizations module."""
|
||||
|
||||
from asyncio import gather
|
||||
from collections.abc import Awaitable
|
||||
from inspect import CO_ITERABLE_COROUTINE
|
||||
from types import CoroutineType, GeneratorType
|
||||
import asyncio
|
||||
import collections.abc
|
||||
import functools
|
||||
import inspect
|
||||
import types
|
||||
|
||||
from .providers cimport Provider, Resource
|
||||
from .wiring import _Marker
|
||||
from . import providers
|
||||
from .wiring import _Marker, PatchedCallable
|
||||
|
||||
from .providers cimport Provider
|
||||
|
||||
|
||||
cdef inline bint _is_injectable(dict kwargs, object name):
|
||||
return name not in kwargs or isinstance(kwargs[name], _Marker)
|
||||
|
||||
|
||||
cdef class DependencyResolver:
|
||||
cdef dict kwargs
|
||||
def _get_sync_patched(fn, patched: PatchedCallable):
|
||||
@functools.wraps(fn)
|
||||
def _patched(*args, **kwargs):
|
||||
cdef object result
|
||||
cdef dict to_inject
|
||||
cdef dict injections
|
||||
cdef dict closings
|
||||
|
||||
def __init__(self, dict kwargs, dict injections, dict closings, /):
|
||||
self.kwargs = kwargs
|
||||
self.to_inject = kwargs.copy()
|
||||
self.injections = injections
|
||||
self.closings = closings
|
||||
|
||||
async def _await_injection(self, name: str, value: object, /) -> None:
|
||||
self.to_inject[name] = await value
|
||||
|
||||
cdef void _handle_injections_sync(self):
|
||||
cdef object arg_key
|
||||
cdef Provider provider
|
||||
|
||||
for name, provider in self.injections.items():
|
||||
if _is_injectable(self.kwargs, name):
|
||||
self.to_inject[name] = provider()
|
||||
to_inject = kwargs.copy()
|
||||
for arg_key, provider in patched.injections.items():
|
||||
if arg_key not in kwargs or isinstance(kwargs[arg_key], _Marker):
|
||||
to_inject[arg_key] = provider()
|
||||
|
||||
cdef list _handle_injections_async(self):
|
||||
cdef list to_await = []
|
||||
cdef Provider provider
|
||||
result = fn(*args, **to_inject)
|
||||
|
||||
for name, provider in self.injections.items():
|
||||
if _is_injectable(self.kwargs, name):
|
||||
provide = provider()
|
||||
|
||||
if provider.is_async_mode_enabled() or _isawaitable(provide):
|
||||
to_await.append(self._await_injection(name, provide))
|
||||
else:
|
||||
self.to_inject[name] = provide
|
||||
|
||||
return to_await
|
||||
|
||||
cdef void _handle_closings_sync(self):
|
||||
cdef Provider provider
|
||||
|
||||
for name, provider in self.closings.items():
|
||||
if _is_injectable(self.kwargs, name) and isinstance(provider, Resource):
|
||||
if patched.closing:
|
||||
for arg_key, provider in patched.closing.items():
|
||||
if arg_key in kwargs and not isinstance(kwargs[arg_key], _Marker):
|
||||
continue
|
||||
if not isinstance(provider, providers.Resource):
|
||||
continue
|
||||
provider.shutdown()
|
||||
|
||||
cdef list _handle_closings_async(self):
|
||||
cdef list to_await = []
|
||||
return result
|
||||
return _patched
|
||||
|
||||
|
||||
async def _async_inject(object fn, tuple args, dict kwargs, dict injections, dict closings):
|
||||
cdef object result
|
||||
cdef dict to_inject
|
||||
cdef list to_inject_await = []
|
||||
cdef list to_close_await = []
|
||||
cdef object arg_key
|
||||
cdef Provider provider
|
||||
|
||||
for name, provider in self.closings.items():
|
||||
if _is_injectable(self.kwargs, name) and isinstance(provider, Resource):
|
||||
if _isawaitable(shutdown := provider.shutdown()):
|
||||
to_await.append(shutdown)
|
||||
to_inject = kwargs.copy()
|
||||
for arg_key, provider in injections.items():
|
||||
if arg_key not in kwargs or isinstance(kwargs[arg_key], _Marker):
|
||||
provide = provider()
|
||||
if provider.is_async_mode_enabled():
|
||||
to_inject_await.append((arg_key, provide))
|
||||
elif _isawaitable(provide):
|
||||
to_inject_await.append((arg_key, provide))
|
||||
else:
|
||||
to_inject[arg_key] = provide
|
||||
|
||||
return to_await
|
||||
if to_inject_await:
|
||||
async_to_inject = await asyncio.gather(*(provide for _, provide in to_inject_await))
|
||||
for provide, (injection, _) in zip(async_to_inject, to_inject_await):
|
||||
to_inject[injection] = provide
|
||||
|
||||
def __enter__(self):
|
||||
self._handle_injections_sync()
|
||||
return self.to_inject
|
||||
result = await fn(*args, **to_inject)
|
||||
|
||||
def __exit__(self, *_):
|
||||
self._handle_closings_sync()
|
||||
if closings:
|
||||
for arg_key, provider in closings.items():
|
||||
if arg_key in kwargs and isinstance(kwargs[arg_key], _Marker):
|
||||
continue
|
||||
if not isinstance(provider, providers.Resource):
|
||||
continue
|
||||
shutdown = provider.shutdown()
|
||||
if _isawaitable(shutdown):
|
||||
to_close_await.append(shutdown)
|
||||
|
||||
async def __aenter__(self):
|
||||
if to_await := self._handle_injections_async():
|
||||
await gather(*to_await)
|
||||
return self.to_inject
|
||||
await asyncio.gather(*to_close_await)
|
||||
|
||||
async def __aexit__(self, *_):
|
||||
if to_await := self._handle_closings_async():
|
||||
await gather(*to_await)
|
||||
return result
|
||||
|
||||
|
||||
cdef bint _isawaitable(object instance):
|
||||
"""Return true if object can be passed to an ``await`` expression."""
|
||||
return (isinstance(instance, CoroutineType) or
|
||||
isinstance(instance, GeneratorType) and
|
||||
bool(instance.gi_code.co_flags & CO_ITERABLE_COROUTINE) or
|
||||
isinstance(instance, Awaitable))
|
||||
return (isinstance(instance, types.CoroutineType) or
|
||||
isinstance(instance, types.GeneratorType) and
|
||||
bool(instance.gi_code.co_flags & inspect.CO_ITERABLE_COROUTINE) or
|
||||
isinstance(instance, collections.abc.Awaitable))
|
||||
|
|
39151
src/dependency_injector/containers.c
Normal file
39151
src/dependency_injector/containers.c
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,28 +1,24 @@
|
|||
from pathlib import Path
|
||||
from typing import (
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable as _Callable,
|
||||
ClassVar,
|
||||
Dict,
|
||||
Generic,
|
||||
Type,
|
||||
Dict,
|
||||
List,
|
||||
Tuple,
|
||||
Optional,
|
||||
Any,
|
||||
Union,
|
||||
ClassVar,
|
||||
Callable as _Callable,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
Awaitable,
|
||||
overload,
|
||||
)
|
||||
|
||||
try:
|
||||
from typing import Self as _Self
|
||||
except ImportError:
|
||||
from typing_extensions import Self as _Self
|
||||
from .providers import Provider, Self, ProviderParent
|
||||
|
||||
from .providers import Provider, Resource, Self, ProviderParent
|
||||
|
||||
C_Base = TypeVar("C_Base", bound="Container")
|
||||
C = TypeVar("C", bound="DeclarativeContainer")
|
||||
|
@ -30,66 +26,51 @@ C_Overriding = TypeVar("C_Overriding", bound="DeclarativeContainer")
|
|||
T = TypeVar("T")
|
||||
TT = TypeVar("TT")
|
||||
|
||||
|
||||
class WiringConfiguration:
|
||||
modules: List[Any]
|
||||
packages: List[Any]
|
||||
from_package: Optional[str]
|
||||
auto_wire: bool
|
||||
keep_cache: bool
|
||||
def __init__(
|
||||
self,
|
||||
modules: Optional[Iterable[Any]] = None,
|
||||
packages: Optional[Iterable[Any]] = None,
|
||||
from_package: Optional[str] = None,
|
||||
auto_wire: bool = True,
|
||||
keep_cache: bool = False,
|
||||
) -> None: ...
|
||||
def __init__(self, modules: Optional[Iterable[Any]] = None, packages: Optional[Iterable[Any]] = None, from_package: Optional[str] = None, auto_wire: bool = True) -> None: ...
|
||||
|
||||
|
||||
class Container:
|
||||
provider_type: Type[Provider[Any]] = Provider
|
||||
providers: Dict[str, Provider[Any]]
|
||||
dependencies: Dict[str, Provider[Any]]
|
||||
overridden: Tuple[Provider[Any], ...]
|
||||
provider_type: Type[Provider] = Provider
|
||||
providers: Dict[str, Provider]
|
||||
dependencies: Dict[str, Provider]
|
||||
overridden: Tuple[Provider]
|
||||
wiring_config: WiringConfiguration
|
||||
auto_load_config: bool = True
|
||||
__self__: Self
|
||||
def __init__(self) -> None: ...
|
||||
def __deepcopy__(self, memo: Optional[Dict[str, Any]]) -> _Self: ...
|
||||
def __setattr__(self, name: str, value: Union[Provider[Any], Any]) -> None: ...
|
||||
def __getattr__(self, name: str) -> Provider[Any]: ...
|
||||
def __deepcopy__(self, memo: Optional[Dict[str, Any]]) -> Provider: ...
|
||||
def __setattr__(self, name: str, value: Union[Provider, Any]) -> None: ...
|
||||
def __getattr__(self, name: str) -> Provider: ...
|
||||
def __delattr__(self, name: str) -> None: ...
|
||||
def set_providers(self, **providers: Provider[Any]) -> None: ...
|
||||
def set_provider(self, name: str, provider: Provider[Any]) -> None: ...
|
||||
def set_providers(self, **providers: Provider): ...
|
||||
def set_provider(self, name: str, provider: Provider) -> None: ...
|
||||
def override(self, overriding: Union[Container, Type[Container]]) -> None: ...
|
||||
def override_providers(
|
||||
self, **overriding_providers: Union[Provider[Any], Any]
|
||||
) -> ProvidersOverridingContext[C_Base]: ...
|
||||
def override_providers(self, **overriding_providers: Union[Provider, Any]) -> ProvidersOverridingContext[C_Base]: ...
|
||||
def reset_last_overriding(self) -> None: ...
|
||||
def reset_override(self) -> None: ...
|
||||
def is_auto_wiring_enabled(self) -> bool: ...
|
||||
def wire(
|
||||
self,
|
||||
modules: Optional[Iterable[Any]] = None,
|
||||
packages: Optional[Iterable[Any]] = None,
|
||||
from_package: Optional[str] = None,
|
||||
) -> None: ...
|
||||
def wire(self, modules: Optional[Iterable[Any]] = None, packages: Optional[Iterable[Any]] = None, from_package: Optional[str] = None) -> None: ...
|
||||
def unwire(self) -> None: ...
|
||||
def init_resources(self, resource_type: Type[Resource[Any]] = Resource) -> Optional[Awaitable[None]]: ...
|
||||
def shutdown_resources(self, resource_type: Type[Resource[Any]] = Resource) -> Optional[Awaitable[None]]: ...
|
||||
def init_resources(self) -> Optional[Awaitable]: ...
|
||||
def shutdown_resources(self) -> Optional[Awaitable]: ...
|
||||
def load_config(self) -> None: ...
|
||||
def apply_container_providers_overridings(self) -> None: ...
|
||||
def reset_singletons(self) -> SingletonResetContext[C_Base]: ...
|
||||
def check_dependencies(self) -> None: ...
|
||||
def from_schema(self, schema: Dict[Any, Any]) -> None: ...
|
||||
def from_yaml_schema(
|
||||
self, filepath: Union[Path, str], loader: Optional[Any] = None
|
||||
) -> None: ...
|
||||
def from_yaml_schema(self, filepath: Union[Path, str], loader: Optional[Any]=None) -> None: ...
|
||||
def from_json_schema(self, filepath: Union[Path, str]) -> None: ...
|
||||
@overload
|
||||
def resolve_provider_name(self, provider: Provider[Any]) -> str: ...
|
||||
def resolve_provider_name(self, provider: Provider) -> str: ...
|
||||
@classmethod
|
||||
@overload
|
||||
def resolve_provider_name(cls, provider: Provider[Any]) -> str: ...
|
||||
def resolve_provider_name(cls, provider: Provider) -> str: ...
|
||||
@property
|
||||
def parent(self) -> Optional[ProviderParent]: ...
|
||||
@property
|
||||
|
@ -101,37 +82,40 @@ class Container:
|
|||
@overload
|
||||
def traverse(cls, types: Optional[Iterable[Type[TT]]] = None) -> Iterator[TT]: ...
|
||||
|
||||
|
||||
class DynamicContainer(Container): ...
|
||||
|
||||
|
||||
class DeclarativeContainer(Container):
|
||||
cls_providers: ClassVar[Dict[str, Provider[Any]]]
|
||||
inherited_providers: ClassVar[Dict[str, Provider[Any]]]
|
||||
def __init__(self, **overriding_providers: Union[Provider[Any], Any]) -> None: ...
|
||||
cls_providers: ClassVar[Dict[str, Provider]]
|
||||
inherited_providers: ClassVar[Dict[str, Provider]]
|
||||
def __init__(self, **overriding_providers: Union[Provider, Any]) -> None: ...
|
||||
@classmethod
|
||||
def override(cls, overriding: Union[Container, Type[Container]]) -> None: ...
|
||||
@classmethod
|
||||
def override_providers(
|
||||
cls, **overriding_providers: Union[Provider[Any], Any]
|
||||
) -> ProvidersOverridingContext[C_Base]: ...
|
||||
def override_providers(cls, **overriding_providers: Union[Provider, Any]) -> ProvidersOverridingContext[C_Base]: ...
|
||||
@classmethod
|
||||
def reset_last_overriding(cls) -> None: ...
|
||||
@classmethod
|
||||
def reset_override(cls) -> None: ...
|
||||
|
||||
|
||||
class ProvidersOverridingContext(Generic[T]):
|
||||
def __init__(
|
||||
self, container: T, overridden_providers: Iterable[Union[Provider[Any], Any]]
|
||||
) -> None: ...
|
||||
def __init__(self, container: T, overridden_providers: Iterable[Union[Provider, Any]]) -> None: ...
|
||||
def __enter__(self) -> T: ...
|
||||
def __exit__(self, *_: Any) -> None: ...
|
||||
|
||||
|
||||
class SingletonResetContext(Generic[T]):
|
||||
def __init__(self, container: T): ...
|
||||
def __enter__(self) -> T: ...
|
||||
def __exit__(self, *_: Any) -> None: ...
|
||||
|
||||
def override(
|
||||
container: Type[C],
|
||||
) -> _Callable[[Type[C_Overriding]], Type[C_Overriding]]: ...
|
||||
|
||||
def override(container: Type[C]) -> _Callable[[Type[C_Overriding]], Type[C_Overriding]]: ...
|
||||
|
||||
|
||||
def copy(container: Type[C]) -> _Callable[[Type[C_Overriding]], Type[C_Overriding]]: ...
|
||||
|
||||
|
||||
def is_container(instance: Any) -> bool: ...
|
||||
|
|
|
@ -1,37 +1,61 @@
|
|||
"""Containers module."""
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import copy as copy_module
|
||||
import json
|
||||
import sys
|
||||
import importlib
|
||||
import inspect
|
||||
import warnings
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
asyncio = None
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
yaml = None
|
||||
|
||||
import six
|
||||
|
||||
from . import providers, errors
|
||||
from .providers cimport __is_future_or_coroutine
|
||||
|
||||
|
||||
if sys.version_info[:2] >= (3, 6):
|
||||
from .wiring import wire, unwire
|
||||
else:
|
||||
def wire(*args, **kwargs):
|
||||
raise NotImplementedError("Wiring requires Python 3.6 or above")
|
||||
|
||||
def unwire(*args, **kwargs):
|
||||
raise NotImplementedError("Wiring requires Python 3.6 or above")
|
||||
|
||||
if sys.version_info[:2] == (3, 5):
|
||||
warnings.warn(
|
||||
"Dependency Injector will drop support of Python 3.5 after Jan 1st of 2022. "
|
||||
"This does not mean that there will be any immediate breaking changes, "
|
||||
"but tests will no longer be executed on Python 3.5, and bugs will not be addressed.",
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
class WiringConfiguration:
|
||||
"""Container wiring configuration."""
|
||||
|
||||
def __init__(self, modules=None, packages=None, from_package=None, auto_wire=True, keep_cache=False):
|
||||
def __init__(self, modules=None, packages=None, from_package=None, auto_wire=True):
|
||||
self.modules = [*modules] if modules else []
|
||||
self.packages = [*packages] if packages else []
|
||||
self.from_package = from_package
|
||||
self.auto_wire = auto_wire
|
||||
self.keep_cache = keep_cache
|
||||
|
||||
def __deepcopy__(self, memo=None):
|
||||
return self.__class__(self.modules, self.packages, self.from_package, self.auto_wire, self.keep_cache)
|
||||
return self.__class__(self.modules, self.packages, self.from_package, self.auto_wire)
|
||||
|
||||
|
||||
class Container:
|
||||
class Container(object):
|
||||
"""Abstract container."""
|
||||
|
||||
|
||||
|
@ -177,7 +201,7 @@ class DynamicContainer(Container):
|
|||
|
||||
:rtype: None
|
||||
"""
|
||||
for name, provider in providers.items():
|
||||
for name, provider in six.iteritems(providers):
|
||||
setattr(self, name, provider)
|
||||
|
||||
def set_provider(self, name, provider):
|
||||
|
@ -210,7 +234,7 @@ class DynamicContainer(Container):
|
|||
|
||||
self.overridden += (overriding,)
|
||||
|
||||
for name, provider in overriding.providers.items():
|
||||
for name, provider in six.iteritems(overriding.providers):
|
||||
try:
|
||||
getattr(self, name).override(provider)
|
||||
except AttributeError:
|
||||
|
@ -226,7 +250,7 @@ class DynamicContainer(Container):
|
|||
:rtype: None
|
||||
"""
|
||||
overridden_providers = []
|
||||
for name, overriding_provider in overriding_providers.items():
|
||||
for name, overriding_provider in six.iteritems(overriding_providers):
|
||||
container_provider = getattr(self, name)
|
||||
container_provider.override(overriding_provider)
|
||||
overridden_providers.append(container_provider)
|
||||
|
@ -242,7 +266,7 @@ class DynamicContainer(Container):
|
|||
|
||||
self.overridden = self.overridden[:-1]
|
||||
|
||||
for provider in self.providers.values():
|
||||
for provider in six.itervalues(self.providers):
|
||||
provider.reset_last_overriding()
|
||||
|
||||
def reset_override(self):
|
||||
|
@ -252,14 +276,14 @@ class DynamicContainer(Container):
|
|||
"""
|
||||
self.overridden = tuple()
|
||||
|
||||
for provider in self.providers.values():
|
||||
for provider in six.itervalues(self.providers):
|
||||
provider.reset_override()
|
||||
|
||||
def is_auto_wiring_enabled(self):
|
||||
"""Check if auto wiring is needed."""
|
||||
return self.wiring_config.auto_wire is True
|
||||
|
||||
def wire(self, modules=None, packages=None, from_package=None, keep_cache=None):
|
||||
def wire(self, modules=None, packages=None, from_package=None):
|
||||
"""Wire container providers with provided packages and modules.
|
||||
|
||||
:rtype: None
|
||||
|
@ -290,14 +314,10 @@ class DynamicContainer(Container):
|
|||
if not modules and not packages:
|
||||
return
|
||||
|
||||
if keep_cache is None:
|
||||
keep_cache = self.wiring_config.keep_cache
|
||||
|
||||
wire(
|
||||
container=self,
|
||||
modules=modules,
|
||||
packages=packages,
|
||||
keep_cache=keep_cache,
|
||||
)
|
||||
|
||||
if modules:
|
||||
|
@ -315,15 +335,11 @@ class DynamicContainer(Container):
|
|||
self.wired_to_modules.clear()
|
||||
self.wired_to_packages.clear()
|
||||
|
||||
def init_resources(self, resource_type=providers.Resource):
|
||||
def init_resources(self):
|
||||
"""Initialize all container resources."""
|
||||
|
||||
if not issubclass(resource_type, providers.Resource):
|
||||
raise TypeError("resource_type must be a subclass of Resource provider")
|
||||
|
||||
futures = []
|
||||
|
||||
for provider in self.traverse(types=[resource_type]):
|
||||
for provider in self.traverse(types=[providers.Resource]):
|
||||
resource = provider.init()
|
||||
|
||||
if __is_future_or_coroutine(resource):
|
||||
|
@ -332,12 +348,8 @@ class DynamicContainer(Container):
|
|||
if futures:
|
||||
return asyncio.gather(*futures)
|
||||
|
||||
def shutdown_resources(self, resource_type=providers.Resource):
|
||||
def shutdown_resources(self):
|
||||
"""Shutdown all container resources."""
|
||||
|
||||
if not issubclass(resource_type, providers.Resource):
|
||||
raise TypeError("resource_type must be a subclass of Resource provider")
|
||||
|
||||
def _independent_resources(resources):
|
||||
for resource in resources:
|
||||
for other_resource in resources:
|
||||
|
@ -368,7 +380,7 @@ class DynamicContainer(Container):
|
|||
for resource in resources_to_shutdown:
|
||||
resource.shutdown()
|
||||
|
||||
resources = list(self.traverse(types=[resource_type]))
|
||||
resources = list(self.traverse(types=[providers.Resource]))
|
||||
if any(resource.is_async_mode_enabled() for resource in resources):
|
||||
return _async_ordered_shutdown(resources)
|
||||
else:
|
||||
|
@ -483,13 +495,13 @@ class DeclarativeContainerMetaClass(type):
|
|||
|
||||
containers = {
|
||||
name: container
|
||||
for name, container in attributes.items()
|
||||
for name, container in six.iteritems(attributes)
|
||||
if is_container(container)
|
||||
}
|
||||
|
||||
cls_providers = {
|
||||
name: provider
|
||||
for name, provider in attributes.items()
|
||||
for name, provider in six.iteritems(attributes)
|
||||
if isinstance(provider, providers.Provider) and not isinstance(provider, providers.Self)
|
||||
}
|
||||
|
||||
|
@ -497,7 +509,7 @@ class DeclarativeContainerMetaClass(type):
|
|||
name: provider
|
||||
for base in bases
|
||||
if is_container(base) and base is not DynamicContainer
|
||||
for name, provider in base.providers.items()
|
||||
for name, provider in six.iteritems(base.providers)
|
||||
}
|
||||
|
||||
all_providers = {}
|
||||
|
@ -524,10 +536,10 @@ class DeclarativeContainerMetaClass(type):
|
|||
self.set_container(cls)
|
||||
cls.__self__ = self
|
||||
|
||||
for provider in cls.providers.values():
|
||||
for provider in six.itervalues(cls.providers):
|
||||
_check_provider_type(cls, provider)
|
||||
|
||||
for provider in cls.cls_providers.values():
|
||||
for provider in six.itervalues(cls.cls_providers):
|
||||
if isinstance(provider, providers.CHILD_PROVIDERS):
|
||||
provider.assign_parent(cls)
|
||||
|
||||
|
@ -629,7 +641,8 @@ class DeclarativeContainerMetaClass(type):
|
|||
return self
|
||||
|
||||
|
||||
class DeclarativeContainer(Container, metaclass=DeclarativeContainerMetaClass):
|
||||
@six.add_metaclass(DeclarativeContainerMetaClass)
|
||||
class DeclarativeContainer(Container):
|
||||
"""Declarative inversion of control container.
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -754,7 +767,7 @@ class DeclarativeContainer(Container, metaclass=DeclarativeContainerMetaClass):
|
|||
|
||||
cls.overridden += (overriding,)
|
||||
|
||||
for name, provider in overriding.cls_providers.items():
|
||||
for name, provider in six.iteritems(overriding.cls_providers):
|
||||
try:
|
||||
getattr(cls, name).override(provider)
|
||||
except AttributeError:
|
||||
|
@ -771,7 +784,7 @@ class DeclarativeContainer(Container, metaclass=DeclarativeContainerMetaClass):
|
|||
|
||||
cls.overridden = cls.overridden[:-1]
|
||||
|
||||
for provider in cls.providers.values():
|
||||
for provider in six.itervalues(cls.providers):
|
||||
provider.reset_last_overriding()
|
||||
|
||||
@classmethod
|
||||
|
@ -782,7 +795,7 @@ class DeclarativeContainer(Container, metaclass=DeclarativeContainerMetaClass):
|
|||
"""
|
||||
cls.overridden = tuple()
|
||||
|
||||
for provider in cls.providers.values():
|
||||
for provider in six.itervalues(cls.providers):
|
||||
provider.reset_override()
|
||||
|
||||
|
||||
|
@ -845,7 +858,7 @@ def copy(object base_container):
|
|||
"""
|
||||
def _get_memo_for_matching_names(new_providers, base_providers):
|
||||
memo = {}
|
||||
for new_provider_name, new_provider in new_providers.items():
|
||||
for new_provider_name, new_provider in six.iteritems(new_providers):
|
||||
if new_provider_name not in base_providers:
|
||||
continue
|
||||
source_provider = base_providers[new_provider_name]
|
||||
|
@ -864,7 +877,7 @@ def copy(object base_container):
|
|||
new_providers.update(providers.deepcopy(base_container.providers, memo))
|
||||
new_providers.update(providers.deepcopy(new_container.cls_providers, memo))
|
||||
|
||||
for name, provider in new_providers.items():
|
||||
for name, provider in six.iteritems(new_providers):
|
||||
setattr(new_container, name, provider)
|
||||
return new_container
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user