Compare commits

..

No commits in common. "master" and "4.41.0" have entirely different histories.

128 changed files with 210850 additions and 2689 deletions

10
.coveragerc Normal file
View File

@ -0,0 +1,10 @@
[run]
source = src/dependency_injector
omit = tests/unit
plugins = Cython.Coverage
[report]
show_missing = true
[html]
directory=reports/unittests/

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
github: rmk135

View File

@ -10,20 +10,20 @@ jobs:
tests: tests:
name: Run tests name: Run tests
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.13 python-version: 3.11
- run: pip install tox - run: pip install tox
- run: tox - run: tox
env: env:
TOXENV: 3.13 TOXENV: 3.11
linters: linters:
name: Run linters name: Run linters
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
strategy: strategy:
matrix: matrix:
toxenv: [flake8, pydocstyle, mypy, pylint] toxenv: [flake8, pydocstyle, mypy, pylint]
@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.13 python-version: 3.11
- run: pip install tox - run: pip install tox
- run: tox - run: tox
env: env:
@ -40,18 +40,15 @@ jobs:
build-sdist: build-sdist:
name: Build source tarball name: Build source tarball
needs: [tests, linters] needs: [tests, linters]
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.13 python-version: 3.11
- run: | - run: python setup.py sdist
python -m pip install --upgrade build - uses: actions/upload-artifact@v3
python -m build --sdist
- uses: actions/upload-artifact@v4
with: with:
name: cibw-sdist
path: ./dist/* path: ./dist/*
build-wheels: build-wheels:
@ -60,28 +57,43 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2019, macos-14] os: [ubuntu-22.04, windows-2019, macos-11]
env: env:
CIBW_SKIP: cp27-* CIBW_SKIP: cp27-win*
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Build wheels - name: Build wheels
uses: pypa/cibuildwheel@v2.20.0 uses: pypa/cibuildwheel@v2.11.3
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
with:
path: ./wheelhouse/*.whl
build-wheels-linux-aarch64:
name: Build wheels (ubuntu-22.04-aarch64)
needs: [tests, linters]
runs-on: ubuntu-22.04
steps:
- 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.11.3
env:
CIBW_ARCHS_LINUX: aarch64
- uses: actions/upload-artifact@v3
with: with:
name: cibw-wheels-x86-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl path: ./wheelhouse/*.whl
publish: publish:
name: Publish on PyPI name: Publish on PyPI
needs: [build-sdist, build-wheels] needs: [build-sdist, build-wheels, build-wheels-linux-aarch64]
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
with: with:
pattern: cibw-* name: artifact
path: dist path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1 - uses: pypa/gh-action-pypi-publish@release/v1
with: with:
user: __token__ user: __token__
@ -93,14 +105,14 @@ jobs:
publish-docs: publish-docs:
name: Publish docs name: Publish docs
needs: [publish] needs: [publish]
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.13 python-version: 3.11
- run: pip install awscli
- run: pip install -r requirements-doc.txt - run: pip install -r requirements-doc.txt
- run: pip install awscli
- run: pip install -e . - run: pip install -e .
- run: (cd docs && make clean html) - run: (cd docs && make clean html)
- run: | - run: |

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy: strategy:
matrix: matrix:
python-version: [3.7] python-version: [2.7, 3.5, 3.6, 3.7, pypy2.7, pypy3.9]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [3.8, 3.9, "3.10", 3.11, 3.12, 3.13] python-version: [3.8, 3.9, "3.10", 3.11]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
@ -36,32 +36,21 @@ jobs:
env: env:
TOXENV: ${{ matrix.python-version }} TOXENV: ${{ matrix.python-version }}
test-different-pydantic-versions:
name: Run tests with different pydantic versions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.12"
- run: pip install tox
- run: tox -e pydantic-v1,pydantic-v2
test-coverage: test-coverage:
name: Run tests with coverage name: Run tests with coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
DEPENDENCY_INJECTOR_DEBUG_MODE: 1 DEPENDENCY_INJECTOR_DEBUG_MODE: 1
PIP_VERBOSE: 1
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.12 python-version: 3.11
- run: pip install tox 'cython>=3,<4' - run: pip install tox cython
- run: tox -vv - run: make cythonize
- run: tox
env: env:
TOXENV: coveralls TOXENV: coveralls
@ -75,7 +64,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.13 python-version: 3.11
- run: pip install tox - run: pip install tox
- run: tox - run: tox
env: env:

14
.gitignore vendored
View File

@ -63,13 +63,13 @@ venv*/
# Vim Rope # Vim Rope
.ropeproject/ .ropeproject/
# Cython artifacts # C extensions
src/**/*.c src/dependency_injector/*.h
src/**/*.h src/dependency_injector/*.so
src/**/*.so src/dependency_injector/containers/*.h
src/**/*.html src/dependency_injector/containers/*.so
src/dependency_injector/providers/*.h
src/dependency_injector/providers/*.so
# Workspace for samples # Workspace for samples
.workspace/ .workspace/
.vscode/

49
.pylintrc Normal file
View 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

View File

@ -20,5 +20,3 @@ Dependency Injector Contributors
+ Ngo Thanh Loi (Leonn) (loingo95) + Ngo Thanh Loi (Leonn) (loingo95)
+ Thiago Hiromi (thiromi) + Thiago Hiromi (thiromi)
+ Felipe Rubio (krouw) + Felipe Rubio (krouw)
+ Anton Petrov (anton-petrov)
+ ZipFile (ZipFile)

View File

@ -1,4 +1,4 @@
Copyright (c) 2024, Roman Mogylatov Copyright (c) 2022, Roman Mogylatov
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@ -1,6 +1,14 @@
VERSION := $(shell python setup.py --version) VERSION := $(shell python setup.py --version)
export COVERAGE_RCFILE := pyproject.toml CYTHON_SRC := $(shell find src/dependency_injector -name '*.pyx')
CYTHON_DIRECTIVES = -Xlanguage_level=2
ifdef DEPENDENCY_INJECTOR_DEBUG_MODE
CYTHON_DIRECTIVES += -Xprofile=True
CYTHON_DIRECTIVES += -Xlinetrace=True
endif
clean: clean:
# Clean sources # Clean sources
@ -17,17 +25,21 @@ clean:
find examples -name '*.py[co]' -delete find examples -name '*.py[co]' -delete
find examples -name '__pycache__' -delete find examples -name '__pycache__' -delete
build: clean cythonize:
# Compile C extensions # Compile Cython to C
python setup.py build_ext --inplace cython -a $(CYTHON_DIRECTIVES) $(CYTHON_SRC)
# Move all Cython html reports # Move all Cython html reports
mkdir -p reports/cython/ mkdir -p reports/cython/
find src -name '*.html' -exec mv {} reports/cython/ \; find src -name '*.html' -exec mv {} reports/cython/ \;
build: clean cythonize
# Compile C extensions
python setup.py build_ext --inplace
docs-live: docs-live:
sphinx-autobuild docs docs/_build/html sphinx-autobuild docs docs/_build/html
install: uninstall clean build install: uninstall clean cythonize
pip install -ve . pip install -ve .
uninstall: uninstall:
@ -36,9 +48,9 @@ uninstall:
test: test:
# Unit tests with coverage report # Unit tests with coverage report
coverage erase coverage erase
coverage run -m pytest -c tests/.configs/pytest.ini coverage run --rcfile=./.coveragerc -m pytest -c tests/.configs/pytest.ini
coverage report coverage report --rcfile=./.coveragerc
coverage html coverage html --rcfile=./.coveragerc
check: check:
flake8 src/dependency_injector/ flake8 src/dependency_injector/
@ -49,9 +61,9 @@ check:
mypy tests/typing mypy tests/typing
test-publish: build test-publish: cythonize
# Create distributions # Create distributions
python -m build --sdist python setup.py sdist
# Upload distributions to PyPI # Upload distributions to PyPI
twine upload --repository testpypi dist/dependency-injector-$(VERSION)* twine upload --repository testpypi dist/dependency-injector-$(VERSION)*

View File

@ -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
View 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);
});

View File

@ -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>

View File

@ -33,7 +33,7 @@ sys.path.insert(0, os.path.abspath(".."))
extensions = [ extensions = [
"alabaster", "alabaster",
"sphinx.ext.autodoc", "sphinx.ext.autodoc",
"sphinx_disqus.disqus", "sphinxcontrib.disqus",
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@ -52,7 +52,7 @@ master_doc = "index"
# General information about the project. # General information about the project.
project = "Dependency Injector" project = "Dependency Injector"
copyright = "2024, Roman Mogylatov" copyright = "2022, Roman Mogylatov"
author = "Roman Mogylatov" author = "Roman Mogylatov"
# The version info for the project you"re documenting, acts as replacement for # The version info for the project you"re documenting, acts as replacement for
@ -147,9 +147,6 @@ html_favicon = "favicon.ico"
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"] html_static_path = ["_static"]
html_css_files = [
"custom.css",
]
# Add any extra paths that contain custom files (such as robots.txt or # Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied # .htaccess) here, relative to this directory. These files are copied
@ -309,5 +306,4 @@ html_theme_options = {
"description": "Dependency injection framework for Python by Roman Mogylatov", "description": "Dependency injection framework for Python by Roman Mogylatov",
"code_font_size": "10pt", "code_font_size": "10pt",
"analytics_id": "UA-67012059-1", "analytics_id": "UA-67012059-1",
"donate_url": "https://github.com/sponsors/rmk135",
} }

View File

@ -78,6 +78,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/aiohttp>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -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>`_. You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-multiple-containers>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -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>`_. You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-single-container>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -17,6 +17,4 @@ Listing of ``boto3_session_example.py``:
.. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py .. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py
:language: python :language: python
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -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>`_. You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -94,6 +94,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/django>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -95,6 +95,4 @@ See also:
- Resource provider :ref:`resource-async-initializers` - Resource provider :ref:`resource-async-initializers`
- Wiring :ref:`async-injections-wiring` - Wiring :ref:`async-injections-wiring`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -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>`_. The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -76,6 +76,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -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>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask-blueprints>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -84,6 +84,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/flask>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -77,6 +77,4 @@ Sources
Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_. Explore the sources on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/sanic>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -9,11 +9,11 @@ Dependency Injector --- Dependency injection framework for Python
:description: Dependency Injector is a dependency injection framework :description: Dependency Injector is a dependency injection framework
for Python. It helps to maintain you application structure. for Python. It helps to maintain you application structure.
It was designed to be unified, developer-friendly It was designed to be unified, developer-friendly
tool that helps to implement dependency injection design tool that helps to implement dependency injection design
pattern in formal, pretty, Pythonic way. Dependency Injector pattern in formal, pretty, Pythonic way. Dependency Injector
provides implementations of such popular design patterns provides implementations of such popular design patterns
like IoC container, Factory and Singleton. Dependency like IoC container, Factory and Singleton. Dependency
Injector providers are implemented as C extension types Injector providers are implemented as C extension types
using Cython. using Cython.
.. _index: .. _index:

View File

@ -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://github.com/ets-labs/python-dependency-injector
+ https://pypi.org/project/dependency-injector/ + https://pypi.org/project/dependency-injector/
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -1,73 +1,12 @@
Changelog Changelog
========= =========
This document describes all the changes in *Dependency Injector* framework This document describes all the changes in *Dependency Injector* framework
that were made in every particular version. that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_ follows `Semantic versioning`_
4.46.0
------
- Add option to disable env var interpolation in configs (`#861 <https://github.com/ets-labs/python-dependency-injector/pull/861>`_)
- Fix ``Closing`` dependency resolution (`#852 <https://github.com/ets-labs/python-dependency-injector/pull/852>`_)
- Add support for ``inspect.iscoroutinefunction()`` in ``Coroutine`` provider (`#830 <https://github.com/ets-labs/python-dependency-injector/pull/830>`_)
- Fix broken wiring of sync inject-decorated methods (`#673 <https://github.com/ets-labs/python-dependency-injector/pull/673>`_)
- Add support for ``typing.Annotated`` (`#721 <https://github.com/ets-labs/python-dependency-injector/pull/721>`_, `#853 <https://github.com/ets-labs/python-dependency-injector/pull/853>`_)
- Documentation updates for movie-lister example (`#747 <https://github.com/ets-labs/python-dependency-injector/pull/747>`_)
- Fix type propagation in ``Provider.provider`` (`#744 <https://github.com/ets-labs/python-dependency-injector/pull/744>`_)
Many thanks for the contributions to:
- `ZipFile <https://github.com/ZipFile>`_
- `Yegor Statkevich <https://github.com/jazzthief>`_
- `Federico Tomasi <https://github.com/federinik>`_
- `Martin Lafrance <https://github.com/martlaf>`_
- `Philip Bjorge <https://github.com/philipbjorge>`_
- `Ilya Kazakov <https://github.com/mrKazzila>`_
4.45.0
--------
- Add Starlette lifespan handler implementation (`#683 <https://github.com/ets-labs/python-dependency-injector/pull/683>`_).
- Raise exception in ``ThreadLocalSingleton`` instead of hiding it in finally (`#845 <https://github.com/ets-labs/python-dependency-injector/pull/845>`_).
- Improve debuggability of ``deepcopy`` errors (`#839 <https://github.com/ets-labs/python-dependency-injector/pull/839>`_).
- Update examples (`#838 <https://github.com/ets-labs/python-dependency-injector/pull/838>`_).
- Upgrade testing dependencies (`#837 <https://github.com/ets-labs/python-dependency-injector/pull/837>`_).
- Add minor fixes to the documentation (`#709 <https://github.com/ets-labs/python-dependency-injector/pull/709>`_).
- Remove ``six`` from the dependencies (`3ba4704 <https://github.com/ets-labs/python-dependency-injector/commit/3ba4704bc1cb00310749fd2eda0c8221167c313c>`_).
Many thanks for the contributions to:
- `ZipFile <https://github.com/ZipFile>`_
- `František Trebuňa <https://github.com/gortibaldik>`_
- `JC (Jonathan Chen) <https://github.com/dijonkitchen>`_
4.44.0
--------
- Implement support for Pydantic 2. PR: `#832 <https://github.com/ets-labs/python-dependency-injector/pull/832>`_.
- Implement `PEP-517 <https://peps.python.org/pep-0517/>`_, `PEP-518 <https://peps.python.org/pep-0518/>`_, and
`PEP-621 <https://peps.python.org/pep-0621/>`_. PR: `#829 <https://github.com/ets-labs/python-dependency-injector/pull/829>`_.
Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
4.43.0
--------
- Add support for Python 3.13.
- Migrate to Cython 3 (version 3.0.11). Many thanks to `ZipFile <https://github.com/ZipFile>`_ for
this contribution `#813 <https://github.com/ets-labs/python-dependency-injector/pull/813>`_.
4.42.0
--------
- Promote release ``4.42.0b1`` to a production release.
- Fix the Disqus comment widget.
4.42.0b1
--------
- Add support of Python 3.12.
- Drop support of Python 2.7, 3.5, and 3.6.
- Regenerate C sources using Cython 0.29.37.
- Update ``cibuildwheel`` to version ``2.20.0``.
4.41.0 4.41.0
------ ------
- Add support of Python 3.11. - Add support of Python 3.11.
@ -171,7 +110,7 @@ Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
- Fix a typo in ``Factory`` provider docs ``service.add_attributes(clent=client)`` - Fix a typo in ``Factory`` provider docs ``service.add_attributes(clent=client)``
`#499 <https://github.com/ets-labs/python-dependency-injector/issues/499>`_. `#499 <https://github.com/ets-labs/python-dependency-injector/issues/499>`_.
Thanks to `@rajanjha786 <https://github.com/rajanjha786>`_ for the contribution. Thanks to `@rajanjha786 <https://github.com/rajanjha786>`_ for the contribution.
- Fix a typo in ``boto3`` example - Fix a typo in ``boto3`` example
`#511 <https://github.com/ets-labs/python-dependency-injector/issues/511>`_. `#511 <https://github.com/ets-labs/python-dependency-injector/issues/511>`_.
Thanks to `@whysage <https://github.com/whysage>`_ for the contribution. Thanks to `@whysage <https://github.com/whysage>`_ for the contribution.
@ -1371,24 +1310,24 @@ Misc:
------ ------
- Add ``DependenciesContainer`` provider. - Add ``DependenciesContainer`` provider.
- Add "use_cases" example miniapp. - Add "use_cases" example miniapp.
- Update documentation requirements to use fixed version of - Update documentation requirements to use fixed version of
``sphinxcontrib-disqus``. ``sphinxcontrib-disqus``.
3.9.1 3.9.1
----- -----
- Fix docs build problem (``sphinx`` is frozen on ``1.5.6`` version because of - Fix docs build problem (``sphinx`` is frozen on ``1.5.6`` version because of
incompatibility with ``sphinxcontrib-discus``). incompatibility with ``sphinxcontrib-discus``).
- Add badge for docs. - Add badge for docs.
3.9.0 3.9.0
----- -----
- Change initialization of declarative container, so it accepts overriding - Change initialization of declarative container, so it accepts overriding
providers as keyword arguments - providers as keyword arguments -
``DeclarativeContainer(**overriding_providers)``. ``DeclarativeContainer(**overriding_providers)``.
- Add method to dynamic catalog for setting groups of providers - - Add method to dynamic catalog for setting groups of providers -
``DynamicContainer.set_providers(**providers)``. ``DynamicContainer.set_providers(**providers)``.
- Add method to dynamic catalog for overriding groups of providers - - Add method to dynamic catalog for overriding groups of providers -
``DynamicContainer.set_providers(**overriding_providers)``. ``DynamicContainer.set_providers(**overriding_providers)``.
- Rename ``ExternalDependency`` provider to ``Dependency``. - Rename ``ExternalDependency`` provider to ``Dependency``.
- Add default value for ``instance_of`` argument of ``Dependency`` provider - - Add default value for ``instance_of`` argument of ``Dependency`` provider -
@ -1420,7 +1359,7 @@ Misc:
3.7.0 3.7.0
----- -----
- Add ``FactoryAggregate`` provider. - Add ``FactoryAggregate`` provider.
- Add ``Provider.provider`` dynamic attribute that return new provider's - Add ``Provider.provider`` dynamic attribute that return new provider's
delegate (alias of method ``Provider.delegate()``). delegate (alias of method ``Provider.delegate()``).
- Add support of six 1.11.0. - Add support of six 1.11.0.
- Regenerate C sources using Cython 0.27.1. - Regenerate C sources using Cython 0.27.1.
@ -1437,7 +1376,7 @@ Misc:
3.5.0 3.5.0
----- -----
- Add functionality for initializing ``Configuration`` provider with default - Add functionality for initializing ``Configuration`` provider with default
values. values.
3.4.8 3.4.8
@ -1460,7 +1399,7 @@ Misc:
3.4.4 3.4.4
----- -----
- Add ``Provider.last_overriding`` read-only property that points to last - Add ``Provider.last_overriding`` read-only property that points to last
overriding provider, if any. If target provider is not overridden, ``None`` overriding provider, if any. If target provider is not overridden, ``None``
would be returned. would be returned.
- Update example of writing custom providers. - Update example of writing custom providers.
@ -1474,7 +1413,7 @@ Misc:
3.4.2 3.4.2
----- -----
- Make ``Provider`` overriding methods thread safe: - Make ``Provider`` overriding methods thread safe:
``Provider.override(provider)``, ``Provider.reset_last_overriding()``, ``Provider.override(provider)``, ``Provider.reset_last_overriding()``,
``Provider.reset_override()``. ``Provider.reset_override()``.
- Refactor storage locking of ``ThreadSafeSingleton`` provider. - Refactor storage locking of ``ThreadSafeSingleton`` provider.
- Fix few ``pydocstyle`` errors in examples. - Fix few ``pydocstyle`` errors in examples.
@ -1546,8 +1485,8 @@ Misc:
3.2.4 3.2.4
----- -----
- Switch to single version of documentation for getting shorter urls (without - Switch to single version of documentation for getting shorter urls (without
``/en/stable/``). Add appropriate redirects for compatibility with previous ``/en/stable/``). Add appropriate redirects for compatibility with previous
links. links.
- Update copyright date. - Update copyright date.
@ -1566,7 +1505,7 @@ Misc:
3.2.0 3.2.0
----- -----
- Add ``Configuration`` provider for late static binding of configuration - Add ``Configuration`` provider for late static binding of configuration
options. options.
3.1.5 3.1.5
@ -1576,7 +1515,7 @@ Misc:
3.1.4 3.1.4
----- -----
- Move ``inline`` functions from class level to module level for removing them - Move ``inline`` functions from class level to module level for removing them
from virtual table and enable inlining. from virtual table and enable inlining.
3.1.3 3.1.3
@ -1608,34 +1547,34 @@ Misc:
- **Providers** - **Providers**
1. All providers from ``dependency_injector.providers`` package are 1. All providers from ``dependency_injector.providers`` package are
implemented as C extension types using Cython. implemented as C extension types using Cython.
2. Add ``BaseSingleton`` super class for all singleton providers. 2. Add ``BaseSingleton`` super class for all singleton providers.
3. Make ``Singleton`` provider not thread-safe. It makes performance of 3. Make ``Singleton`` provider not thread-safe. It makes performance of
``Singleton`` provider 10x times faster. ``Singleton`` provider 10x times faster.
4. Add ``ThreadSafeSingleton`` provider - thread-safe version of 4. Add ``ThreadSafeSingleton`` provider - thread-safe version of
``Singleton`` provider. ``Singleton`` provider.
5. Add ``ThreadLocalSingleton`` provider - ``Singleton`` provider that uses 5. Add ``ThreadLocalSingleton`` provider - ``Singleton`` provider that uses
thread-local storage. thread-local storage.
6. Remove ``provides`` attribute from ``Factory`` and ``Singleton`` 6. Remove ``provides`` attribute from ``Factory`` and ``Singleton``
providers. providers.
7. Add ``set_args()`` and ``clear_args()`` methods for ``Callable``, 7. Add ``set_args()`` and ``clear_args()`` methods for ``Callable``,
``Factory`` and ``Singleton`` providers. ``Factory`` and ``Singleton`` providers.
- **Containers** - **Containers**
1. Module ``dependency_injector.containers`` was split into submodules 1. Module ``dependency_injector.containers`` was split into submodules
without any functional changes. without any functional changes.
- **Utils** - **Utils**
1. Module ``dependency_injector.utils`` is split into 1. Module ``dependency_injector.utils`` is split into
``dependency_injector.containers`` and ``dependency_injector.providers``. ``dependency_injector.containers`` and ``dependency_injector.providers``.
- **Miscellaneous** - **Miscellaneous**
1. Remove ``@inject`` decorator. 1. Remove ``@inject`` decorator.
2. Add makefile (``clean``, ``test``, ``build``, ``install``, ``uninstall`` 2. Add makefile (``clean``, ``test``, ``build``, ``install``, ``uninstall``
& ``publish`` commands). & ``publish`` commands).
3. Update repository structure: 3. Update repository structure:
@ -1702,7 +1641,7 @@ Misc:
2.0.0 2.0.0
------ ------
- Introduce new injections style for ``Callable``, ``Factory`` & - Introduce new injections style for ``Callable``, ``Factory`` &
``Singleton`` providers. ``Singleton`` providers.
- Drop providers: ``Static``, ``Value``, ``Function``, ``Class``, ``Config``. - Drop providers: ``Static``, ``Value``, ``Function``, ``Class``, ``Config``.
- Increase performance of making injections in 2 times (+100%). - Increase performance of making injections in 2 times (+100%).
@ -1715,8 +1654,8 @@ Misc:
1.17.0 1.17.0
------ ------
- Add ``add_injections()`` method to ``Callable``, ``DelegatedCallable``, - Add ``add_injections()`` method to ``Callable``, ``DelegatedCallable``,
``Factory``, ``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton`` ``Factory``, ``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton``
providers. providers.
- Fix bug with accessing to declarative catalog attributes from instance level. - Fix bug with accessing to declarative catalog attributes from instance level.
@ -1744,14 +1683,14 @@ Misc:
- Add "Examples" section into documentation. - Add "Examples" section into documentation.
- Add "Movie Lister" example. - Add "Movie Lister" example.
- Add "Services" example. - Add "Services" example.
- Move project documentation into organisation's domain - Move project documentation into organisation's domain
(dependency-injector.ets-labs.org). (dependency-injector.ets-labs.org).
1.15.2 1.15.2
------ ------
- [Refactoring] split ``catalogs`` module into smaller modules, - [Refactoring] split ``catalogs`` module into smaller modules,
``catalogs`` module become a package. ``catalogs`` module become a package.
- [Refactoring] split ``providers`` module into smaller modules, - [Refactoring] split ``providers`` module into smaller modules,
``providers`` module become a package. ``providers`` module become a package.
- Update introduction documentation. - Update introduction documentation.
@ -1761,7 +1700,7 @@ Misc:
1.15.0 1.15.0
------ ------
- Add ``Provider.provide()`` method. ``Provider.__call__()`` become a - Add ``Provider.provide()`` method. ``Provider.__call__()`` become a
reference to ``Provider.provide()``. reference to ``Provider.provide()``.
- Add provider overriding context. - Add provider overriding context.
- Update main examples and README. - Update main examples and README.
@ -1791,7 +1730,7 @@ Misc:
1.14.6 1.14.6
------ ------
- Add ``cls`` alias for ``provides`` attributes of ``Factory``, - Add ``cls`` alias for ``provides`` attributes of ``Factory``,
``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton`` providers. ``DelegatedFactory``, ``Singleton`` and ``DelegatedSingleton`` providers.
1.14.5 1.14.5
@ -1850,27 +1789,27 @@ Misc:
1.11.1 1.11.1
------ ------
Previous state of *Dependency Injector* framework (0.11.0 version) is Previous state of *Dependency Injector* framework (0.11.0 version) is
considered to be production ready / stable, so current release is considered considered to be production ready / stable, so current release is considered
to be the first major release. to be the first major release.
- Increase major version. - Increase major version.
- Backward compatibility with all previous versions above 0.7.6 has been saved. - Backward compatibility with all previous versions above 0.7.6 has been saved.
0.11.0 0.11.0
------ ------
- Rename ``AbstractCatalog`` to ``DeclarativeCatalog`` - Rename ``AbstractCatalog`` to ``DeclarativeCatalog``
(with backward compatibility). (with backward compatibility).
- Rename ``catalog`` module to ``catalogs`` with backward compatibility. - Rename ``catalog`` module to ``catalogs`` with backward compatibility.
- Implement dynamic binding of providers for ``DeclarativeCatalog``. - Implement dynamic binding of providers for ``DeclarativeCatalog``.
- Add ``DynamicCatalog``. - Add ``DynamicCatalog``.
- Change restrictions for providers-to-catalogs bindings - provider could be - Change restrictions for providers-to-catalogs bindings - provider could be
bound to several catalogs with different names. bound to several catalogs with different names.
- Restrict overriding of providers by themselves. - Restrict overriding of providers by themselves.
- Restrict overriding of catalogs by themselves. - Restrict overriding of catalogs by themselves.
- Make ``DeclarativeCatalog.last_overriding`` attribute to be ``None`` by - Make ``DeclarativeCatalog.last_overriding`` attribute to be ``None`` by
default. default.
- Make ``Provider.last_overriding`` attribute to be ``None`` by - Make ``Provider.last_overriding`` attribute to be ``None`` by
default. default.
- Refactor catalogs and providers modules. - Refactor catalogs and providers modules.
- Add API documentation - Add API documentation
@ -1878,7 +1817,7 @@ to be the first major release.
0.10.5 0.10.5
------ ------
- Add more representable implementation for ``AbstractCatalog`` and - Add more representable implementation for ``AbstractCatalog`` and
``AbstractCatalog.Bundle``. ``AbstractCatalog.Bundle``.
0.10.4 0.10.4
@ -1902,17 +1841,17 @@ to be the first major release.
- Add functionality for creating ``AbstractCatalog`` provider bundles. - Add functionality for creating ``AbstractCatalog`` provider bundles.
- Improve ``AbstractCatalog`` inheritance. - Improve ``AbstractCatalog`` inheritance.
- Improve ``AbstractCatalog`` overriding. - Improve ``AbstractCatalog`` overriding.
- Add images for catalog "Writing catalogs" and "Operating with catalogs" - Add images for catalog "Writing catalogs" and "Operating with catalogs"
examples. examples.
- Add functionality for using positional argument injections with - Add functionality for using positional argument injections with
``Factory``, ``Singleton``, ``Callable`` providers and ``Factory``, ``Singleton``, ``Callable`` providers and
``inject`` decorator. ``inject`` decorator.
- Add functionality for decorating classes with ``@inject``. - Add functionality for decorating classes with ``@inject``.
- Add ``Singleton.injections`` attribute that represents a tuple of all - Add ``Singleton.injections`` attribute that represents a tuple of all
``Singleton`` injections (including args, kwargs, attributes and methods). ``Singleton`` injections (including args, kwargs, attributes and methods).
- Add ``Callable.injections`` attribute that represents a tuple of all - Add ``Callable.injections`` attribute that represents a tuple of all
``Callable`` injections (including args and kwargs). ``Callable`` injections (including args and kwargs).
- Add optimization for ``Injection.value`` property that will compute - Add optimization for ``Injection.value`` property that will compute
type of injection once, instead of doing this on every call. type of injection once, instead of doing this on every call.
- Add ``VERSION`` constant for verification of currently installed version. - Add ``VERSION`` constant for verification of currently installed version.
- Add support of Python 3.5. - Add support of Python 3.5.
@ -1922,7 +1861,7 @@ to be the first major release.
0.9.5 0.9.5
----- -----
- Change provider attributes scope to public. - Change provider attributes scope to public.
- Add ``Factory.injections`` attribute that represents a tuple of all - Add ``Factory.injections`` attribute that represents a tuple of all
``Factory`` injections (including kwargs, attributes and methods). ``Factory`` injections (including kwargs, attributes and methods).
0.9.4 0.9.4
@ -1939,14 +1878,14 @@ to be the first major release.
0.9.1 0.9.1
----- -----
- Add simplified syntax of kwarg injections for ``di.Factory`` and - Add simplified syntax of kwarg injections for ``di.Factory`` and
``di.Singleton`` providers: ``di.Singleton`` providers:
``di.Factory(SomeClass, dependency1=injectable_provider_or_value)``. ``di.Factory(SomeClass, dependency1=injectable_provider_or_value)``.
- Add simplified syntax of kwarg injections for ``di.Callable`` provider: - Add simplified syntax of kwarg injections for ``di.Callable`` provider:
``di.Callable(some_callable, dependency1=injectable_provider_or_value)`` ``di.Callable(some_callable, dependency1=injectable_provider_or_value)``
- Add simplified syntax of kwarg injections for ``@di.inject`` decorator: - Add simplified syntax of kwarg injections for ``@di.inject`` decorator:
``@di.inject(dependency1=injectable_provider_or_value)``. ``@di.inject(dependency1=injectable_provider_or_value)``.
- Optimize ``@di.inject()`` decorations when they were made several times for - Optimize ``@di.inject()`` decorations when they were made several times for
the same callback. the same callback.
- Add minor refactorings. - Add minor refactorings.
- Fix of minor documentation issues. - Fix of minor documentation issues.
@ -1966,21 +1905,21 @@ to be the first major release.
0.7.6 0.7.6
----- -----
- Adding support of six from 1.7.0 to 1.9.0. - Adding support of six from 1.7.0 to 1.9.0.
- Factory / Singleton providers are free from restriction to operate with - Factory / Singleton providers are free from restriction to operate with
classes only. This feature gives a change to use factory method and classes only. This feature gives a change to use factory method and
functions with Factory / Singleton providers. functions with Factory / Singleton providers.
- All attributes of all entities that have to be protected was renamed using - All attributes of all entities that have to be protected was renamed using
``_protected`` manner. ``_protected`` manner.
- Providers extending was improved by implementing overriding logic in - Providers extending was improved by implementing overriding logic in
``Provider.__call__()`` and moving providing logic into ``Provider.__call__()`` and moving providing logic into
``Provider._provide()``. ``Provider._provide()``.
- ``NewInstance`` provider was renamed to ``Factory`` provider. - ``NewInstance`` provider was renamed to ``Factory`` provider.
``NewInstance`` still can be used, but it considered to be deprecated and ``NewInstance`` still can be used, but it considered to be deprecated and
will be removed in further releases. will be removed in further releases.
- ``@inject`` decorator was refactored to keep all injections in - ``@inject`` decorator was refactored to keep all injections in
``_injections`` attribute of decorated callback. It will give a possibility to ``_injections`` attribute of decorated callback. It will give a possibility to
track all the injections of particular callbacks and gives some performance track all the injections of particular callbacks and gives some performance
boost due minimizing number of calls for doing injections. boost due minimizing number of calls for doing injections.
- A lot of documentation updates were made. - A lot of documentation updates were made.
- A lot of examples were added. - A lot of examples were added.

View File

@ -183,22 +183,22 @@ See also: :ref:`configuration-envs-interpolation`.
Loading from a Pydantic settings Loading from a Pydantic settings
-------------------------------- --------------------------------
``Configuration`` provider can load configuration from a ``pydantic_settings.BaseSettings`` object using the ``Configuration`` provider can load configuration from a ``pydantic`` settings object using the
:py:meth:`Configuration.from_pydantic` method: :py:meth:`Configuration.from_pydantic` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py .. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 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. If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments.
.. code-block:: python .. code-block:: python
container.config.from_pydantic(Settings(), exclude={"optional"}) container.config.from_pydantic(Settings(), exclude={"optional"})
Alternatively, you can provide a ``pydantic_settings.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: the container will call ``config.from_pydantic()`` automatically:
.. code-block:: python .. code-block:: python
@ -215,23 +215,18 @@ the container will call ``config.from_pydantic()`` automatically:
.. note:: .. 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:: 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.* *Don't forget to mirror the changes in the requirements file.*
.. note::
For backward-compatibility, Pydantic v1 is still supported.
Passing ``pydantic.BaseSettings`` instances will work just as fine as ``pydantic_settings.BaseSettings``.
Loading from a dictionary Loading from a dictionary
------------------------- -------------------------
@ -366,19 +361,6 @@ See also: :ref:`configuration-strict-mode`.
assert container.config.section.option() is None assert container.config.section.option() is None
If you want to disable environment variables interpolation, pass ``envs_required=None``:
.. code-block:: yaml
:caption: templates.yml
template_string: 'Hello, ${name}!'
.. code-block:: python
>>> container.config.from_yaml("templates.yml", envs_required=None)
>>> container.config.template_string()
'Hello, ${name}!'
Mandatory and optional sources Mandatory and optional sources
------------------------------ ------------------------------

View File

@ -24,7 +24,7 @@ returns it on the rest of the calls.
.. note:: .. 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. is created and memorized ``Singleton`` provider just returns it without applying injections.
Specialization of the provided type and abstract singletons work the same like like for the Specialization of the provided type and abstract singletons work the same like like for the

View File

@ -1,7 +0,0 @@
.. list-table::
:class: no-border
:align: left
* - Sponsor the project on GitHub:
- .. raw:: html
:file: _static/sponsor.html

View File

@ -859,6 +859,4 @@ What's next?
- Know more about the :ref:`providers` - Know more about the :ref:`providers`
- Go to the :ref:`contents` - Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -18,7 +18,7 @@ In this tutorial we will use:
- Python 3 - Python 3
- Docker - Docker
- Docker Compose - Docker-compose
Start from the scratch or jump to the section: Start from the scratch or jump to the section:
@ -47,27 +47,28 @@ response it will log:
Prerequisites 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 .. code-block:: bash
docker --version docker --version
docker compose version docker-compose --version
The output should look something like: The output should look something like:
.. code-block:: bash .. code-block:: bash
Docker version 27.3.1, build ce12230 Docker version 20.10.5, build 55c4c88
Docker Compose version v2.29.7 docker-compose version 1.29.0, build 07737305
.. note:: .. note::
If you don't have ``Docker`` or ``docker compose`` you need to install them before proceeding. If you don't have ``Docker`` or ``docker-compose`` you need to install them before proceeding.
Follow these installation guides: Follow these installation guides:
- `Install Docker <https://docs.docker.com/get-docker/>`_ - `Install Docker <https://docs.docker.com/get-docker/>`_
- `Install docker compose <https://docs.docker.com/compose/install/>`_ - `Install docker-compose <https://docs.docker.com/compose/install/>`_
The prerequisites are satisfied. Let's get started with the project layout. The prerequisites are satisfied. Let's get started with the project layout.
@ -128,13 +129,13 @@ Put next lines into the ``requirements.txt`` file:
pytest-cov pytest-cov
Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and Second, we need to create the ``Dockerfile``. It will describe the daemon's build process and
specify how to run it. We will use ``python:3.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: Put next lines into the ``Dockerfile`` file:
.. code-block:: bash .. code-block:: bash
FROM python:3.13-bookworm FROM python:3.10-buster
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
@ -154,6 +155,8 @@ Put next lines into the ``docker-compose.yml`` file:
.. code-block:: yaml .. code-block:: yaml
version: "3.7"
services: services:
monitor: monitor:
@ -168,7 +171,7 @@ Run in the terminal:
.. code-block:: bash .. code-block:: bash
docker compose build docker-compose build
The build process may take a couple of minutes. You should see something like this in the end: The build process may take a couple of minutes. You should see something like this in the end:
@ -181,7 +184,7 @@ After the build is done run the container:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
The output should look like: The output should look like:
@ -458,7 +461,7 @@ Run in the terminal:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
The output should look like: The output should look like:
@ -702,7 +705,7 @@ Run in the terminal:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
You should see: You should see:
@ -810,7 +813,7 @@ Run in the terminal:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
You should see: You should see:
@ -962,16 +965,15 @@ Run in the terminal:
.. code-block:: bash .. code-block:: bash
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
You should see: You should see:
.. code-block:: bash .. code-block:: bash
platform linux -- Python 3.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 rootdir: /code
plugins: cov-6.0.0, asyncio-0.24.0 plugins: asyncio-0.16.0, cov-3.0.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 2 items collected 2 items
monitoringdaemon/tests.py .. [100%] monitoringdaemon/tests.py .. [100%]
@ -1026,6 +1028,4 @@ What's next?
- Know more about the :ref:`providers` - Know more about the :ref:`providers`
- Go to the :ref:`contents` - Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -911,7 +911,7 @@ Create ``tests.py`` in the ``movies`` package:
and put next into it: and put next into it:
.. code-block:: python .. code-block:: python
:emphasize-lines: 41,50 :emphasize-lines: 36,51
"""Tests module.""" """Tests module."""
@ -941,18 +941,13 @@ and put next into it:
return container return container
@pytest.fixture def test_movies_directed_by(container):
def finder_mock(container):
finder_mock = mock.Mock() finder_mock = mock.Mock()
finder_mock.find_all.return_value = [ finder_mock.find_all.return_value = [
container.movie("The 33", 2015, "Patricia Riggen"), container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"), container.movie("The Jungle Book", 2016, "Jon Favreau"),
] ]
return finder_mock
def test_movies_directed_by(container, finder_mock):
with container.finder.override(finder_mock): with container.finder.override(finder_mock):
lister = container.lister() lister = container.lister()
movies = lister.movies_directed_by("Jon Favreau") movies = lister.movies_directed_by("Jon Favreau")
@ -961,7 +956,13 @@ and put next into it:
assert movies[0].title == "The Jungle Book" assert movies[0].title == "The Jungle Book"
def test_movies_released_in(container, 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): with container.finder.override(finder_mock):
lister = container.lister() lister = container.lister()
movies = lister.movies_released_in(2015) movies = lister.movies_released_in(2015)
@ -994,9 +995,9 @@ You should see:
movies/entities.py 7 1 86% movies/entities.py 7 1 86%
movies/finders.py 26 13 50% movies/finders.py 26 13 50%
movies/listers.py 8 0 100% movies/listers.py 8 0 100%
movies/tests.py 24 0 100% movies/tests.py 23 0 100%
------------------------------------------ ------------------------------------------
TOTAL 90 30 67% TOTAL 89 30 66%
.. note:: .. note::
@ -1033,6 +1034,4 @@ What's next?
- Know more about the :ref:`providers` - Know more about the :ref:`providers`
- Go to the :ref:`contents` - Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -998,6 +998,5 @@ What's next?
- Know more about the :ref:`providers` - Know more about the :ref:`providers`
- Go to the :ref:`contents` - Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -64,7 +64,7 @@ FastAPI example:
@app.api_route("/") @app.api_route("/")
@inject @inject
async def index(service: Annotated[Service, Depends(Provide[Container.service])]): async def index(service: Service = Depends(Provide[Container.service])):
value = await service.process() value = await service.process()
return {"result": value} return {"result": value}

View File

@ -98,9 +98,8 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0, aiohttp-1.0.5 plugins: asyncio-0.16.0, anyio-3.3.4, aiohttp-0.3.0, cov-3.0.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 3 items collected 3 items
giphynavigator/tests.py ... [100%] giphynavigator/tests.py ... [100%]

View File

@ -3,15 +3,11 @@
from unittest import mock from unittest import mock
import pytest import pytest
import pytest_asyncio
from giphynavigator.application import create_app from giphynavigator.application import create_app
from giphynavigator.giphy import GiphyClient from giphynavigator.giphy import GiphyClient
pytestmark = pytest.mark.asyncio
@pytest.fixture @pytest.fixture
def app(): def app():
app = create_app() app = create_app()
@ -19,9 +15,9 @@ def app():
app.container.unwire() app.container.unwire()
@pytest_asyncio.fixture @pytest.fixture
async def client(app, aiohttp_client): def client(app, aiohttp_client, loop):
return await aiohttp_client(app) return loop.run_until_complete(aiohttp_client(app))
async def test_index(client, app): async def test_index(client, app):

View File

@ -2,5 +2,4 @@ dependency-injector
aiohttp aiohttp
pyyaml pyyaml
pytest-aiohttp pytest-aiohttp
pytest-asyncio
pytest-cov pytest-cov

View File

@ -1,4 +1,4 @@
FROM python:3.13-bookworm FROM python:3.10-buster
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1

View File

@ -13,13 +13,13 @@ Build the Docker image:
.. code-block:: bash .. code-block:: bash
docker compose build docker-compose build
Run the docker-compose environment: Run the docker-compose environment:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
The output should be something like: The output should be something like:
@ -59,16 +59,15 @@ To run the tests do:
.. code-block:: bash .. code-block:: bash
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
The output should be something like: The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.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 rootdir: /code
plugins: cov-6.0.0, asyncio-0.24.0 plugins: asyncio-0.16.0, cov-3.0.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 2 items collected 2 items
monitoringdaemon/tests.py .. [100%] monitoringdaemon/tests.py .. [100%]

View File

@ -61,7 +61,7 @@ async def test_example_monitor(container, caplog):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_dispatcher(container, caplog): async def test_dispatcher(container, caplog, event_loop):
caplog.set_level("INFO") caplog.set_level("INFO")
example_monitor_mock = mock.AsyncMock() example_monitor_mock = mock.AsyncMock()
@ -72,7 +72,6 @@ async def test_dispatcher(container, caplog):
httpbin_monitor=httpbin_monitor_mock, httpbin_monitor=httpbin_monitor_mock,
): ):
dispatcher = container.dispatcher() dispatcher = container.dispatcher()
event_loop = asyncio.get_running_loop()
event_loop.create_task(dispatcher.start()) event_loop.create_task(dispatcher.start())
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
dispatcher.stop() dispatcher.stop()

View File

@ -1,4 +1,4 @@
FROM python:3.13-bookworm FROM python:3.10-buster
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1

View File

@ -12,13 +12,13 @@ Build the Docker image:
.. code-block:: bash .. code-block:: bash
docker compose build docker-compose build
Run the docker-compose environment: Run the docker-compose environment:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
The output should be something like: The output should be something like:
@ -54,16 +54,16 @@ To run the tests do:
.. code-block:: bash .. code-block:: bash
docker compose run --rm example py.test fastapiredis/tests.py --cov=fastapiredis docker-compose run --rm example py.test fastapiredis/tests.py --cov=fastapiredis
The output should be something like: The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.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 rootdir: /code
plugins: cov-6.0.0, asyncio-0.24.0, anyio-4.7.0 plugins: cov-4.0.0, asyncio-0.20.3
asyncio: mode=Mode.STRICT, default_loop_scope=None collected 1 item
fastapiredis/tests.py . [100%] fastapiredis/tests.py . [100%]
@ -77,4 +77,4 @@ The output should be something like:
fastapiredis/services.py 7 3 57% fastapiredis/services.py 7 3 57%
fastapiredis/tests.py 18 0 100% fastapiredis/tests.py 18 0 100%
------------------------------------------------- -------------------------------------------------
TOTAL 52 7 87% TOTAL 52 7 87%

View File

@ -1,22 +1,18 @@
"""Application module.""" """Application module."""
from typing import Annotated from dependency_injector.wiring import inject, Provide
from fastapi import FastAPI, Depends
from fastapi import Depends, FastAPI
from dependency_injector.wiring import Provide, inject
from .containers import Container from .containers import Container
from .services import Service from .services import Service
app = FastAPI() app = FastAPI()
@app.api_route("/") @app.api_route("/")
@inject @inject
async def index( async def index(service: Service = Depends(Provide[Container.service])):
service: Annotated[Service, Depends(Provide[Container.service])]
) -> dict[str, str]:
value = await service.process() value = await service.process()
return {"result": value} return {"result": value}

View File

@ -1,6 +1,6 @@
from typing import AsyncIterator 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]: async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]:

View File

@ -1,6 +1,6 @@
"""Services module.""" """Services module."""
from redis.asyncio import Redis from aioredis import Redis
class Service: class Service:

View File

@ -3,7 +3,7 @@
from unittest import mock from unittest import mock
import pytest import pytest
from httpx import ASGITransport, AsyncClient from httpx import AsyncClient
from .application import app, container from .application import app, container
from .services import Service from .services import Service
@ -11,10 +11,7 @@ from .services import Service
@pytest.fixture @pytest.fixture
def client(event_loop): def client(event_loop):
client = AsyncClient( client = AsyncClient(app=app, base_url="http://test")
transport=ASGITransport(app=app),
base_url="http://test",
)
yield client yield client
event_loop.run_until_complete(client.aclose()) event_loop.run_until_complete(client.aclose())

View File

@ -1,7 +1,7 @@
dependency-injector dependency-injector
fastapi fastapi
uvicorn uvicorn
redis>=4.2 aioredis
# For testing: # For testing:
pytest pytest

View File

@ -1,7 +1,4 @@
from typing import Annotated from fastapi import FastAPI, Depends
from fastapi import Depends, FastAPI
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject from dependency_injector.wiring import Provide, inject
@ -21,9 +18,7 @@ app = FastAPI()
@app.api_route("/") @app.api_route("/")
@inject @inject
async def index( async def index(service: Service = Depends(Provide[Container.service])):
service: Annotated[Service, Depends(Provide[Container.service])]
) -> dict[str, str]:
result = await service.process() result = await service.process()
return {"result": result} return {"result": result}

View File

@ -1,18 +1,14 @@
from unittest import mock from unittest import mock
import pytest import pytest
import pytest_asyncio from httpx import AsyncClient
from httpx import ASGITransport, AsyncClient
from fastapi_di_example import app, container, Service from fastapi_di_example import app, container, Service
@pytest_asyncio.fixture @pytest.fixture
async def client(): async def client(event_loop):
async with AsyncClient( async with AsyncClient(app=app, base_url="http://test") as client:
transport=ASGITransport(app=app),
base_url="http://test",
) as client:
yield client yield client

View File

@ -1,4 +1,4 @@
FROM python:3.13-bookworm FROM python:3.10-buster
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0

View File

@ -15,13 +15,13 @@ Build the Docker image:
.. code-block:: bash .. code-block:: bash
docker compose build docker-compose build
Run the docker-compose environment: Run the docker-compose environment:
.. code-block:: bash .. code-block:: bash
docker compose up docker-compose up
The output should be something like: The output should be something like:
@ -67,15 +67,15 @@ To run the tests do:
.. code-block:: bash .. code-block:: bash
docker compose run --rm webapp py.test webapp/tests.py --cov=webapp docker-compose run --rm webapp py.test webapp/tests.py --cov=webapp
The output should be something like: The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.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 rootdir: /code
plugins: cov-6.0.0, anyio-4.7.0 plugins: cov-3.0.0
collected 7 items collected 7 items
webapp/tests.py ....... [100%] webapp/tests.py ....... [100%]

View File

@ -1,5 +1,5 @@
dependency-injector dependency-injector
fastapi[standard] fastapi
uvicorn uvicorn
pyyaml pyyaml
sqlalchemy sqlalchemy

View File

@ -1,14 +1,11 @@
"""Endpoints module.""" """Endpoints module."""
from typing import Annotated
from fastapi import APIRouter, Depends, Response, status from fastapi import APIRouter, Depends, Response, status
from dependency_injector.wiring import inject, Provide
from dependency_injector.wiring import Provide, inject
from .containers import Container from .containers import Container
from .repositories import NotFoundError
from .services import UserService from .services import UserService
from .repositories import NotFoundError
router = APIRouter() router = APIRouter()
@ -16,7 +13,7 @@ router = APIRouter()
@router.get("/users") @router.get("/users")
@inject @inject
def get_list( 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() return user_service.get_users()
@ -24,8 +21,8 @@ def get_list(
@router.get("/users/{user_id}") @router.get("/users/{user_id}")
@inject @inject
def get_by_id( def get_by_id(
user_id: int, user_id: int,
user_service: Annotated[UserService, Depends(Provide[Container.user_service])], user_service: UserService = Depends(Provide[Container.user_service]),
): ):
try: try:
return user_service.get_user_by_id(user_id) return user_service.get_user_by_id(user_id)
@ -36,7 +33,7 @@ def get_by_id(
@router.post("/users", status_code=status.HTTP_201_CREATED) @router.post("/users", status_code=status.HTTP_201_CREATED)
@inject @inject
def add( def add(
user_service: Annotated[UserService, Depends(Provide[Container.user_service])], user_service: UserService = Depends(Provide[Container.user_service]),
): ):
return user_service.create_user() return user_service.create_user()
@ -44,9 +41,9 @@ def add(
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) @router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
@inject @inject
def remove( def remove(
user_id: int, user_id: int,
user_service: Annotated[UserService, Depends(Provide[Container.user_service])], user_service: UserService = Depends(Provide[Container.user_service]),
) -> Response: ):
try: try:
user_service.delete_user_by_id(user_id) user_service.delete_user_by_id(user_id)
except NotFoundError: except NotFoundError:

View File

@ -101,9 +101,9 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0, aiohttp-1.0.5 plugins: asyncio-0.16.0, cov-3.0.0
asyncio: mode=Mode.STRICT, default_loop_scope=None collected 3 items
giphynavigator/tests.py ... [100%] giphynavigator/tests.py ... [100%]

View File

@ -1,14 +1,13 @@
"""Endpoints module.""" """Endpoints module."""
from typing import Annotated, List from typing import Optional, List
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from pydantic import BaseModel from pydantic import BaseModel
from dependency_injector.wiring import inject, Provide
from dependency_injector.wiring import Provide, inject
from .containers import Container
from .services import SearchService from .services import SearchService
from .containers import Container
class Gif(BaseModel): class Gif(BaseModel):
@ -27,15 +26,11 @@ router = APIRouter()
@router.get("/", response_model=Response) @router.get("/", response_model=Response)
@inject @inject
async def index( async def index(
default_query: Annotated[str, Depends(Provide[Container.config.default.query])], query: Optional[str] = None,
default_limit: Annotated[ limit: Optional[str] = None,
int, Depends(Provide[Container.config.default.limit.as_int()]) default_query: str = Depends(Provide[Container.config.default.query]),
], default_limit: int = Depends(Provide[Container.config.default.limit.as_int()]),
search_service: Annotated[ search_service: SearchService = Depends(Provide[Container.search_service]),
SearchService, Depends(Provide[Container.search_service])
],
query: str | None = None,
limit: int | None = None,
): ):
query = query or default_query query = query or default_query
limit = limit or default_limit limit = limit or default_limit

View File

@ -3,19 +3,15 @@
from unittest import mock from unittest import mock
import pytest import pytest
import pytest_asyncio from httpx import AsyncClient
from httpx import ASGITransport, AsyncClient
from giphynavigator.application import app from giphynavigator.application import app
from giphynavigator.giphy import GiphyClient from giphynavigator.giphy import GiphyClient
@pytest_asyncio.fixture @pytest.fixture
async def client(): async def client():
async with AsyncClient( async with AsyncClient(app=app, base_url="http://test") as client:
transport=ASGITransport(app=app),
base_url="http://test",
) as client:
yield client yield client

View File

@ -81,9 +81,8 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0, flask-1.3.0 plugins: cov-3.0.0, flask-1.2.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 2 items collected 2 items
githubnavigator/tests.py .. [100%] githubnavigator/tests.py .. [100%]

View File

@ -1,7 +1,7 @@
"""Application module.""" """Application module."""
from flask import Flask from flask import Flask
from flask_bootstrap import Bootstrap4 from flask_bootstrap import Bootstrap
from .containers import Container from .containers import Container
from .blueprints import example from .blueprints import example
@ -15,7 +15,7 @@ def create_app() -> Flask:
app.container = container app.container = container
app.register_blueprint(example.blueprint) app.register_blueprint(example.blueprint)
bootstrap = Bootstrap4() bootstrap = Bootstrap()
bootstrap.init_app(app) bootstrap.init_app(app)
return app return app

View File

@ -81,9 +81,8 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0, flask-1.3.0 plugins: cov-3.0.0, flask-1.2.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 2 items collected 2 items
githubnavigator/tests.py .. [100%] githubnavigator/tests.py .. [100%]

View File

@ -1,7 +1,7 @@
"""Application module.""" """Application module."""
from flask import Flask from flask import Flask
from flask_bootstrap import Bootstrap4 from flask_bootstrap import Bootstrap
from .containers import Container from .containers import Container
from . import views from . import views
@ -15,7 +15,7 @@ def create_app() -> Flask:
app.container = container app.container = container
app.add_url_rule("/", "index", views.index) app.add_url_rule("/", "index", views.index)
bootstrap = Bootstrap4() bootstrap = Bootstrap()
bootstrap.init_app(app) bootstrap.init_app(app)
return app return app

View File

@ -58,8 +58,8 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0 plugins: cov-3.0.0
collected 2 items collected 2 items
movies/tests.py .. [100%] movies/tests.py .. [100%]

View File

@ -26,18 +26,13 @@ def container():
return container return container
@pytest.fixture def test_movies_directed_by(container):
def finder_mock(container):
finder_mock = mock.Mock() finder_mock = mock.Mock()
finder_mock.find_all.return_value = [ finder_mock.find_all.return_value = [
container.movie("The 33", 2015, "Patricia Riggen"), container.movie("The 33", 2015, "Patricia Riggen"),
container.movie("The Jungle Book", 2016, "Jon Favreau"), container.movie("The Jungle Book", 2016, "Jon Favreau"),
] ]
return finder_mock
def test_movies_directed_by(container, finder_mock):
with container.finder.override(finder_mock): with container.finder.override(finder_mock):
lister = container.lister() lister = container.lister()
movies = lister.movies_directed_by("Jon Favreau") movies = lister.movies_directed_by("Jon Favreau")
@ -46,7 +41,13 @@ def test_movies_directed_by(container, finder_mock):
assert movies[0].title == "The Jungle Book" 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): with container.finder.override(finder_mock):
lister = container.lister() lister = container.lister()
movies = lister.movies_released_in(2015) movies = lister.movies_released_in(2015)

View File

@ -27,7 +27,7 @@ To run the application do:
.. code-block:: bash .. code-block:: bash
export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0 export GIPHY_API_KEY=wBJ2wZG7SRqfrU9nPgPiWvORmloDyuL0
sanic giphynavigator.application:create_app python -m giphynavigator
The output should be something like: The output should be something like:
@ -98,9 +98,8 @@ The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: cov-6.0.0, anyio-4.4.0, asyncio-0.24.0 plugins: sanic-1.9.1, anyio-3.3.4, cov-3.0.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 3 items collected 3 items
giphynavigator/tests.py ... [100%] giphynavigator/tests.py ... [100%]

View File

@ -8,8 +8,6 @@ from sanic import Sanic
from giphynavigator.application import create_app from giphynavigator.application import create_app
from giphynavigator.giphy import GiphyClient from giphynavigator.giphy import GiphyClient
pytestmark = pytest.mark.asyncio
@pytest.fixture @pytest.fixture
def app(): def app():
@ -19,7 +17,12 @@ def app():
app.ctx.container.unwire() 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 = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = { giphy_client_mock.search.return_value = {
"data": [ "data": [
@ -29,7 +32,7 @@ async def test_index(app):
} }
with app.ctx.container.giphy_client.override(giphy_client_mock): with app.ctx.container.giphy_client.override(giphy_client_mock):
_, response = await app.asgi_client.get( response = await test_client.get(
"/", "/",
params={ params={
"query": "test", "query": "test",
@ -38,7 +41,7 @@ async def test_index(app):
) )
assert response.status_code == 200 assert response.status_code == 200
data = response.json data = response.json()
assert data == { assert data == {
"query": "test", "query": "test",
"limit": 10, "limit": 10,
@ -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 = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = { giphy_client_mock.search.return_value = {
"data": [], "data": [],
} }
with app.ctx.container.giphy_client.override(giphy_client_mock): with app.ctx.container.giphy_client.override(giphy_client_mock):
_, response = await app.asgi_client.get("/") response = await test_client.get("/")
assert response.status_code == 200 assert response.status_code == 200
data = response.json data = response.json()
assert data["gifs"] == [] assert data["gifs"] == []
async def test_index_default_params(app): async def test_index_default_params(app, test_client):
giphy_client_mock = mock.AsyncMock(spec=GiphyClient) giphy_client_mock = mock.AsyncMock(spec=GiphyClient)
giphy_client_mock.search.return_value = { giphy_client_mock.search.return_value = {
"data": [], "data": [],
} }
with app.ctx.container.giphy_client.override(giphy_client_mock): 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 assert response.status_code == 200
data = response.json data = response.json()
assert data["query"] == app.ctx.container.config.default.query() assert data["query"] == app.ctx.container.config.default.query()
assert data["limit"] == app.ctx.container.config.default.limit() assert data["limit"] == app.ctx.container.config.default.limit()

View File

@ -1,6 +1,6 @@
dependency-injector dependency-injector
sanic sanic<=21.6
sanic-testing
aiohttp aiohttp
pyyaml pyyaml
pytest-sanic
pytest-cov pytest-cov

View File

@ -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).

View File

@ -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,
)

View File

@ -1,3 +0,0 @@
dependency-injector
starlette
uvicorn

View File

@ -3,7 +3,7 @@
import os import os
from dependency_injector import containers, providers from dependency_injector import containers, providers
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import BaseSettings, Field
# Emulate environment variables # Emulate environment variables
os.environ["AWS_ACCESS_KEY_ID"] = "KEY" os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
@ -11,16 +11,15 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
class AwsSettings(BaseSettings): class AwsSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="aws_")
access_key_id: str access_key_id: str = Field(env="aws_access_key_id")
secret_access_key: str secret_access_key: str = Field(env="aws_secret_access_key")
class Settings(BaseSettings): class Settings(BaseSettings):
aws: AwsSettings = AwsSettings() aws: AwsSettings = AwsSettings()
optional: str = "default_value" optional: str = Field(default="default_value")
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):

View File

@ -3,7 +3,7 @@
import os import os
from dependency_injector import containers, providers from dependency_injector import containers, providers
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import BaseSettings, Field
# Emulate environment variables # Emulate environment variables
os.environ["AWS_ACCESS_KEY_ID"] = "KEY" os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
@ -11,16 +11,15 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
class AwsSettings(BaseSettings): class AwsSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="aws_")
access_key_id: str access_key_id: str = Field(env="aws_access_key_id")
secret_access_key: str secret_access_key: str = Field(env="aws_secret_access_key")
class Settings(BaseSettings): class Settings(BaseSettings):
aws: AwsSettings = AwsSettings() aws: AwsSettings = AwsSettings()
optional: str = "default_value" optional: str = Field(default="default_value")
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):

View File

@ -2,10 +2,10 @@
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject from dependency_injector.wiring import Provide, inject
from typing import Annotated
class Service: ... class Service:
...
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -13,16 +13,9 @@ class Container(containers.DeclarativeContainer):
service = providers.Factory(Service) service = providers.Factory(Service)
# You can place marker on parameter default value
@inject @inject
def main(service: Service = Provide[Container.service]) -> None: ... def main(service: Service = Provide[Container.service]) -> None:
...
# Also, you can place marker with typing.Annotated
@inject
def main_with_annotated(
service: Annotated[Service, Provide[Container.service]]
) -> None: ...
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,101 +0,0 @@
[build-system]
requires = ["setuptools", "Cython"]
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.7"
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 :: 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"]
[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"
[tool.pylint.main]
ignore = ["tests"]
[tool.pylint.design]
min-public-methods = 0
max-public-methods = 30

View File

@ -1,11 +1,9 @@
cython==3.0.11 cython==0.29.32
setuptools
pytest pytest
pytest-asyncio pytest-asyncio
tox tox
coverage coverage
flake8 flake8
flake8-pyproject
pydocstyle pydocstyle
sphinx_autobuild sphinx_autobuild
pip pip
@ -13,11 +11,10 @@ mypy
pyyaml pyyaml
httpx httpx
fastapi fastapi
pydantic==1.10.17 pydantic
numpy numpy
scipy scipy
boto3 boto3
mypy_boto3_s3 mypy_boto3_s3
typing_extensions
-r requirements-ext.txt -r requirements-ext.txt

View File

@ -1,9 +1,9 @@
# TODO: unpin 3.5.0 when this bug is fixed: https://github.com/sphinx-doc/sphinx/issues/8885 # TODO: unpin 3.5.0 when this bug is fixed: https://github.com/sphinx-doc/sphinx/issues/8885
sphinx sphinx<3.5.0
# TODO: unpin jinja2 after sphinx update to 4+ # TODO: unpin jinja2 after sphinx update to 4+
jinja2 jinja2<3.1
sphinx-disqus==1.3.0 -e git+https://github.com/rmk135/sphinxcontrib-disqus.git#egg=sphinxcontrib-disqus
-r requirements-ext.txt -r requirements-ext.txt

View File

@ -1,3 +1,2 @@
flask flask
werkzeug
aiohttp aiohttp

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
six>=1.7.0,<=1.16.0

View File

@ -2,7 +2,6 @@
max_line_length = 120 max_line_length = 120
max_complexity = 10 max_complexity = 10
exclude = types.py exclude = types.py
extend-ignore = E203,E701
per-file-ignores = per-file-ignores =
examples/demo/*: F841 examples/demo/*: F841
examples/containers/traverse.py: E501 examples/containers/traverse.py: E501

154
setup.py
View File

@ -1,42 +1,130 @@
"""`Dependency injector` setup script.""" """`Dependency injector` setup script."""
import os import os
import re
import sys
from Cython.Build import cythonize from setuptools import setup, Extension
from Cython.Compiler import Options
from setuptools import Extension, setup
debug = os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1"
defined_macros = [] def _open(filename):
compiler_directives = { if sys.version_info[0] == 2:
"language_level": 3, return open(filename)
"profile": debug, return open(filename, encoding="utf-8")
"linetrace": debug,
}
Options.annotate = debug # 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: # Adding debug options:
if debug: if os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1":
defined_macros.extend( defined_macros["CYTHON_TRACE"] = 1
[ defined_macros["CYTHON_TRACE_NOGIL"] = 1
("CYTHON_TRACE", "1"), defined_macros["CYTHON_CLINE_IN_TRACEBACK"] = 1
("CYTHON_TRACE_NOGIL", "1"),
("CYTHON_CLINE_IN_TRACEBACK", "1"),
]
)
setup( setup(name="dependency-injector",
ext_modules=cythonize( version=version,
[ description="Dependency injection framework for Python",
Extension( long_description=description,
"*", author="Roman Mogylatov",
["src/**/*.pyx"], author_email="rmogilatov@gmail.com",
define_macros=defined_macros, maintainer="Roman Mogylatov",
), maintainer_email="rmogilatov@gmail.com",
], url="https://github.com/ets-labs/python-dependency-injector",
annotate=debug, download_url="https://pypi.python.org/pypi/dependency_injector",
show_all_warnings=True, packages=[
compiler_directives=compiler_directives, "dependency_injector",
), "dependency_injector.ext",
) ],
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 :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Framework :: AsyncIO",
"Framework :: Bottle",
"Framework :: Django",
"Framework :: Flask",
"Framework :: Pylons",
"Framework :: Pyramid",
"Framework :: Pytest",
"Framework :: TurboGears",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
])

View File

@ -1,6 +1,6 @@
"""Top-level package.""" """Top-level package."""
__version__ = "4.46.0" __version__ = "4.41.0"
"""Version number. """Version number.
:type: str :type: str

File diff suppressed because it is too large Load Diff

View File

@ -2,39 +2,44 @@
import asyncio import asyncio
import collections.abc import collections.abc
import functools
import inspect import inspect
import types import types
from .wiring import _Marker from . import providers
from .wiring import _Marker, PatchedCallable
from .providers cimport Provider, Resource from .providers cimport Provider
def _sync_inject(object fn, tuple args, dict kwargs, dict injections, dict closings, /): def _get_sync_patched(fn, patched: PatchedCallable):
cdef object result @functools.wraps(fn)
cdef dict to_inject def _patched(*args, **kwargs):
cdef object arg_key cdef object result
cdef Provider provider cdef dict to_inject
cdef object arg_key
cdef Provider provider
to_inject = kwargs.copy() to_inject = kwargs.copy()
for arg_key, provider in injections.items(): for arg_key, provider in patched.injections.items():
if arg_key not in kwargs or isinstance(kwargs[arg_key], _Marker): if arg_key not in kwargs or isinstance(kwargs[arg_key], _Marker):
to_inject[arg_key] = provider() to_inject[arg_key] = provider()
result = fn(*args, **to_inject) result = fn(*args, **to_inject)
if closings: if patched.closing:
for arg_key, provider in closings.items(): for arg_key, provider in patched.closing.items():
if arg_key in kwargs and not isinstance(kwargs[arg_key], _Marker): if arg_key in kwargs and not isinstance(kwargs[arg_key], _Marker):
continue continue
if not isinstance(provider, Resource): if not isinstance(provider, providers.Resource):
continue continue
provider.shutdown() provider.shutdown()
return result return result
return _patched
async def _async_inject(object fn, tuple args, dict kwargs, dict injections, dict closings, /): async def _async_inject(object fn, tuple args, dict kwargs, dict injections, dict closings):
cdef object result cdef object result
cdef dict to_inject cdef dict to_inject
cdef list to_inject_await = [] cdef list to_inject_await = []
@ -64,7 +69,7 @@ async def _async_inject(object fn, tuple args, dict kwargs, dict injections, dic
for arg_key, provider in closings.items(): for arg_key, provider in closings.items():
if arg_key in kwargs and isinstance(kwargs[arg_key], _Marker): if arg_key in kwargs and isinstance(kwargs[arg_key], _Marker):
continue continue
if not isinstance(provider, Resource): if not isinstance(provider, providers.Resource):
continue continue
shutdown = provider.shutdown() shutdown = provider.shutdown()
if _isawaitable(shutdown): if _isawaitable(shutdown):

File diff suppressed because it is too large Load Diff

View File

@ -19,24 +19,21 @@ from typing import (
from .providers import Provider, Self, ProviderParent from .providers import Provider, Self, ProviderParent
C_Base = TypeVar("C_Base", bound="Container") C_Base = TypeVar("C_Base", bound="Container")
C = TypeVar("C", bound="DeclarativeContainer") C = TypeVar("C", bound="DeclarativeContainer")
C_Overriding = TypeVar("C_Overriding", bound="DeclarativeContainer") C_Overriding = TypeVar("C_Overriding", bound="DeclarativeContainer")
T = TypeVar("T") T = TypeVar("T")
TT = TypeVar("TT") TT = TypeVar("TT")
class WiringConfiguration: class WiringConfiguration:
modules: List[Any] modules: List[Any]
packages: List[Any] packages: List[Any]
from_package: Optional[str] from_package: Optional[str]
auto_wire: bool auto_wire: bool
def __init__( def __init__(self, modules: Optional[Iterable[Any]] = None, packages: Optional[Iterable[Any]] = None, from_package: Optional[str] = None, auto_wire: bool = True) -> None: ...
self,
modules: Optional[Iterable[Any]] = None,
packages: Optional[Iterable[Any]] = None,
from_package: Optional[str] = None,
auto_wire: bool = True,
) -> None: ...
class Container: class Container:
provider_type: Type[Provider] = Provider provider_type: Type[Provider] = Provider
@ -54,18 +51,11 @@ class Container:
def set_providers(self, **providers: Provider): ... def set_providers(self, **providers: Provider): ...
def set_provider(self, name: str, provider: Provider) -> None: ... def set_provider(self, name: str, provider: Provider) -> None: ...
def override(self, overriding: Union[Container, Type[Container]]) -> None: ... def override(self, overriding: Union[Container, Type[Container]]) -> None: ...
def override_providers( def override_providers(self, **overriding_providers: Union[Provider, Any]) -> ProvidersOverridingContext[C_Base]: ...
self, **overriding_providers: Union[Provider, Any]
) -> ProvidersOverridingContext[C_Base]: ...
def reset_last_overriding(self) -> None: ... def reset_last_overriding(self) -> None: ...
def reset_override(self) -> None: ... def reset_override(self) -> None: ...
def is_auto_wiring_enabled(self) -> bool: ... def is_auto_wiring_enabled(self) -> bool: ...
def wire( def wire(self, modules: Optional[Iterable[Any]] = None, packages: Optional[Iterable[Any]] = None, from_package: Optional[str] = None) -> None: ...
self,
modules: Optional[Iterable[Any]] = None,
packages: Optional[Iterable[Any]] = None,
from_package: Optional[str] = None,
) -> None: ...
def unwire(self) -> None: ... def unwire(self) -> None: ...
def init_resources(self) -> Optional[Awaitable]: ... def init_resources(self) -> Optional[Awaitable]: ...
def shutdown_resources(self) -> Optional[Awaitable]: ... def shutdown_resources(self) -> Optional[Awaitable]: ...
@ -74,9 +64,7 @@ class Container:
def reset_singletons(self) -> SingletonResetContext[C_Base]: ... def reset_singletons(self) -> SingletonResetContext[C_Base]: ...
def check_dependencies(self) -> None: ... def check_dependencies(self) -> None: ...
def from_schema(self, schema: Dict[Any, Any]) -> None: ... def from_schema(self, schema: Dict[Any, Any]) -> None: ...
def from_yaml_schema( def from_yaml_schema(self, filepath: Union[Path, str], loader: Optional[Any]=None) -> None: ...
self, filepath: Union[Path, str], loader: Optional[Any] = None
) -> None: ...
def from_json_schema(self, filepath: Union[Path, str]) -> None: ... def from_json_schema(self, filepath: Union[Path, str]) -> None: ...
@overload @overload
def resolve_provider_name(self, provider: Provider) -> str: ... def resolve_provider_name(self, provider: Provider) -> str: ...
@ -94,8 +82,10 @@ class Container:
@overload @overload
def traverse(cls, types: Optional[Iterable[Type[TT]]] = None) -> Iterator[TT]: ... def traverse(cls, types: Optional[Iterable[Type[TT]]] = None) -> Iterator[TT]: ...
class DynamicContainer(Container): ... class DynamicContainer(Container): ...
class DeclarativeContainer(Container): class DeclarativeContainer(Container):
cls_providers: ClassVar[Dict[str, Provider]] cls_providers: ClassVar[Dict[str, Provider]]
inherited_providers: ClassVar[Dict[str, Provider]] inherited_providers: ClassVar[Dict[str, Provider]]
@ -103,28 +93,29 @@ class DeclarativeContainer(Container):
@classmethod @classmethod
def override(cls, overriding: Union[Container, Type[Container]]) -> None: ... def override(cls, overriding: Union[Container, Type[Container]]) -> None: ...
@classmethod @classmethod
def override_providers( def override_providers(cls, **overriding_providers: Union[Provider, Any]) -> ProvidersOverridingContext[C_Base]: ...
cls, **overriding_providers: Union[Provider, Any]
) -> ProvidersOverridingContext[C_Base]: ...
@classmethod @classmethod
def reset_last_overriding(cls) -> None: ... def reset_last_overriding(cls) -> None: ...
@classmethod @classmethod
def reset_override(cls) -> None: ... def reset_override(cls) -> None: ...
class ProvidersOverridingContext(Generic[T]): class ProvidersOverridingContext(Generic[T]):
def __init__( def __init__(self, container: T, overridden_providers: Iterable[Union[Provider, Any]]) -> None: ...
self, container: T, overridden_providers: Iterable[Union[Provider, Any]]
) -> None: ...
def __enter__(self) -> T: ... def __enter__(self) -> T: ...
def __exit__(self, *_: Any) -> None: ... def __exit__(self, *_: Any) -> None: ...
class SingletonResetContext(Generic[T]): class SingletonResetContext(Generic[T]):
def __init__(self, container: T): ... def __init__(self, container: T): ...
def __enter__(self) -> T: ... def __enter__(self) -> T: ...
def __exit__(self, *_: Any) -> None: ... def __exit__(self, *_: Any) -> None: ...
def override(
container: Type[C], def override(container: Type[C]) -> _Callable[[Type[C_Overriding]], Type[C_Overriding]]: ...
) -> _Callable[[Type[C_Overriding]], Type[C_Overriding]]: ...
def copy(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: ... def is_container(instance: Any) -> bool: ...

View File

@ -1,20 +1,45 @@
"""Containers module.""" """Containers module."""
import asyncio
import contextlib import contextlib
import copy as copy_module import copy as copy_module
import json import json
import sys
import importlib import importlib
import inspect import inspect
import warnings
try:
import asyncio
except ImportError:
asyncio = None
try: try:
import yaml import yaml
except ImportError: except ImportError:
yaml = None yaml = None
import six
from . import providers, errors from . import providers, errors
from .providers cimport __is_future_or_coroutine from .providers cimport __is_future_or_coroutine
from .wiring import wire, unwire
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: class WiringConfiguration:
@ -30,7 +55,7 @@ class WiringConfiguration:
return self.__class__(self.modules, self.packages, self.from_package, self.auto_wire) return self.__class__(self.modules, self.packages, self.from_package, self.auto_wire)
class Container: class Container(object):
"""Abstract container.""" """Abstract container."""
@ -176,7 +201,7 @@ class DynamicContainer(Container):
:rtype: None :rtype: None
""" """
for name, provider in providers.items(): for name, provider in six.iteritems(providers):
setattr(self, name, provider) setattr(self, name, provider)
def set_provider(self, name, provider): def set_provider(self, name, provider):
@ -209,7 +234,7 @@ class DynamicContainer(Container):
self.overridden += (overriding,) self.overridden += (overriding,)
for name, provider in overriding.providers.items(): for name, provider in six.iteritems(overriding.providers):
try: try:
getattr(self, name).override(provider) getattr(self, name).override(provider)
except AttributeError: except AttributeError:
@ -225,7 +250,7 @@ class DynamicContainer(Container):
:rtype: None :rtype: None
""" """
overridden_providers = [] 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 = getattr(self, name)
container_provider.override(overriding_provider) container_provider.override(overriding_provider)
overridden_providers.append(container_provider) overridden_providers.append(container_provider)
@ -241,7 +266,7 @@ class DynamicContainer(Container):
self.overridden = self.overridden[:-1] self.overridden = self.overridden[:-1]
for provider in self.providers.values(): for provider in six.itervalues(self.providers):
provider.reset_last_overriding() provider.reset_last_overriding()
def reset_override(self): def reset_override(self):
@ -251,7 +276,7 @@ class DynamicContainer(Container):
""" """
self.overridden = tuple() self.overridden = tuple()
for provider in self.providers.values(): for provider in six.itervalues(self.providers):
provider.reset_override() provider.reset_override()
def is_auto_wiring_enabled(self): def is_auto_wiring_enabled(self):
@ -470,13 +495,13 @@ class DeclarativeContainerMetaClass(type):
containers = { containers = {
name: container name: container
for name, container in attributes.items() for name, container in six.iteritems(attributes)
if is_container(container) if is_container(container)
} }
cls_providers = { cls_providers = {
name: provider 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) if isinstance(provider, providers.Provider) and not isinstance(provider, providers.Self)
} }
@ -484,7 +509,7 @@ class DeclarativeContainerMetaClass(type):
name: provider name: provider
for base in bases for base in bases
if is_container(base) and base is not DynamicContainer 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 = {} all_providers = {}
@ -511,10 +536,10 @@ class DeclarativeContainerMetaClass(type):
self.set_container(cls) self.set_container(cls)
cls.__self__ = self cls.__self__ = self
for provider in cls.providers.values(): for provider in six.itervalues(cls.providers):
_check_provider_type(cls, provider) _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): if isinstance(provider, providers.CHILD_PROVIDERS):
provider.assign_parent(cls) provider.assign_parent(cls)
@ -616,7 +641,8 @@ class DeclarativeContainerMetaClass(type):
return self return self
class DeclarativeContainer(Container, metaclass=DeclarativeContainerMetaClass): @six.add_metaclass(DeclarativeContainerMetaClass)
class DeclarativeContainer(Container):
"""Declarative inversion of control container. """Declarative inversion of control container.
.. code-block:: python .. code-block:: python
@ -741,7 +767,7 @@ class DeclarativeContainer(Container, metaclass=DeclarativeContainerMetaClass):
cls.overridden += (overriding,) cls.overridden += (overriding,)
for name, provider in overriding.cls_providers.items(): for name, provider in six.iteritems(overriding.cls_providers):
try: try:
getattr(cls, name).override(provider) getattr(cls, name).override(provider)
except AttributeError: except AttributeError:
@ -758,7 +784,7 @@ class DeclarativeContainer(Container, metaclass=DeclarativeContainerMetaClass):
cls.overridden = cls.overridden[:-1] cls.overridden = cls.overridden[:-1]
for provider in cls.providers.values(): for provider in six.itervalues(cls.providers):
provider.reset_last_overriding() provider.reset_last_overriding()
@classmethod @classmethod
@ -769,7 +795,7 @@ class DeclarativeContainer(Container, metaclass=DeclarativeContainerMetaClass):
""" """
cls.overridden = tuple() cls.overridden = tuple()
for provider in cls.providers.values(): for provider in six.itervalues(cls.providers):
provider.reset_override() provider.reset_override()
@ -832,7 +858,7 @@ def copy(object base_container):
""" """
def _get_memo_for_matching_names(new_providers, base_providers): def _get_memo_for_matching_names(new_providers, base_providers):
memo = {} 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: if new_provider_name not in base_providers:
continue continue
source_provider = base_providers[new_provider_name] source_provider = base_providers[new_provider_name]
@ -851,7 +877,7 @@ def copy(object base_container):
new_providers.update(providers.deepcopy(base_container.providers, memo)) new_providers.update(providers.deepcopy(base_container.providers, memo))
new_providers.update(providers.deepcopy(new_container.cls_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) setattr(new_container, name, provider)
return new_container return new_container

View File

@ -10,24 +10,3 @@ class Error(Exception):
class NoSuchProviderError(Error, AttributeError): class NoSuchProviderError(Error, AttributeError):
"""Error that is raised when provider lookup is failed.""" """Error that is raised when provider lookup is failed."""
class NonCopyableArgumentError(Error):
"""Error that is raised when provider argument is not deep-copyable."""
index: int
keyword: str
provider: object
def __init__(self, provider: object, index: int = -1, keyword: str = "") -> None:
self.provider = provider
self.index = index
self.keyword = keyword
def __str__(self) -> str:
s = (
f"keyword argument {self.keyword}"
if self.keyword
else f"argument at index {self.index}"
)
return f"Couldn't copy {s} for provider {self.provider!r}"

View File

@ -38,11 +38,9 @@ class View(providers.Callable):
def as_view(self): def as_view(self):
"""Return aiohttp view function.""" """Return aiohttp view function."""
@functools.wraps(self.provides) @functools.wraps(self.provides)
async def _view(request, *args, **kwargs): async def _view(request, *args, **kwargs):
return await self.__call__(request, *args, **kwargs) return await self.__call__(request, *args, **kwargs)
return _view return _view
@ -51,8 +49,6 @@ class ClassBasedView(providers.Factory):
def as_view(self): def as_view(self):
"""Return aiohttp view function.""" """Return aiohttp view function."""
async def _view(request, *args, **kwargs): async def _view(request, *args, **kwargs):
return await self.__call__(request, *args, **kwargs) return await self.__call__(request, *args, **kwargs)
return _view return _view

View File

@ -2,13 +2,22 @@ from typing import Awaitable as _Awaitable
from dependency_injector import providers from dependency_injector import providers
class Application(providers.Singleton): ... class Application(providers.Singleton): ...
class Extension(providers.Singleton): ... class Extension(providers.Singleton): ...
class Middleware(providers.DelegatedCallable): ... class Middleware(providers.DelegatedCallable): ...
class MiddlewareFactory(providers.Factory): ... class MiddlewareFactory(providers.Factory): ...
class View(providers.Callable): class View(providers.Callable):
def as_view(self) -> _Awaitable: ... def as_view(self) -> _Awaitable: ...
class ClassBasedView(providers.Factory): class ClassBasedView(providers.Factory):
def as_view(self) -> _Awaitable: ... def as_view(self) -> _Awaitable: ...

View File

@ -45,7 +45,6 @@ class ClassBasedView(providers.Factory):
def as_view(provider, name=None): def as_view(provider, name=None):
"""Transform class-based view provider to view function.""" """Transform class-based view provider to view function."""
if isinstance(provider, providers.Factory): if isinstance(provider, providers.Factory):
def view(*args, **kwargs): def view(*args, **kwargs):
self = provider() self = provider()
return self.dispatch_request(*args, **kwargs) return self.dispatch_request(*args, **kwargs)
@ -53,13 +52,12 @@ def as_view(provider, name=None):
assert name, 'Argument "endpoint" is required for class-based views' assert name, 'Argument "endpoint" is required for class-based views'
view.__name__ = name view.__name__ = name
elif isinstance(provider, providers.Callable): elif isinstance(provider, providers.Callable):
def view(*args, **kwargs): def view(*args, **kwargs):
return provider(*args, **kwargs) return provider(*args, **kwargs)
view.__name__ = provider.provides.__name__ view.__name__ = provider.provides.__name__
else: else:
raise errors.Error("Undefined provider type") raise errors.Error('Undefined provider type')
view.__doc__ = provider.provides.__doc__ view.__doc__ = provider.provides.__doc__
view.__module__ = provider.provides.__module__ view.__module__ = provider.provides.__module__
@ -67,14 +65,14 @@ def as_view(provider, name=None):
if isinstance(provider.provides, type): if isinstance(provider.provides, type):
view.view_class = provider.provides view.view_class = provider.provides
if hasattr(provider.provides, "decorators"): if hasattr(provider.provides, 'decorators'):
for decorator in provider.provides.decorators: for decorator in provider.provides.decorators:
view = decorator(view) view = decorator(view)
if hasattr(provider.provides, "methods"): if hasattr(provider.provides, 'methods'):
view.methods = provider.provides.methods view.methods = provider.provides.methods
if hasattr(provider.provides, "provide_automatic_options"): if hasattr(provider.provides, 'provide_automatic_options'):
view.provide_automatic_options = provider.provides.provide_automatic_options view.provide_automatic_options = provider.provides.provide_automatic_options
return view return view

View File

@ -3,17 +3,22 @@ from typing import Union, Optional, Callable as _Callable, Any
from flask import request as flask_request from flask import request as flask_request
from dependency_injector import providers from dependency_injector import providers
request: providers.Object[flask_request] request: providers.Object[flask_request]
class Application(providers.Singleton): ... class Application(providers.Singleton): ...
class Extension(providers.Singleton): ... class Extension(providers.Singleton): ...
class View(providers.Callable): class View(providers.Callable):
def as_view(self) -> _Callable[..., Any]: ... def as_view(self) -> _Callable[..., Any]: ...
class ClassBasedView(providers.Factory): class ClassBasedView(providers.Factory):
def as_view(self, name: str) -> _Callable[..., Any]: ... def as_view(self, name: str) -> _Callable[..., Any]: ...
def as_view(
provider: Union[View, ClassBasedView], name: Optional[str] = None def as_view(provider: Union[View, ClassBasedView], name: Optional[str] = None) -> _Callable[..., Any]: ...
) -> _Callable[..., Any]: ...

View File

@ -1,52 +0,0 @@
import sys
from typing import Any
if sys.version_info >= (3, 11): # pragma: no cover
from typing import Self
else: # pragma: no cover
from typing_extensions import Self
from dependency_injector.containers import Container
class Lifespan:
"""A starlette lifespan handler performing container resource initialization and shutdown.
See https://www.starlette.io/lifespan/ for details.
Usage:
.. code-block:: python
from dependency_injector.containers import DeclarativeContainer
from dependency_injector.ext.starlette import Lifespan
from dependency_injector.providers import Factory, Self, Singleton
from starlette.applications import Starlette
class Container(DeclarativeContainer):
__self__ = Self()
lifespan = Singleton(Lifespan, __self__)
app = Factory(Starlette, lifespan=lifespan)
:param container: container instance
"""
container: Container
def __init__(self, container: Container) -> None:
self.container = container
def __call__(self, app: Any) -> Self:
return self
async def __aenter__(self) -> None:
result = self.container.init_resources()
if result is not None:
await result
async def __aexit__(self, *exc_info: Any) -> None:
result = self.container.shutdown_resources()
if result is not None:
await result

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,10 @@
"""Providers module.""" """Providers module."""
import asyncio try:
import asyncio
except ImportError:
asyncio = None
import functools import functools
cimport cython cimport cython
@ -15,11 +19,11 @@ cdef tuple __COROUTINE_TYPES
# Base providers # Base providers
cdef class Provider: cdef class Provider(object):
cdef tuple _overridden cdef tuple __overridden
cdef Provider _last_overriding cdef Provider __last_overriding
cdef tuple _overrides cdef tuple __overrides
cdef int _async_mode cdef int __async_mode
cpdef bint is_async_mode_enabled(self) cpdef bint is_async_mode_enabled(self)
cpdef bint is_async_mode_disabled(self) cpdef bint is_async_mode_disabled(self)
@ -30,32 +34,32 @@ cdef class Provider:
cdef class Object(Provider): cdef class Object(Provider):
cdef object _provides cdef object __provides
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class Self(Provider): cdef class Self(Provider):
cdef object _container cdef object __container
cdef tuple _alt_names cdef tuple __alt_names
cdef class Delegate(Provider): cdef class Delegate(Provider):
cdef object _provides cdef object __provides
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class Aggregate(Provider): cdef class Aggregate(Provider):
cdef dict _providers cdef dict __providers
cdef Provider __get_provider(self, object provider_name) cdef Provider __get_provider(self, object provider_name)
cdef class Dependency(Provider): cdef class Dependency(Provider):
cdef object _instance_of cdef object __instance_of
cdef object _default cdef object __default
cdef object _parent cdef object __parent
cdef class ExternalDependency(Dependency): cdef class ExternalDependency(Dependency):
@ -63,21 +67,21 @@ cdef class ExternalDependency(Dependency):
cdef class DependenciesContainer(Object): cdef class DependenciesContainer(Object):
cdef dict _providers cdef dict __providers
cdef object _parent cdef object __parent
cpdef object _override_providers(self, object container) cpdef object _override_providers(self, object container)
# Callable providers # Callable providers
cdef class Callable(Provider): cdef class Callable(Provider):
cdef object _provides cdef object __provides
cdef tuple _args cdef tuple __args
cdef int _args_len cdef int __args_len
cdef tuple _kwargs cdef tuple __kwargs
cdef int _kwargs_len cdef int __kwargs_len
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
@ -113,11 +117,11 @@ cdef class CoroutineDelegate(Delegate):
# Configuration providers # Configuration providers
cdef class ConfigurationOption(Provider): cdef class ConfigurationOption(Provider):
cdef tuple _name cdef tuple __name
cdef Configuration _root cdef Configuration __root
cdef dict _children cdef dict __children
cdef bint _required cdef bint __required
cdef object _cache cdef object __cache
cdef class TypedConfigurationOption(Callable): cdef class TypedConfigurationOption(Callable):
@ -125,22 +129,22 @@ cdef class TypedConfigurationOption(Callable):
cdef class Configuration(Object): cdef class Configuration(Object):
cdef str _name cdef str __name
cdef bint __strict cdef bint __strict
cdef dict _children cdef dict __children
cdef list _ini_files cdef list __ini_files
cdef list _yaml_files cdef list __yaml_files
cdef list _json_files cdef list __json_files
cdef list _pydantic_settings cdef list __pydantic_settings
cdef object __weakref__ cdef object __weakref__
# Factory providers # Factory providers
cdef class Factory(Provider): cdef class Factory(Provider):
cdef Callable _instantiator cdef Callable __instantiator
cdef tuple _attributes cdef tuple __attributes
cdef int _attributes_len cdef int __attributes_len
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
@ -163,8 +167,8 @@ cdef class FactoryAggregate(Aggregate):
# Singleton providers # Singleton providers
cdef class BaseSingleton(Provider): cdef class BaseSingleton(Provider):
cdef Factory _instantiator cdef Factory __instantiator
cdef object _storage cdef object __storage
cdef class Singleton(BaseSingleton): cdef class Singleton(BaseSingleton):
@ -177,7 +181,7 @@ cdef class DelegatedSingleton(Singleton):
cdef class ThreadSafeSingleton(BaseSingleton): cdef class ThreadSafeSingleton(BaseSingleton):
cdef object _storage_lock cdef object __storage_lock
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
@ -211,87 +215,87 @@ cdef class SingletonDelegate(Delegate):
# Miscellaneous providers # Miscellaneous providers
cdef class List(Provider): cdef class List(Provider):
cdef tuple _args cdef tuple __args
cdef int _args_len cdef int __args_len
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class Dict(Provider): cdef class Dict(Provider):
cdef tuple _kwargs cdef tuple __kwargs
cdef int _kwargs_len cdef int __kwargs_len
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class Resource(Provider): cdef class Resource(Provider):
cdef object _provides cdef object __provides
cdef bint _initialized cdef bint __initialized
cdef object _shutdowner cdef object __shutdowner
cdef object _resource cdef object __resource
cdef tuple _args cdef tuple __args
cdef int _args_len cdef int __args_len
cdef tuple _kwargs cdef tuple __kwargs
cdef int _kwargs_len cdef int __kwargs_len
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class Container(Provider): cdef class Container(Provider):
cdef object _container_cls cdef object __container_cls
cdef dict _overriding_providers cdef dict __overriding_providers
cdef object _container cdef object __container
cdef object _parent cdef object __parent
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class Selector(Provider): cdef class Selector(Provider):
cdef object _selector cdef object __selector
cdef dict _providers cdef dict __providers
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
# Provided instance # Provided instance
cdef class ProvidedInstance(Provider): cdef class ProvidedInstance(Provider):
cdef object _provides cdef object __provides
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class AttributeGetter(Provider): cdef class AttributeGetter(Provider):
cdef object _provides cdef object __provides
cdef object _name cdef object __name
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class ItemGetter(Provider): cdef class ItemGetter(Provider):
cdef object _provides cdef object __provides
cdef object _name cdef object __name
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class MethodCaller(Provider): cdef class MethodCaller(Provider):
cdef object _provides cdef object __provides
cdef tuple _args cdef tuple __args
cdef int _args_len cdef int __args_len
cdef tuple _kwargs cdef tuple __kwargs
cdef int _kwargs_len cdef int __kwargs_len
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
# Injections # Injections
cdef class Injection: cdef class Injection(object):
cdef object _value cdef object __value
cdef int _is_provider cdef int __is_provider
cdef int _is_delegated cdef int __is_delegated
cdef int _call cdef int __call
cdef class PositionalInjection(Injection): cdef class PositionalInjection(Injection):
@ -299,7 +303,7 @@ cdef class PositionalInjection(Injection):
cdef class NamedInjection(Injection): cdef class NamedInjection(Injection):
cdef object _name cdef object __name
cpdef tuple parse_positional_injections(tuple args) cpdef tuple parse_positional_injections(tuple args)
@ -309,13 +313,13 @@ cpdef tuple parse_named_injections(dict kwargs)
# Utils # Utils
cdef class OverridingContext: cdef class OverridingContext(object):
cdef Provider _overridden cdef Provider __overridden
cdef Provider _overriding cdef Provider __overriding
cdef class BaseSingletonResetContext: cdef class BaseSingletonResetContext(object):
cdef object _singleton cdef object __singleton
cdef class SingletonResetContext(BaseSingletonResetContext): cdef class SingletonResetContext(BaseSingletonResetContext):
@ -352,19 +356,19 @@ cpdef object deepcopy(object instance, dict memo=*)
# Inline helper functions # Inline helper functions
cdef inline object __get_name(NamedInjection self): cdef inline object __get_name(NamedInjection self):
return self._name return self.__name
cdef inline object __get_value(Injection self): cdef inline object __get_value(Injection self):
if self._call == 0: if self.__call == 0:
return self._value return self.__value
return self._value() return self.__value()
cdef inline object __get_value_kwargs(Injection self, dict kwargs): cdef inline object __get_value_kwargs(Injection self, dict kwargs):
if self._call == 0: if self.__call == 0:
return self._value return self.__value
return self._value(**kwargs) return self.__value(**kwargs)
cdef inline tuple __separate_prefixed_kwargs(dict kwargs): cdef inline tuple __separate_prefixed_kwargs(dict kwargs):
@ -629,14 +633,14 @@ cdef inline object __async_result_callback(object future_result, object future):
cdef inline object __callable_call(Callable self, tuple args, dict kwargs, ): cdef inline object __callable_call(Callable self, tuple args, dict kwargs, ):
return __call( return __call(
self._provides, self.__provides,
args, args,
self._args, self.__args,
self._args_len, self.__args_len,
kwargs, kwargs,
self._kwargs, self.__kwargs,
self._kwargs_len, self.__kwargs_len,
self._async_mode, self.__async_mode,
) )
@ -644,18 +648,18 @@ cdef inline object __factory_call(Factory self, tuple args, dict kwargs):
cdef object instance cdef object instance
instance = __call( instance = __call(
self._instantiator._provides, self.__instantiator.__provides,
args, args,
self._instantiator._args, self.__instantiator.__args,
self._instantiator._args_len, self.__instantiator.__args_len,
kwargs, kwargs,
self._instantiator._kwargs, self.__instantiator.__kwargs,
self._instantiator._kwargs_len, self.__instantiator.__kwargs_len,
self._async_mode, self.__async_mode,
) )
if self._attributes_len > 0: if self.__attributes_len > 0:
attributes = __provide_attributes(self._attributes, self._attributes_len) attributes = __provide_attributes(self.__attributes, self.__attributes_len)
is_future_instance = __is_future_or_coroutine(instance) is_future_instance = __is_future_or_coroutine(instance)
is_future_attributes = __is_future_or_coroutine(attributes) is_future_attributes = __is_future_or_coroutine(attributes)

View File

@ -33,6 +33,7 @@ except ImportError:
from . import resources from . import resources
Injection = Any Injection = Any
ProviderParent = Union["Provider", Any] ProviderParent = Union["Provider", Any]
T = TypeVar("T") T = TypeVar("T")
@ -40,13 +41,16 @@ TT = TypeVar("TT")
P = TypeVar("P", bound="Provider") P = TypeVar("P", bound="Provider")
BS = TypeVar("BS", bound="BaseSingleton") BS = TypeVar("BS", bound="BaseSingleton")
class Provider(Generic[T]): class Provider(Generic[T]):
def __init__(self) -> None: ... def __init__(self) -> None: ...
@overload @overload
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ... def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
@overload @overload
def __call__(self, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ... def __call__(self, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
def async_(self, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ... def async_(self, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
def __deepcopy__(self, memo: Optional[_Dict[Any, Any]]) -> Provider: ... def __deepcopy__(self, memo: Optional[_Dict[Any, Any]]) -> Provider: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
@ -63,9 +67,9 @@ class Provider(Generic[T]):
def unregister_overrides(self, provider: Union[Provider, Any]) -> None: ... def unregister_overrides(self, provider: Union[Provider, Any]) -> None: ...
def delegate(self) -> Provider: ... def delegate(self) -> Provider: ...
@property @property
def provider(self) -> Provider[T]: ... def provider(self) -> Provider: ...
@property @property
def provided(self) -> ProvidedInstance[T]: ... def provided(self) -> ProvidedInstance: ...
def enable_async_mode(self) -> None: ... def enable_async_mode(self) -> None: ...
def disable_async_mode(self) -> None: ... def disable_async_mode(self) -> None: ...
def reset_async_mode(self) -> None: ... def reset_async_mode(self) -> None: ...
@ -74,12 +78,9 @@ class Provider(Generic[T]):
def is_async_mode_undefined(self) -> bool: ... def is_async_mode_undefined(self) -> bool: ...
@property @property
def related(self) -> _Iterator[Provider]: ... def related(self) -> _Iterator[Provider]: ...
def traverse( def traverse(self, types: Optional[_Iterable[Type[TT]]] = None) -> _Iterator[TT]: ...
self, types: Optional[_Iterable[Type[TT]]] = None def _copy_overridings(self, copied: Provider, memo: Optional[_Dict[Any, Any]]) -> None: ...
) -> _Iterator[TT]: ...
def _copy_overridings(
self, copied: Provider, memo: Optional[_Dict[Any, Any]]
) -> None: ...
class Object(Provider[T]): class Object(Provider[T]):
def __init__(self, provides: Optional[T] = None) -> None: ... def __init__(self, provides: Optional[T] = None) -> None: ...
@ -87,6 +88,7 @@ class Object(Provider[T]):
def provides(self) -> Optional[T]: ... def provides(self) -> Optional[T]: ...
def set_provides(self, provides: Optional[T]) -> Object: ... def set_provides(self, provides: Optional[T]) -> Object: ...
class Self(Provider[T]): class Self(Provider[T]):
def __init__(self, container: Optional[T] = None) -> None: ... def __init__(self, container: Optional[T] = None) -> None: ...
def set_container(self, container: T) -> None: ... def set_container(self, container: T) -> None: ...
@ -94,51 +96,41 @@ class Self(Provider[T]):
@property @property
def alt_names(self) -> Tuple[Any]: ... def alt_names(self) -> Tuple[Any]: ...
class Delegate(Provider[Provider]): class Delegate(Provider[Provider]):
def __init__(self, provides: Optional[Provider] = None) -> None: ... def __init__(self, provides: Optional[Provider] = None) -> None: ...
@property @property
def provides(self) -> Optional[Provider]: ... def provides(self) -> Optional[Provider]: ...
def set_provides(self, provides: Optional[Provider]) -> Delegate: ... def set_provides(self, provides: Optional[Provider]) -> Delegate: ...
class Aggregate(Provider[T]): class Aggregate(Provider[T]):
def __init__( def __init__(self, provider_dict: Optional[_Dict[Any, Provider[T]]] = None, **provider_kwargs: Provider[T]): ...
self,
provider_dict: Optional[_Dict[Any, Provider[T]]] = None,
**provider_kwargs: Provider[T],
): ...
def __getattr__(self, provider_name: Any) -> Provider[T]: ... def __getattr__(self, provider_name: Any) -> Provider[T]: ...
@overload @overload
def __call__( def __call__(self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection) -> T: ...
self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection
) -> T: ...
@overload @overload
def __call__( def __call__(self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection def async_(self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
) -> Awaitable[T]: ...
def async_(
self, provider_name: Optional[Any] = None, *args: Injection, **kwargs: Injection
) -> Awaitable[T]: ...
@property @property
def providers(self) -> _Dict[Any, Provider[T]]: ... def providers(self) -> _Dict[Any, Provider[T]]: ...
def set_providers( def set_providers(self, provider_dict: Optional[_Dict[Any, Provider[T]]] = None, **provider_kwargs: Provider[T]) -> Aggregate[T]: ...
self,
provider_dict: Optional[_Dict[Any, Provider[T]]] = None,
**provider_kwargs: Provider[T],
) -> Aggregate[T]: ...
class Dependency(Provider[T]): class Dependency(Provider[T]):
def __init__( def __init__(self, instance_of: Type[T] = object, default: Optional[Union[Provider, Any]] = None) -> None: ...
self,
instance_of: Type[T] = object,
default: Optional[Union[Provider, Any]] = None,
) -> None: ...
def __getattr__(self, name: str) -> Any: ... def __getattr__(self, name: str) -> Any: ...
@property @property
def instance_of(self) -> Type[T]: ... def instance_of(self) -> Type[T]: ...
def set_instance_of(self, instance_of: Type[T]) -> Dependency[T]: ... def set_instance_of(self, instance_of: Type[T]) -> Dependency[T]: ...
@property @property
def default(self) -> Provider[T]: ... def default(self) -> Provider[T]: ...
def set_default(self, default: Optional[Union[Provider, Any]]) -> Dependency[T]: ... def set_default(self, default: Optional[Union[Provider, Any]]) -> Dependency[T]: ...
@property @property
def is_defined(self) -> bool: ... def is_defined(self) -> bool: ...
def provided_by(self, provider: Provider) -> OverridingContext[P]: ... def provided_by(self, provider: Provider) -> OverridingContext[P]: ...
@ -148,8 +140,10 @@ class Dependency(Provider[T]):
def parent_name(self) -> Optional[str]: ... def parent_name(self) -> Optional[str]: ...
def assign_parent(self, parent: ProviderParent) -> None: ... def assign_parent(self, parent: ProviderParent) -> None: ...
class ExternalDependency(Dependency[T]): ... class ExternalDependency(Dependency[T]): ...
class DependenciesContainer(Object): class DependenciesContainer(Object):
def __init__(self, **dependencies: Provider) -> None: ... def __init__(self, **dependencies: Provider) -> None: ...
def __getattr__(self, name: str) -> Provider: ... def __getattr__(self, name: str) -> Provider: ...
@ -162,18 +156,12 @@ class DependenciesContainer(Object):
def parent_name(self) -> Optional[str]: ... def parent_name(self) -> Optional[str]: ...
def assign_parent(self, parent: ProviderParent) -> None: ... def assign_parent(self, parent: ProviderParent) -> None: ...
class Callable(Provider[T]): class Callable(Provider[T]):
def __init__( def __init__(self, provides: Optional[Union[_Callable[..., T], str]] = None, *args: Injection, **kwargs: Injection) -> None: ...
self,
provides: Optional[Union[_Callable[..., T], str]] = None,
*args: Injection,
**kwargs: Injection,
) -> None: ...
@property @property
def provides(self) -> Optional[_Callable[..., T]]: ... def provides(self) -> Optional[_Callable[..., T]]: ...
def set_provides( def set_provides(self, provides: Optional[Union[_Callable[..., T], str]]) -> Callable[T]: ...
self, provides: Optional[Union[_Callable[..., T], str]]
) -> Callable[T]: ...
@property @property
def args(self) -> Tuple[Injection]: ... def args(self) -> Tuple[Injection]: ...
def add_args(self, *args: Injection) -> Callable[T]: ... def add_args(self, *args: Injection) -> Callable[T]: ...
@ -185,23 +173,32 @@ class Callable(Provider[T]):
def set_kwargs(self, **kwargs: Injection) -> Callable[T]: ... def set_kwargs(self, **kwargs: Injection) -> Callable[T]: ...
def clear_kwargs(self) -> Callable[T]: ... def clear_kwargs(self) -> Callable[T]: ...
class DelegatedCallable(Callable[T]): ... class DelegatedCallable(Callable[T]): ...
class AbstractCallable(Callable[T]): class AbstractCallable(Callable[T]):
def override(self, provider: Callable) -> OverridingContext[P]: ... def override(self, provider: Callable) -> OverridingContext[P]: ...
class CallableDelegate(Delegate): class CallableDelegate(Delegate):
def __init__(self, callable: Callable) -> None: ... def __init__(self, callable: Callable) -> None: ...
class Coroutine(Callable[T]): ... class Coroutine(Callable[T]): ...
class DelegatedCoroutine(Coroutine[T]): ... class DelegatedCoroutine(Coroutine[T]): ...
class AbstractCoroutine(Coroutine[T]): class AbstractCoroutine(Coroutine[T]):
def override(self, provider: Coroutine) -> OverridingContext[P]: ... def override(self, provider: Coroutine) -> OverridingContext[P]: ...
class CoroutineDelegate(Delegate): class CoroutineDelegate(Delegate):
def __init__(self, coroutine: Coroutine) -> None: ... def __init__(self, coroutine: Coroutine) -> None: ...
class ConfigurationOption(Provider[Any]): class ConfigurationOption(Provider[Any]):
UNDEFINED: object UNDEFINED: object
def __init__(self, name: Tuple[str], root: Configuration) -> None: ... def __init__(self, name: Tuple[str], root: Configuration) -> None: ...
@ -215,137 +212,89 @@ class ConfigurationOption(Provider[Any]):
def get_name_segments(self) -> Tuple[Union[str, Provider]]: ... def get_name_segments(self) -> Tuple[Union[str, Provider]]: ...
def as_int(self) -> TypedConfigurationOption[int]: ... def as_int(self) -> TypedConfigurationOption[int]: ...
def as_float(self) -> TypedConfigurationOption[float]: ... def as_float(self) -> TypedConfigurationOption[float]: ...
def as_( def as_(self, callback: _Callable[..., T], *args: Injection, **kwargs: Injection) -> TypedConfigurationOption[T]: ...
self, callback: _Callable[..., T], *args: Injection, **kwargs: Injection
) -> TypedConfigurationOption[T]: ...
def required(self) -> ConfigurationOption: ... def required(self) -> ConfigurationOption: ...
def is_required(self) -> bool: ... def is_required(self) -> bool: ...
def update(self, value: Any) -> None: ... def update(self, value: Any) -> None: ...
def from_ini( def from_ini(self, filepath: Union[Path, str], required: bool = False, envs_required: bool = False) -> None: ...
self, def from_yaml(self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any] = None, envs_required: bool = False) -> None: ...
filepath: Union[Path, str], def from_json(self, filepath: Union[Path, str], required: bool = False, envs_required: bool = False) -> None: ...
required: bool = False, def from_pydantic(self, settings: PydanticSettings, required: bool = False, **kwargs: Any) -> None: ...
envs_required: Optional[bool] = False,
) -> None: ...
def from_yaml(
self,
filepath: Union[Path, str],
required: bool = False,
loader: Optional[Any] = None,
envs_required: Optional[bool] = False,
) -> None: ...
def from_json(
self,
filepath: Union[Path, str],
required: bool = False,
envs_required: Optional[bool] = False,
) -> None: ...
def from_pydantic(
self, settings: PydanticSettings, required: bool = False, **kwargs: Any
) -> None: ...
def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ... def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
def from_env( def from_env(self, name: str, default: Optional[Any] = None, required: bool = False, as_: Optional[_Callable[..., Any]] = None) -> None: ...
self,
name: str,
default: Optional[Any] = None,
required: bool = False,
as_: Optional[_Callable[..., Any]] = None,
) -> None: ...
def from_value(self, value: Any) -> None: ... def from_value(self, value: Any) -> None: ...
class TypedConfigurationOption(Callable[T]): class TypedConfigurationOption(Callable[T]):
@property @property
def option(self) -> ConfigurationOption: ... def option(self) -> ConfigurationOption: ...
class Configuration(Object[Any]): class Configuration(Object[Any]):
DEFAULT_NAME: str = "config" DEFAULT_NAME: str = "config"
def __init__( def __init__(
self, self,
name: str = DEFAULT_NAME, name: str = DEFAULT_NAME,
default: Optional[Any] = None, default: Optional[Any] = None,
*, *,
strict: bool = False, strict: bool = False,
ini_files: Optional[_Iterable[Union[Path, str]]] = None, ini_files: Optional[_Iterable[Union[Path, str]]] = None,
yaml_files: Optional[_Iterable[Union[Path, str]]] = None, yaml_files: Optional[_Iterable[Union[Path, str]]] = None,
json_files: Optional[_Iterable[Union[Path, str]]] = None, json_files: Optional[_Iterable[Union[Path, str]]] = None,
pydantic_settings: Optional[_Iterable[PydanticSettings]] = None, pydantic_settings: Optional[_Iterable[PydanticSettings]] = None,
) -> None: ... ) -> None: ...
def __enter__(self) -> Configuration: ... def __enter__(self) -> Configuration : ...
def __exit__(self, *exc_info: Any) -> None: ... def __exit__(self, *exc_info: Any) -> None: ...
def __getattr__(self, item: str) -> ConfigurationOption: ... def __getattr__(self, item: str) -> ConfigurationOption: ...
def __getitem__(self, item: Union[str, Provider]) -> ConfigurationOption: ... def __getitem__(self, item: Union[str, Provider]) -> ConfigurationOption: ...
def get_name(self) -> str: ... def get_name(self) -> str: ...
def set_name(self, name: str) -> Configuration: ... def set_name(self, name: str) -> Configuration: ...
def get_default(self) -> _Dict[Any, Any]: ... def get_default(self) -> _Dict[Any, Any]: ...
def set_default(self, default: _Dict[Any, Any]): ... def set_default(self, default: _Dict[Any, Any]): ...
def get_strict(self) -> bool: ... def get_strict(self) -> bool: ...
def set_strict(self, strict: bool) -> Configuration: ... def set_strict(self, strict: bool) -> Configuration: ...
def get_children(self) -> _Dict[str, ConfigurationOption]: ... def get_children(self) -> _Dict[str, ConfigurationOption]: ...
def set_children( def set_children(self, children: _Dict[str, ConfigurationOption]) -> Configuration: ...
self, children: _Dict[str, ConfigurationOption]
) -> Configuration: ...
def get_ini_files(self) -> _List[Union[Path, str]]: ... def get_ini_files(self) -> _List[Union[Path, str]]: ...
def set_ini_files(self, files: _Iterable[Union[Path, str]]) -> Configuration: ... def set_ini_files(self, files: _Iterable[Union[Path, str]]) -> Configuration: ...
def get_yaml_files(self) -> _List[Union[Path, str]]: ... def get_yaml_files(self) -> _List[Union[Path, str]]: ...
def set_yaml_files(self, files: _Iterable[Union[Path, str]]) -> Configuration: ... def set_yaml_files(self, files: _Iterable[Union[Path, str]]) -> Configuration: ...
def get_json_files(self) -> _List[Union[Path, str]]: ... def get_json_files(self) -> _List[Union[Path, str]]: ...
def set_json_files(self, files: _Iterable[Union[Path, str]]) -> Configuration: ... def set_json_files(self, files: _Iterable[Union[Path, str]]) -> Configuration: ...
def get_pydantic_settings(self) -> _List[PydanticSettings]: ... def get_pydantic_settings(self) -> _List[PydanticSettings]: ...
def set_pydantic_settings( def set_pydantic_settings(self, settings: _Iterable[PydanticSettings]) -> Configuration: ...
self, settings: _Iterable[PydanticSettings]
) -> Configuration: ...
def load(self, required: bool = False, envs_required: bool = False) -> None: ... def load(self, required: bool = False, envs_required: bool = False) -> None: ...
def get(self, selector: str) -> Any: ... def get(self, selector: str) -> Any: ...
def set(self, selector: str, value: Any) -> OverridingContext[P]: ... def set(self, selector: str, value: Any) -> OverridingContext[P]: ...
def reset_cache(self) -> None: ... def reset_cache(self) -> None: ...
def update(self, value: Any) -> None: ... def update(self, value: Any) -> None: ...
def from_ini( def from_ini(self, filepath: Union[Path, str], required: bool = False, envs_required: bool = False) -> None: ...
self, def from_yaml(self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any] = None, envs_required: bool = False) -> None: ...
filepath: Union[Path, str], def from_json(self, filepath: Union[Path, str], required: bool = False, envs_required: bool = False) -> None: ...
required: bool = False, def from_pydantic(self, settings: PydanticSettings, required: bool = False, **kwargs: Any) -> None: ...
envs_required: bool = False,
) -> None: ...
def from_yaml(
self,
filepath: Union[Path, str],
required: bool = False,
loader: Optional[Any] = None,
envs_required: bool = False,
) -> None: ...
def from_json(
self,
filepath: Union[Path, str],
required: bool = False,
envs_required: bool = False,
) -> None: ...
def from_pydantic(
self, settings: PydanticSettings, required: bool = False, **kwargs: Any
) -> None: ...
def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ... def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
def from_env( def from_env(self, name: str, default: Optional[Any] = None, required: bool = False, as_: Optional[_Callable[..., Any]] = None) -> None: ...
self,
name: str,
default: Optional[Any] = None,
required: bool = False,
as_: Optional[_Callable[..., Any]] = None,
) -> None: ...
def from_value(self, value: Any) -> None: ... def from_value(self, value: Any) -> None: ...
class Factory(Provider[T]): class Factory(Provider[T]):
provided_type: Optional[Type] provided_type: Optional[Type]
def __init__( def __init__(self, provides: Optional[Union[_Callable[..., T], str]] = None, *args: Injection, **kwargs: Injection) -> None: ...
self,
provides: Optional[Union[_Callable[..., T], str]] = None,
*args: Injection,
**kwargs: Injection,
) -> None: ...
@property @property
def cls(self) -> Type[T]: ... def cls(self) -> Type[T]: ...
@property @property
def provides(self) -> Optional[_Callable[..., T]]: ... def provides(self) -> Optional[_Callable[..., T]]: ...
def set_provides( def set_provides(self, provides: Optional[Union[_Callable[..., T], str]]) -> Factory[T]: ...
self, provides: Optional[Union[_Callable[..., T], str]]
) -> Factory[T]: ...
@property @property
def args(self) -> Tuple[Injection]: ... def args(self) -> Tuple[Injection]: ...
def add_args(self, *args: Injection) -> Factory[T]: ... def add_args(self, *args: Injection) -> Factory[T]: ...
@ -362,39 +311,33 @@ class Factory(Provider[T]):
def set_attributes(self, **kwargs: Injection) -> Factory[T]: ... def set_attributes(self, **kwargs: Injection) -> Factory[T]: ...
def clear_attributes(self) -> Factory[T]: ... def clear_attributes(self) -> Factory[T]: ...
class DelegatedFactory(Factory[T]): ... class DelegatedFactory(Factory[T]): ...
class AbstractFactory(Factory[T]): class AbstractFactory(Factory[T]):
def override(self, provider: Factory) -> OverridingContext[P]: ... def override(self, provider: Factory) -> OverridingContext[P]: ...
class FactoryDelegate(Delegate): class FactoryDelegate(Delegate):
def __init__(self, factory: Factory): ... def __init__(self, factory: Factory): ...
class FactoryAggregate(Aggregate[T]): class FactoryAggregate(Aggregate[T]):
def __getattr__(self, provider_name: Any) -> Factory[T]: ... def __getattr__(self, provider_name: Any) -> Factory[T]: ...
@property @property
def factories(self) -> _Dict[Any, Factory[T]]: ... def factories(self) -> _Dict[Any, Factory[T]]: ...
def set_factories( def set_factories(self, provider_dict: Optional[_Dict[Any, Factory[T]]] = None, **provider_kwargs: Factory[T]) -> FactoryAggregate[T]: ...
self,
provider_dict: Optional[_Dict[Any, Factory[T]]] = None,
**provider_kwargs: Factory[T],
) -> FactoryAggregate[T]: ...
class BaseSingleton(Provider[T]): class BaseSingleton(Provider[T]):
provided_type = Optional[Type] provided_type = Optional[Type]
def __init__( def __init__(self, provides: Optional[Union[_Callable[..., T], str]] = None, *args: Injection, **kwargs: Injection) -> None: ...
self,
provides: Optional[Union[_Callable[..., T], str]] = None,
*args: Injection,
**kwargs: Injection,
) -> None: ...
@property @property
def cls(self) -> Type[T]: ... def cls(self) -> Type[T]: ...
@property @property
def provides(self) -> Optional[_Callable[..., T]]: ... def provides(self) -> Optional[_Callable[..., T]]: ...
def set_provides( def set_provides(self, provides: Optional[Union[_Callable[..., T], str]]) -> BaseSingleton[T]: ...
self, provides: Optional[Union[_Callable[..., T], str]]
) -> BaseSingleton[T]: ...
@property @property
def args(self) -> Tuple[Injection]: ... def args(self) -> Tuple[Injection]: ...
def add_args(self, *args: Injection) -> BaseSingleton[T]: ... def add_args(self, *args: Injection) -> BaseSingleton[T]: ...
@ -413,20 +356,36 @@ class BaseSingleton(Provider[T]):
def reset(self) -> SingletonResetContext[BS]: ... def reset(self) -> SingletonResetContext[BS]: ...
def full_reset(self) -> SingletonFullResetContext[BS]: ... def full_reset(self) -> SingletonFullResetContext[BS]: ...
class Singleton(BaseSingleton[T]): ... class Singleton(BaseSingleton[T]): ...
class DelegatedSingleton(Singleton[T]): ... class DelegatedSingleton(Singleton[T]): ...
class ThreadSafeSingleton(Singleton[T]): ... class ThreadSafeSingleton(Singleton[T]): ...
class DelegatedThreadSafeSingleton(ThreadSafeSingleton[T]): ... class DelegatedThreadSafeSingleton(ThreadSafeSingleton[T]): ...
class ThreadLocalSingleton(BaseSingleton[T]): ... class ThreadLocalSingleton(BaseSingleton[T]): ...
class ContextLocalSingleton(BaseSingleton[T]): ... class ContextLocalSingleton(BaseSingleton[T]): ...
class DelegatedThreadLocalSingleton(ThreadLocalSingleton[T]): ... class DelegatedThreadLocalSingleton(ThreadLocalSingleton[T]): ...
class AbstractSingleton(BaseSingleton[T]): class AbstractSingleton(BaseSingleton[T]):
def override(self, provider: BaseSingleton) -> OverridingContext[P]: ... def override(self, provider: BaseSingleton) -> OverridingContext[P]: ...
class SingletonDelegate(Delegate): class SingletonDelegate(Delegate):
def __init__(self, singleton: BaseSingleton): ... def __init__(self, singleton: BaseSingleton): ...
class List(Provider[_List]): class List(Provider[_List]):
def __init__(self, *args: Injection): ... def __init__(self, *args: Injection): ...
@property @property
@ -435,63 +394,29 @@ class List(Provider[_List]):
def set_args(self, *args: Injection) -> List[T]: ... def set_args(self, *args: Injection) -> List[T]: ...
def clear_args(self) -> List[T]: ... def clear_args(self) -> List[T]: ...
class Dict(Provider[_Dict]): class Dict(Provider[_Dict]):
def __init__( def __init__(self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection): ...
self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection
): ...
@property @property
def kwargs(self) -> _Dict[Any, Injection]: ... def kwargs(self) -> _Dict[Any, Injection]: ...
def add_kwargs( def add_kwargs(self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection) -> Dict: ...
self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection def set_kwargs(self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection) -> Dict: ...
) -> Dict: ...
def set_kwargs(
self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection
) -> Dict: ...
def clear_kwargs(self) -> Dict: ... def clear_kwargs(self) -> Dict: ...
class Resource(Provider[T]): class Resource(Provider[T]):
@overload @overload
def __init__( def __init__(self, provides: Optional[Type[resources.Resource[T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
self,
provides: Optional[Type[resources.Resource[T]]] = None,
*args: Injection,
**kwargs: Injection,
) -> None: ...
@overload @overload
def __init__( def __init__(self, provides: Optional[Type[resources.AsyncResource[T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
self,
provides: Optional[Type[resources.AsyncResource[T]]] = None,
*args: Injection,
**kwargs: Injection,
) -> None: ...
@overload @overload
def __init__( def __init__(self, provides: Optional[_Callable[..., _Iterator[T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
self,
provides: Optional[_Callable[..., _Iterator[T]]] = None,
*args: Injection,
**kwargs: Injection,
) -> None: ...
@overload @overload
def __init__( def __init__(self, provides: Optional[_Callable[..., _AsyncIterator[T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
self,
provides: Optional[_Callable[..., _AsyncIterator[T]]] = None,
*args: Injection,
**kwargs: Injection,
) -> None: ...
@overload @overload
def __init__( def __init__(self, provides: Optional[_Callable[..., _Coroutine[Injection, Injection, T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
self,
provides: Optional[_Callable[..., _Coroutine[Injection, Injection, T]]] = None,
*args: Injection,
**kwargs: Injection,
) -> None: ...
@overload @overload
def __init__( def __init__(self, provides: Optional[Union[_Callable[..., T], str]] = None, *args: Injection, **kwargs: Injection) -> None: ...
self,
provides: Optional[Union[_Callable[..., T], str]] = None,
*args: Injection,
**kwargs: Injection,
) -> None: ...
@property @property
def provides(self) -> Optional[_Callable[..., Any]]: ... def provides(self) -> Optional[_Callable[..., Any]]: ...
def set_provides(self, provides: Optional[Any]) -> Resource[T]: ... def set_provides(self, provides: Optional[Any]) -> Resource[T]: ...
@ -510,13 +435,9 @@ class Resource(Provider[T]):
def init(self) -> Optional[Awaitable[T]]: ... def init(self) -> Optional[Awaitable[T]]: ...
def shutdown(self) -> Optional[Awaitable]: ... def shutdown(self) -> Optional[Awaitable]: ...
class Container(Provider[T]): class Container(Provider[T]):
def __init__( def __init__(self, container_cls: Type[T], container: Optional[T] = None, **overriding_providers: Union[Provider, Any]) -> None: ...
self,
container_cls: Type[T],
container: Optional[T] = None,
**overriding_providers: Union[Provider, Any],
) -> None: ...
def __getattr__(self, name: str) -> Provider: ... def __getattr__(self, name: str) -> Provider: ...
@property @property
def container(self) -> T: ... def container(self) -> T: ...
@ -527,51 +448,50 @@ class Container(Provider[T]):
def parent_name(self) -> Optional[str]: ... def parent_name(self) -> Optional[str]: ...
def assign_parent(self, parent: ProviderParent) -> None: ... def assign_parent(self, parent: ProviderParent) -> None: ...
class Selector(Provider[Any]): class Selector(Provider[Any]):
def __init__( def __init__(self, selector: Optional[_Callable[..., Any]] = None, **providers: Provider): ...
self, selector: Optional[_Callable[..., Any]] = None, **providers: Provider
): ...
def __getattr__(self, name: str) -> Provider: ... def __getattr__(self, name: str) -> Provider: ...
@property @property
def selector(self) -> Optional[_Callable[..., Any]]: ... def selector(self) -> Optional[_Callable[..., Any]]: ...
def set_selector(self, selector: Optional[_Callable[..., Any]]) -> Selector: ... def set_selector(self, selector: Optional[_Callable[..., Any]]) -> Selector: ...
@property @property
def providers(self) -> _Dict[str, Provider]: ... def providers(self) -> _Dict[str, Provider]: ...
def set_providers(self, **providers: Provider) -> Selector: ... def set_providers(self, **providers: Provider) -> Selector: ...
class ProvidedInstanceFluentInterface: class ProvidedInstanceFluentInterface:
def __getattr__(self, item: Any) -> AttributeGetter: ... def __getattr__(self, item: Any) -> AttributeGetter: ...
def __getitem__(self, item: Any) -> ItemGetter: ... def __getitem__(self, item: Any) -> ItemGetter: ...
def call(self, *args: Injection, **kwargs: Injection) -> MethodCaller: ... def call(self, *args: Injection, **kwargs: Injection) -> MethodCaller: ...
@property @property
def provides(self) -> Optional[Provider]: ... def provides(self) -> Optional[Provider]: ...
def set_provides( def set_provides(self, provides: Optional[Provider]) -> ProvidedInstanceFluentInterface: ...
self, provides: Optional[Provider]
) -> ProvidedInstanceFluentInterface: ...
class ProvidedInstance(Provider, ProvidedInstanceFluentInterface): class ProvidedInstance(Provider, ProvidedInstanceFluentInterface):
def __init__(self, provides: Optional[Provider] = None) -> None: ... def __init__(self, provides: Optional[Provider] = None) -> None: ...
class AttributeGetter(Provider, ProvidedInstanceFluentInterface): class AttributeGetter(Provider, ProvidedInstanceFluentInterface):
def __init__( def __init__(self, provides: Optional[Provider] = None, name: Optional[str] = None) -> None: ...
self, provides: Optional[Provider] = None, name: Optional[str] = None
) -> None: ...
@property @property
def name(self) -> Optional[str]: ... def name(self) -> Optional[str]: ...
def set_name(self, name: Optional[str]) -> ProvidedInstanceFluentInterface: ... def set_name(self, name: Optional[str]) -> ProvidedInstanceFluentInterface: ...
class ItemGetter(Provider, ProvidedInstanceFluentInterface): class ItemGetter(Provider, ProvidedInstanceFluentInterface):
def __init__( def __init__(self, provides: Optional[Provider] = None, name: Optional[str] = None) -> None: ...
self, provides: Optional[Provider] = None, name: Optional[str] = None
) -> None: ...
@property @property
def name(self) -> Optional[str]: ... def name(self) -> Optional[str]: ...
def set_name(self, name: Optional[str]) -> ProvidedInstanceFluentInterface: ... def set_name(self, name: Optional[str]) -> ProvidedInstanceFluentInterface: ...
class MethodCaller(Provider, ProvidedInstanceFluentInterface): class MethodCaller(Provider, ProvidedInstanceFluentInterface):
def __init__( def __init__(self, provides: Optional[Provider] = None, *args: Injection, **kwargs: Injection) -> None: ...
self, provides: Optional[Provider] = None, *args: Injection, **kwargs: Injection
) -> None: ...
class OverridingContext(Generic[T]): class OverridingContext(Generic[T]):
def __init__(self, overridden: Provider, overriding: Provider): ... def __init__(self, overridden: Provider, overriding: Provider): ...
@ -580,39 +500,47 @@ class OverridingContext(Generic[T]):
pass pass
... ...
class BaseSingletonResetContext(Generic[T]): class BaseSingletonResetContext(Generic[T]):
def __init__(self, provider: T): ... def __init__(self, provider: T): ...
def __enter__(self) -> T: ... def __enter__(self) -> T: ...
def __exit__(self, *_: Any) -> None: ... def __exit__(self, *_: Any) -> None: ...
class SingletonResetContext(BaseSingletonResetContext): ...
class SingletonFullResetContext(BaseSingletonResetContext): ... class SingletonResetContext(BaseSingletonResetContext):
...
class SingletonFullResetContext(BaseSingletonResetContext):
...
CHILD_PROVIDERS: Tuple[Provider] CHILD_PROVIDERS: Tuple[Provider]
def is_provider(instance: Any) -> bool: ... def is_provider(instance: Any) -> bool: ...
def ensure_is_provider(instance: Any) -> Provider: ... def ensure_is_provider(instance: Any) -> Provider: ...
def is_delegated(instance: Any) -> bool: ... def is_delegated(instance: Any) -> bool: ...
def represent_provider(provider: Provider, provides: Any) -> str: ... def represent_provider(provider: Provider, provides: Any) -> str: ...
def deepcopy(instance: Any, memo: Optional[_Dict[Any, Any]] = None) -> Any: ...
def deepcopy_args(
provider: Provider[Any], def deepcopy(instance: Any, memo: Optional[_Dict[Any, Any]] = None): Any: ...
args: Tuple[Any, ...],
memo: Optional[_Dict[int, Any]] = None,
) -> Tuple[Any, ...]: ...
def deepcopy_kwargs(
provider: Provider[Any],
kwargs: _Dict[str, Any],
memo: Optional[_Dict[int, Any]] = None,
) -> Dict[str, Any]: ...
def merge_dicts(dict1: _Dict[Any, Any], dict2: _Dict[Any, Any]) -> _Dict[Any, Any]: ... def merge_dicts(dict1: _Dict[Any, Any], dict2: _Dict[Any, Any]) -> _Dict[Any, Any]: ...
def traverse(
*providers: Provider, types: Optional[_Iterable[Type]] = None
) -> _Iterator[Provider]: ... def traverse(*providers: Provider, types: Optional[_Iterable[Type]]=None) -> _Iterator[Provider]: ...
if yaml: if yaml:
class YamlLoader(yaml.SafeLoader): ... class YamlLoader(yaml.SafeLoader): ...
else: else:
class YamlLoader: ... class YamlLoader: ...

File diff suppressed because it is too large Load Diff

View File

@ -10,14 +10,18 @@ T = TypeVar("T")
class Resource(Generic[T], metaclass=abc.ABCMeta): class Resource(Generic[T], metaclass=abc.ABCMeta):
@abc.abstractmethod @abc.abstractmethod
def init(self, *args, **kwargs) -> Optional[T]: ... def init(self, *args, **kwargs) -> Optional[T]:
...
def shutdown(self, resource: Optional[T]) -> None: ... def shutdown(self, resource: Optional[T]) -> None:
...
class AsyncResource(Generic[T], metaclass=abc.ABCMeta): class AsyncResource(Generic[T], metaclass=abc.ABCMeta):
@abc.abstractmethod @abc.abstractmethod
async def init(self, *args, **kwargs) -> Optional[T]: ... async def init(self, *args, **kwargs) -> Optional[T]:
...
async def shutdown(self, resource: Optional[T]) -> None: ... async def shutdown(self, resource: Optional[T]) -> None:
...

View File

@ -27,9 +27,9 @@ class SchemaProcessorV1:
return self._container.providers return self._container.providers
def _create_providers( def _create_providers(
self, self,
provider_schema: ProviderSchema, provider_schema: ProviderSchema,
container: Optional[containers.Container] = None, container: Optional[containers.Container] = None,
) -> None: ) -> None:
if container is None: if container is None:
container = self._container container = self._container
@ -57,9 +57,9 @@ class SchemaProcessorV1:
self._create_providers(provider_schema=data, container=provider) self._create_providers(provider_schema=data, container=provider)
def _setup_injections( # noqa: C901 def _setup_injections( # noqa: C901
self, self,
provider_schema: ProviderSchema, provider_schema: ProviderSchema,
container: Optional[containers.Container] = None, container: Optional[containers.Container] = None,
) -> None: ) -> None:
if container is None: if container is None:
container = self._container container = self._container
@ -72,7 +72,7 @@ class SchemaProcessorV1:
provides = data.get("provides") provides = data.get("provides")
if provides: if provides:
if isinstance(provides, str) and provides.startswith("container."): if isinstance(provides, str) and provides.startswith("container."):
provides = self._resolve_provider(provides[len("container.") :]) provides = self._resolve_provider(provides[len("container."):])
else: else:
provides = _import_string(provides) provides = _import_string(provides)
provider.set_provides(provides) provider.set_provides(provides)
@ -83,7 +83,7 @@ class SchemaProcessorV1:
injection = None injection = None
if isinstance(arg, str) and arg.startswith("container."): if isinstance(arg, str) and arg.startswith("container."):
injection = self._resolve_provider(arg[len("container.") :]) injection = self._resolve_provider(arg[len("container."):])
# TODO: refactoring # TODO: refactoring
if isinstance(arg, dict): if isinstance(arg, dict):
@ -91,23 +91,16 @@ class SchemaProcessorV1:
provider_type = _get_provider_cls(arg.get("provider")) provider_type = _get_provider_cls(arg.get("provider"))
provides = arg.get("provides") provides = arg.get("provides")
if provides: if provides:
if isinstance(provides, str) and provides.startswith( if isinstance(provides, str) and provides.startswith("container."):
"container." provides = self._resolve_provider(provides[len("container."):])
):
provides = self._resolve_provider(
provides[len("container.") :]
)
else: else:
provides = _import_string(provides) provides = _import_string(provides)
provider_args.append(provides) provider_args.append(provides)
for provider_arg in arg.get("args", []): for provider_arg in arg.get("args", []):
if isinstance( if isinstance(provider_arg, str) \
provider_arg, str and provider_arg.startswith("container."):
) and provider_arg.startswith("container."):
provider_args.append( provider_args.append(
self._resolve_provider( self._resolve_provider(provider_arg[len("container."):]),
provider_arg[len("container.") :]
),
) )
injection = provider_type(*provider_args) injection = provider_type(*provider_args)
@ -124,7 +117,7 @@ class SchemaProcessorV1:
injection = None injection = None
if isinstance(arg, str) and arg.startswith("container."): if isinstance(arg, str) and arg.startswith("container."):
injection = self._resolve_provider(arg[len("container.") :]) injection = self._resolve_provider(arg[len("container."):])
# TODO: refactoring # TODO: refactoring
if isinstance(arg, dict): if isinstance(arg, dict):
@ -132,23 +125,16 @@ class SchemaProcessorV1:
provider_type = _get_provider_cls(arg.get("provider")) provider_type = _get_provider_cls(arg.get("provider"))
provides = arg.get("provides") provides = arg.get("provides")
if provides: if provides:
if isinstance(provides, str) and provides.startswith( if isinstance(provides, str) and provides.startswith("container."):
"container." provides = self._resolve_provider(provides[len("container."):])
):
provides = self._resolve_provider(
provides[len("container.") :]
)
else: else:
provides = _import_string(provides) provides = _import_string(provides)
provider_args.append(provides) provider_args.append(provides)
for provider_arg in arg.get("args", []): for provider_arg in arg.get("args", []):
if isinstance( if isinstance(provider_arg, str) \
provider_arg, str and provider_arg.startswith("container."):
) and provider_arg.startswith("container."):
provider_args.append( provider_args.append(
self._resolve_provider( self._resolve_provider(provider_arg[len("container."):]),
provider_arg[len("container.") :]
),
) )
injection = provider_type(*provider_args) injection = provider_type(*provider_args)
@ -172,7 +158,7 @@ class SchemaProcessorV1:
for segment in segments[1:]: for segment in segments[1:]:
parentheses = "" parentheses = ""
if "(" in segment and ")" in segment: if "(" in segment and ")" in segment:
parentheses = segment[segment.find("(") : segment.rfind(")") + 1] parentheses = segment[segment.find("("):segment.rfind(")")+1]
segment = segment.replace(parentheses, "") segment = segment.replace(parentheses, "")
try: try:
@ -204,12 +190,10 @@ def _get_provider_cls(provider_cls_name: str) -> Type[providers.Provider]:
if custom_provider_type: if custom_provider_type:
return custom_provider_type return custom_provider_type
raise SchemaError(f'Undefined provider class "{provider_cls_name}"') raise SchemaError(f"Undefined provider class \"{provider_cls_name}\"")
def _fetch_provider_cls_from_std( def _fetch_provider_cls_from_std(provider_cls_name: str) -> Optional[Type[providers.Provider]]:
provider_cls_name: str,
) -> Optional[Type[providers.Provider]]:
return getattr(providers, provider_cls_name, None) return getattr(providers, provider_cls_name, None)
@ -217,16 +201,12 @@ def _import_provider_cls(provider_cls_name: str) -> Optional[Type[providers.Prov
try: try:
cls = _import_string(provider_cls_name) cls = _import_string(provider_cls_name)
except (ImportError, ValueError) as exception: except (ImportError, ValueError) as exception:
raise SchemaError( raise SchemaError(f"Can not import provider \"{provider_cls_name}\"") from exception
f'Can not import provider "{provider_cls_name}"'
) from exception
except AttributeError: except AttributeError:
return None return None
else: else:
if isinstance(cls, type) and not issubclass(cls, providers.Provider): if isinstance(cls, type) and not issubclass(cls, providers.Provider):
raise SchemaError( raise SchemaError(f"Provider class \"{cls}\" is not a subclass of providers base class")
f'Provider class "{cls}" is not a subclass of providers base class'
)
return cls return cls

View File

@ -1,28 +1,34 @@
"""Wiring module.""" """Wiring module."""
import functools import functools
import inspect
import importlib import importlib
import importlib.machinery import importlib.machinery
import inspect
import pkgutil import pkgutil
import warnings
import sys import sys
from types import ModuleType from types import ModuleType
from typing import ( from typing import (
Any, Optional,
Callable,
Dict,
Generic,
Iterable, Iterable,
Iterator, Iterator,
Optional, Callable,
Set, Any,
Tuple, Tuple,
Type, Dict,
Generic,
TypeVar, TypeVar,
Type,
Union, Union,
Set,
cast, cast,
) )
if sys.version_info < (3, 7):
from typing import GenericMeta
else:
class GenericMeta(type):
...
# Hotfix, see: https://github.com/ets-labs/python-dependency-injector/issues/362 # Hotfix, see: https://github.com/ets-labs/python-dependency-injector/issues/362
if sys.version_info >= (3, 9): if sys.version_info >= (3, 9):
@ -30,21 +36,6 @@ if sys.version_info >= (3, 9):
else: else:
GenericAlias = None GenericAlias = None
if sys.version_info >= (3, 9):
from typing import Annotated, get_args, get_origin
else:
try:
from typing_extensions import Annotated, get_args, get_origin
except ImportError:
Annotated = object()
# For preventing NameError. Never executes
def get_args(hint):
return ()
def get_origin(tp):
return None
try: try:
import fastapi.params import fastapi.params
@ -66,6 +57,13 @@ except ImportError:
from . import providers from . import providers
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,
)
__all__ = ( __all__ = (
"wire", "wire",
@ -101,9 +99,7 @@ class PatchedRegistry:
def register_callable(self, patched: "PatchedCallable") -> None: def register_callable(self, patched: "PatchedCallable") -> None:
self._callables[patched.patched] = patched self._callables[patched.patched] = patched
def get_callables_from_module( def get_callables_from_module(self, module: ModuleType) -> Iterator[Callable[..., Any]]:
self, module: ModuleType
) -> Iterator[Callable[..., Any]]:
for patched_callable in self._callables.values(): for patched_callable in self._callables.values():
if not patched_callable.is_in_module(module): if not patched_callable.is_in_module(module):
continue continue
@ -118,9 +114,7 @@ class PatchedRegistry:
def register_attribute(self, patched: "PatchedAttribute") -> None: def register_attribute(self, patched: "PatchedAttribute") -> None:
self._attributes.add(patched) self._attributes.add(patched)
def get_attributes_from_module( def get_attributes_from_module(self, module: ModuleType) -> Iterator["PatchedAttribute"]:
self, module: ModuleType
) -> Iterator["PatchedAttribute"]:
for attribute in self._attributes: for attribute in self._attributes:
if not attribute.is_in_module(module): if not attribute.is_in_module(module):
continue continue
@ -145,11 +139,11 @@ class PatchedCallable:
) )
def __init__( def __init__(
self, self,
patched: Optional[Callable[..., Any]] = None, patched: Optional[Callable[..., Any]] = None,
original: Optional[Callable[..., Any]] = None, original: Optional[Callable[..., Any]] = None,
reference_injections: Optional[Dict[Any, Any]] = None, reference_injections: Optional[Dict[Any, Any]] = None,
reference_closing: Optional[Dict[Any, Any]] = None, reference_closing: Optional[Dict[Any, Any]] = None,
) -> None: ) -> None:
self.patched = patched self.patched = patched
self.original = original self.original = original
@ -220,21 +214,18 @@ class ProvidersMap:
) )
def resolve_provider( def resolve_provider(
self, self,
provider: Union[providers.Provider, str], provider: Union[providers.Provider, str],
modifier: Optional["Modifier"] = None, modifier: Optional["Modifier"] = None,
) -> Optional[providers.Provider]: ) -> Optional[providers.Provider]:
if isinstance(provider, providers.Delegate): if isinstance(provider, providers.Delegate):
return self._resolve_delegate(provider) return self._resolve_delegate(provider)
elif isinstance( elif isinstance(provider, (
provider, providers.ProvidedInstance,
( providers.AttributeGetter,
providers.ProvidedInstance, providers.ItemGetter,
providers.AttributeGetter, providers.MethodCaller,
providers.ItemGetter, )):
providers.MethodCaller,
),
):
return self._resolve_provided_instance(provider) return self._resolve_provided_instance(provider)
elif isinstance(provider, providers.ConfigurationOption): elif isinstance(provider, providers.ConfigurationOption):
return self._resolve_config_option(provider) return self._resolve_config_option(provider)
@ -246,9 +237,9 @@ class ProvidersMap:
return self._resolve_provider(provider) return self._resolve_provider(provider)
def _resolve_string_id( def _resolve_string_id(
self, self,
id: str, id: str,
modifier: Optional["Modifier"] = None, modifier: Optional["Modifier"] = None,
) -> Optional[providers.Provider]: ) -> Optional[providers.Provider]:
if id == self.CONTAINER_STRING_ID: if id == self.CONTAINER_STRING_ID:
return self._container.__self__ return self._container.__self__
@ -265,19 +256,16 @@ class ProvidersMap:
return provider return provider
def _resolve_provided_instance( def _resolve_provided_instance(
self, self,
original: providers.Provider, original: providers.Provider,
) -> Optional[providers.Provider]: ) -> Optional[providers.Provider]:
modifiers = [] modifiers = []
while isinstance( while isinstance(original, (
original,
(
providers.ProvidedInstance, providers.ProvidedInstance,
providers.AttributeGetter, providers.AttributeGetter,
providers.ItemGetter, providers.ItemGetter,
providers.MethodCaller, providers.MethodCaller,
), )):
):
modifiers.insert(0, original) modifiers.insert(0, original)
original = original.provides original = original.provides
@ -301,8 +289,8 @@ class ProvidersMap:
return new return new
def _resolve_delegate( def _resolve_delegate(
self, self,
original: providers.Delegate, original: providers.Delegate,
) -> Optional[providers.Provider]: ) -> Optional[providers.Provider]:
provider = self._resolve_provider(original.provides) provider = self._resolve_provider(original.provides)
if provider: if provider:
@ -310,9 +298,9 @@ class ProvidersMap:
return provider return provider
def _resolve_config_option( def _resolve_config_option(
self, self,
original: providers.ConfigurationOption, original: providers.ConfigurationOption,
as_: Any = None, as_: Any = None,
) -> Optional[providers.Provider]: ) -> Optional[providers.Provider]:
original_root = original.root original_root = original.root
new = self._resolve_provider(original_root) new = self._resolve_provider(original_root)
@ -336,8 +324,8 @@ class ProvidersMap:
return new return new
def _resolve_provider( def _resolve_provider(
self, self,
original: providers.Provider, original: providers.Provider,
) -> Optional[providers.Provider]: ) -> Optional[providers.Provider]:
try: try:
return self._map[original] return self._map[original]
@ -346,9 +334,9 @@ class ProvidersMap:
@classmethod @classmethod
def _create_providers_map( def _create_providers_map(
cls, cls,
current_container: Container, current_container: Container,
original_container: Container, original_container: Container,
) -> Dict[providers.Provider, providers.Provider]: ) -> Dict[providers.Provider, providers.Provider]:
current_providers = current_container.providers current_providers = current_container.providers
current_providers["__self__"] = current_container.__self__ current_providers["__self__"] = current_container.__self__
@ -361,9 +349,8 @@ class ProvidersMap:
original_provider = original_providers[provider_name] original_provider = original_providers[provider_name]
providers_map[original_provider] = current_provider providers_map[original_provider] = current_provider
if isinstance(current_provider, providers.Container) and isinstance( if isinstance(current_provider, providers.Container) \
original_provider, providers.Container and isinstance(original_provider, providers.Container):
):
subcontainer_map = cls._create_providers_map( subcontainer_map = cls._create_providers_map(
current_container=current_provider.container, current_container=current_provider.container,
original_container=original_provider.container, original_container=original_provider.container,
@ -389,21 +376,19 @@ class InspectFilter:
return werkzeug and isinstance(instance, werkzeug.local.LocalProxy) return werkzeug and isinstance(instance, werkzeug.local.LocalProxy)
def _is_starlette_request_cls(self, instance: object) -> bool: def _is_starlette_request_cls(self, instance: object) -> bool:
return ( return starlette \
starlette and isinstance(instance, type) \
and isinstance(instance, type) and _safe_is_subclass(instance, starlette.requests.Request)
and _safe_is_subclass(instance, starlette.requests.Request)
)
def _is_builtin(self, instance: object) -> bool: def _is_builtin(self, instance: object) -> bool:
return inspect.isbuiltin(instance) return inspect.isbuiltin(instance)
def wire( # noqa: C901 def wire( # noqa: C901
container: Container, container: Container,
*, *,
modules: Optional[Iterable[ModuleType]] = None, modules: Optional[Iterable[ModuleType]] = None,
packages: Optional[Iterable[ModuleType]] = None, packages: Optional[Iterable[ModuleType]] = None,
) -> None: ) -> None:
"""Wire container providers with provided packages and modules.""" """Wire container providers with provided packages and modules."""
modules = [*modules] if modules else [] modules = [*modules] if modules else []
@ -433,22 +418,18 @@ def wire( # noqa: C901
else: else:
for cls_member_name, cls_member in cls_members: for cls_member_name, cls_member in cls_members:
if _is_marker(cls_member): if _is_marker(cls_member):
_patch_attribute( _patch_attribute(cls, cls_member_name, cls_member, providers_map)
cls, cls_member_name, cls_member, providers_map
)
elif _is_method(cls_member): elif _is_method(cls_member):
_patch_method( _patch_method(cls, cls_member_name, cls_member, providers_map)
cls, cls_member_name, cls_member, providers_map
)
for patched in _patched_registry.get_callables_from_module(module): for patched in _patched_registry.get_callables_from_module(module):
_bind_injections(patched, providers_map) _bind_injections(patched, providers_map)
def unwire( # noqa: C901 def unwire( # noqa: C901
*, *,
modules: Optional[Iterable[ModuleType]] = None, modules: Optional[Iterable[ModuleType]] = None,
packages: Optional[Iterable[ModuleType]] = None, packages: Optional[Iterable[ModuleType]] = None,
) -> None: ) -> None:
"""Wire provided packages and modules with previous wired providers.""" """Wire provided packages and modules with previous wired providers."""
modules = [*modules] if modules else [] modules = [*modules] if modules else []
@ -462,9 +443,7 @@ def unwire( # noqa: C901
if inspect.isfunction(member): if inspect.isfunction(member):
_unpatch(module, name, member) _unpatch(module, name, member)
elif inspect.isclass(member): elif inspect.isclass(member):
for method_name, method in inspect.getmembers( for method_name, method in inspect.getmembers(member, inspect.isfunction):
member, inspect.isfunction
):
_unpatch(member, method_name, method) _unpatch(member, method_name, method)
for patched in _patched_registry.get_callables_from_module(module): for patched in _patched_registry.get_callables_from_module(module):
@ -483,10 +462,10 @@ def inject(fn: F) -> F:
def _patch_fn( def _patch_fn(
module: ModuleType, module: ModuleType,
name: str, name: str,
fn: Callable[..., Any], fn: Callable[..., Any],
providers_map: ProvidersMap, providers_map: ProvidersMap,
) -> None: ) -> None:
if not _is_patched(fn): if not _is_patched(fn):
reference_injections, reference_closing = _fetch_reference_injections(fn) reference_injections, reference_closing = _fetch_reference_injections(fn)
@ -500,16 +479,14 @@ def _patch_fn(
def _patch_method( def _patch_method(
cls: Type, cls: Type,
name: str, name: str,
method: Callable[..., Any], method: Callable[..., Any],
providers_map: ProvidersMap, providers_map: ProvidersMap,
) -> None: ) -> None:
if ( if hasattr(cls, "__dict__") \
hasattr(cls, "__dict__") and name in cls.__dict__ \
and name in cls.__dict__ and isinstance(cls.__dict__[name], (classmethod, staticmethod)):
and isinstance(cls.__dict__[name], (classmethod, staticmethod))
):
method = cls.__dict__[name] method = cls.__dict__[name]
fn = method.__func__ fn = method.__func__
else: else:
@ -530,15 +507,13 @@ def _patch_method(
def _unpatch( def _unpatch(
module: ModuleType, module: ModuleType,
name: str, name: str,
fn: Callable[..., Any], fn: Callable[..., Any],
) -> None: ) -> None:
if ( if hasattr(module, "__dict__") \
hasattr(module, "__dict__") and name in module.__dict__ \
and name in module.__dict__ and isinstance(module.__dict__[name], (classmethod, staticmethod)):
and isinstance(module.__dict__[name], (classmethod, staticmethod))
):
method = module.__dict__[name] method = module.__dict__[name]
fn = method.__func__ fn = method.__func__
@ -549,10 +524,10 @@ def _unpatch(
def _patch_attribute( def _patch_attribute(
member: Any, member: Any,
name: str, name: str,
marker: "_Marker", marker: "_Marker",
providers_map: ProvidersMap, providers_map: ProvidersMap,
) -> None: ) -> None:
provider = providers_map.resolve_provider(marker.provider, marker.modifier) provider = providers_map.resolve_provider(marker.provider, marker.modifier)
if provider is None: if provider is None:
@ -573,33 +548,16 @@ def _unpatch_attribute(patched: PatchedAttribute) -> None:
setattr(patched.member, patched.name, patched.marker) setattr(patched.member, patched.name, patched.marker)
def _extract_marker(parameter: inspect.Parameter) -> Optional["_Marker"]:
if get_origin(parameter.annotation) is Annotated:
marker = get_args(parameter.annotation)[1]
else:
marker = parameter.default
if not isinstance(marker, _Marker) and not _is_fastapi_depends(marker):
return None
if _is_fastapi_depends(marker):
marker = marker.dependency
if not isinstance(marker, _Marker):
return None
return marker
def _fetch_reference_injections( # noqa: C901 def _fetch_reference_injections( # noqa: C901
fn: Callable[..., Any], fn: Callable[..., Any],
) -> Tuple[Dict[str, Any], Dict[str, Any]]: ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
# Hotfix, see: # Hotfix, see:
# - https://github.com/ets-labs/python-dependency-injector/issues/362 # - https://github.com/ets-labs/python-dependency-injector/issues/362
# - https://github.com/ets-labs/python-dependency-injector/issues/398 # - https://github.com/ets-labs/python-dependency-injector/issues/398
if GenericAlias and any( if GenericAlias and any((
(fn is GenericAlias, getattr(fn, "__func__", None) is GenericAlias) fn is GenericAlias,
): getattr(fn, "__func__", None) is GenericAlias
)):
fn = fn.__init__ fn = fn.__init__
try: try:
@ -615,11 +573,18 @@ def _fetch_reference_injections( # noqa: C901
injections = {} injections = {}
closing = {} closing = {}
for parameter_name, parameter in signature.parameters.items(): for parameter_name, parameter in signature.parameters.items():
marker = _extract_marker(parameter) if not isinstance(parameter.default, _Marker) \
and not _is_fastapi_depends(parameter.default):
if marker is None:
continue continue
marker = parameter.default
if _is_fastapi_depends(marker):
marker = marker.dependency
if not isinstance(marker, _Marker):
continue
if isinstance(marker, Closing): if isinstance(marker, Closing):
marker = marker.provider marker = marker.provider
closing[parameter_name] = marker closing[parameter_name] = marker
@ -628,19 +593,20 @@ def _fetch_reference_injections( # noqa: C901
return injections, closing return injections, closing
def _locate_dependent_closing_args( def _locate_dependent_closing_args(provider: providers.Provider) -> Dict[str, providers.Provider]:
provider: providers.Provider, closing_deps: Dict[str, providers.Provider] if not hasattr(provider, "args"):
) -> Dict[str, providers.Provider]: return {}
for arg in [
*getattr(provider, "args", []),
*getattr(provider, "kwargs", {}).values(),
]:
if not isinstance(arg, providers.Provider):
continue
if isinstance(arg, providers.Resource):
closing_deps[str(id(arg))] = arg
_locate_dependent_closing_args(arg, closing_deps) closing_deps = {}
for arg in provider.args:
if not isinstance(arg, providers.Provider) or not hasattr(arg, "args"):
continue
if not arg.args and isinstance(arg, providers.Resource):
return {str(id(arg)): arg}
else:
closing_deps += _locate_dependent_closing_args(arg)
return closing_deps
def _bind_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> None: def _bind_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> None:
@ -664,8 +630,7 @@ def _bind_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> Non
if injection in patched_callable.reference_closing: if injection in patched_callable.reference_closing:
patched_callable.add_closing(injection, provider) patched_callable.add_closing(injection, provider)
deps = {} deps = _locate_dependent_closing_args(provider)
_locate_dependent_closing_args(provider, deps)
for key, dep in deps.items(): for key, dep in deps.items():
patched_callable.add_closing(key, dep) patched_callable.add_closing(key, dep)
@ -682,8 +647,8 @@ def _fetch_modules(package):
if not hasattr(package, "__path__") or not hasattr(package, "__name__"): if not hasattr(package, "__path__") or not hasattr(package, "__name__"):
return modules return modules
for module_info in pkgutil.walk_packages( for module_info in pkgutil.walk_packages(
path=package.__path__, path=package.__path__,
prefix=package.__name__ + ".", prefix=package.__name__ + ".",
): ):
module = importlib.import_module(module_info.name) module = importlib.import_module(module_info.name)
modules.append(module) modules.append(module)
@ -699,9 +664,9 @@ def _is_marker(member) -> bool:
def _get_patched( def _get_patched(
fn: F, fn: F,
reference_injections: Dict[Any, Any], reference_injections: Dict[Any, Any],
reference_closing: Dict[Any, Any], reference_closing: Dict[Any, Any],
) -> F: ) -> F:
patched_object = PatchedCallable( patched_object = PatchedCallable(
original=fn, original=fn,
@ -729,11 +694,9 @@ def _is_patched(fn) -> bool:
def _is_declarative_container(instance: Any) -> bool: def _is_declarative_container(instance: Any) -> bool:
return ( return (isinstance(instance, type)
isinstance(instance, type) and getattr(instance, "__IS_CONTAINER__", False) is True
and getattr(instance, "__IS_CONTAINER__", False) is True and getattr(instance, "declarative_parent", None) is None)
and getattr(instance, "declarative_parent", None) is None
)
def _safe_is_subclass(instance: Any, cls: Type) -> bool: def _safe_is_subclass(instance: Any, cls: Type) -> bool:
@ -746,10 +709,11 @@ def _safe_is_subclass(instance: Any, cls: Type) -> bool:
class Modifier: class Modifier:
def modify( def modify(
self, self,
provider: providers.ConfigurationOption, provider: providers.ConfigurationOption,
providers_map: ProvidersMap, providers_map: ProvidersMap,
) -> providers.Provider: ... ) -> providers.Provider:
...
class TypeModifier(Modifier): class TypeModifier(Modifier):
@ -758,9 +722,9 @@ class TypeModifier(Modifier):
self.type_ = type_ self.type_ = type_
def modify( def modify(
self, self,
provider: providers.ConfigurationOption, provider: providers.ConfigurationOption,
providers_map: ProvidersMap, providers_map: ProvidersMap,
) -> providers.Provider: ) -> providers.Provider:
return provider.as_(self.type_) return provider.as_(self.type_)
@ -798,9 +762,9 @@ class RequiredModifier(Modifier):
return self return self
def modify( def modify(
self, self,
provider: providers.ConfigurationOption, provider: providers.ConfigurationOption,
providers_map: ProvidersMap, providers_map: ProvidersMap,
) -> providers.Provider: ) -> providers.Provider:
provider = provider.required() provider = provider.required()
if self.type_modifier: if self.type_modifier:
@ -819,9 +783,9 @@ class InvariantModifier(Modifier):
self.id = id self.id = id
def modify( def modify(
self, self,
provider: providers.ConfigurationOption, provider: providers.ConfigurationOption,
providers_map: ProvidersMap, providers_map: ProvidersMap,
) -> providers.Provider: ) -> providers.Provider:
invariant_segment = providers_map.resolve_provider(self.id) invariant_segment = providers_map.resolve_provider(self.id)
return provider[invariant_segment] return provider[invariant_segment]
@ -854,9 +818,9 @@ class ProvidedInstance(Modifier):
return self return self
def modify( def modify(
self, self,
provider: providers.Provider, provider: providers.Provider,
providers_map: ProvidersMap, providers_map: ProvidersMap,
) -> providers.Provider: ) -> providers.Provider:
provider = provider.provided provider = provider.provided
for type_, value in self.segments: for type_, value in self.segments:
@ -874,14 +838,22 @@ def provided() -> ProvidedInstance:
return ProvidedInstance() return ProvidedInstance()
class _Marker(Generic[T]): class ClassGetItemMeta(GenericMeta):
def __getitem__(cls, item):
# Spike for Python 3.6
if isinstance(item, tuple):
return cls(*item)
return cls(item)
class _Marker(Generic[T], metaclass=ClassGetItemMeta):
__IS_MARKER__ = True __IS_MARKER__ = True
def __init__( def __init__(
self, self,
provider: Union[providers.Provider, Container, str], provider: Union[providers.Provider, Container, str],
modifier: Optional[Modifier] = None, modifier: Optional[Modifier] = None,
) -> None: ) -> None:
if _is_declarative_container(provider): if _is_declarative_container(provider):
provider = provider.__self__ provider = provider.__self__
@ -897,13 +869,16 @@ class _Marker(Generic[T]):
return self return self
class Provide(_Marker): ... class Provide(_Marker):
...
class Provider(_Marker): ... class Provider(_Marker):
...
class Closing(_Marker): ... class Closing(_Marker):
...
class AutoLoader: class AutoLoader:
@ -953,7 +928,8 @@ class AutoLoader:
super().exec_module(module) super().exec_module(module)
loader.wire_module(module) loader.wire_module(module)
class ExtensionFileLoader(importlib.machinery.ExtensionFileLoader): ... class ExtensionFileLoader(importlib.machinery.ExtensionFileLoader):
...
loader_details = [ loader_details = [
(SourcelessFileLoader, importlib.machinery.BYTECODE_SUFFIXES), (SourcelessFileLoader, importlib.machinery.BYTECODE_SUFFIXES),
@ -1006,7 +982,7 @@ _inspect_filter = InspectFilter()
_loader = AutoLoader() _loader = AutoLoader()
# Optimizations # Optimizations
from ._cwiring import _sync_inject # noqa from ._cwiring import _get_sync_patched # noqa
from ._cwiring import _async_inject # noqa from ._cwiring import _async_inject # noqa
@ -1022,18 +998,4 @@ def _get_async_patched(fn: F, patched: PatchedCallable) -> F:
patched.injections, patched.injections,
patched.closing, patched.closing,
) )
return _patched
return cast(F, _patched)
def _get_sync_patched(fn: F, patched: PatchedCallable) -> F:
@functools.wraps(fn)
def _patched(*args, **kwargs):
return _sync_inject(
fn,
args,
kwargs,
patched.injections,
patched.closing,
)
return cast(F, _patched)

View File

@ -0,0 +1,10 @@
[pytest]
testpaths = tests/unit/
python_files = test_*_py2_py3.py
asyncio_mode = auto
filterwarnings =
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:The \`scipy(.*?)\` namespace is deprecated(.*):DeprecationWarning
ignore:ssl\.PROTOCOL_TLS is deprecated:DeprecationWarning:botocore.*

Some files were not shown because too many files have changed in this diff Show More