Merge branch 'ets-labs:master' into provided-types

This commit is contained in:
Gonzalo Martinez 2025-01-07 11:29:12 -08:00 committed by GitHub
commit b547a9e088
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
93 changed files with 1718 additions and 210141 deletions

View File

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

1
.github/FUNDING.yml vendored Normal file
View File

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

View File

@ -15,11 +15,11 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.11 python-version: 3.13
- run: pip install tox - run: pip install tox
- run: tox - run: tox
env: env:
TOXENV: 3.11 TOXENV: 3.13
linters: linters:
name: Run linters name: Run linters
@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.11 python-version: 3.13
- run: pip install tox - run: pip install tox
- run: tox - run: tox
env: env:
@ -45,8 +45,10 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.11 python-version: 3.13
- run: python setup.py sdist - run: |
python -m pip install --upgrade build
python -m build --sdist
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
path: ./dist/* path: ./dist/*
@ -57,13 +59,13 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-22.04, windows-2019, macos-11] os: [ubuntu-22.04, windows-2019, macos-14]
env: env:
CIBW_SKIP: cp27-win* CIBW_SKIP: cp27-*
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Build wheels - name: Build wheels
uses: pypa/cibuildwheel@v2.11.3 uses: pypa/cibuildwheel@v2.20.0
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
path: ./wheelhouse/*.whl path: ./wheelhouse/*.whl
@ -72,13 +74,15 @@ jobs:
name: Build wheels (ubuntu-22.04-aarch64) name: Build wheels (ubuntu-22.04-aarch64)
needs: [tests, linters] needs: [tests, linters]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env:
CIBW_SKIP: cp27-*
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up QEMU - name: Set up QEMU
if: runner.os == 'Linux' if: runner.os == 'Linux'
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Build wheels - name: Build wheels
uses: pypa/cibuildwheel@v2.11.3 uses: pypa/cibuildwheel@v2.20.0
env: env:
CIBW_ARCHS_LINUX: aarch64 CIBW_ARCHS_LINUX: aarch64
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
@ -110,9 +114,9 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.11 python-version: 3.13
- run: pip install -r requirements-doc.txt
- run: pip install awscli - run: pip install awscli
- run: pip install -r requirements-doc.txt
- run: pip install -e . - run: pip install -e .
- run: (cd docs && make clean html) - run: (cd docs && make clean html)
- run: | - run: |

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy: strategy:
matrix: matrix:
python-version: [2.7, 3.5, 3.6, 3.7, pypy2.7, pypy3.9] python-version: [3.7]
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] python-version: [3.8, 3.9, "3.10", 3.11, 3.12, 3.13]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
@ -36,21 +36,32 @@ 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.11 python-version: 3.12
- run: pip install tox cython - run: pip install tox 'cython>=3,<4'
- run: make cythonize - run: tox -vv
- run: tox
env: env:
TOXENV: coveralls TOXENV: coveralls
@ -64,7 +75,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.11 python-version: 3.13
- run: pip install tox - run: pip install tox
- run: tox - run: tox
env: env:

14
.gitignore vendored
View File

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

View File

@ -1,49 +0,0 @@
[MASTER]
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
ignore=utils,tests
[MESSAGES CONTROL]
# Disable the message(s) with the given id(s).
# disable-msg=
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=5
[TYPECHECK]
ignore-mixin-members=yes
# ignored-classes=
zope=no
# generated-members=providedBy,implementedBy,rawDataReceived
[DESIGN]
# Maximum number of arguments for function / method
max-args=10
# Maximum number of locals for function / method body
max-locals=20
# Maximum number of return / yield for function / method body
max-returns=10
# Maximum number of branch for function / method body
max-branchs=10
# Maximum number of statements in function / method body
max-statements=60
# Maximum number of parents for a class (see R0901).
max-parents=10
# Maximum number of attributes for a class (see R0902).
max-attributes=30
# Minimum number of public methods for a class (see R0903).
min-public-methods=0
# Maximum number of public methods for a class (see R0904).
max-public-methods=30

View File

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

View File

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

View File

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

9
docs/_static/custom.css vendored Normal file
View File

@ -0,0 +1,9 @@
.no-border {
border: 0 !important;
box-shadow: none !important;
-webkit-box-shadow: none !important;
}
.no-border td {
border: 0px !important;
padding: 0px 10px 0px 0px !important;
}

View File

@ -1,11 +0,0 @@
var disqus_shortname;
var disqus_identifier;
$(function() {
var disqus_thread = $("#disqus_thread");
disqus_shortname = disqus_thread.data('disqus-shortname');
disqus_identifier = disqus_thread.data('disqus-identifier');
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
});

1
docs/_static/sponsor.html vendored Normal file
View File

@ -0,0 +1 @@
<iframe src="https://github.com/sponsors/rmk135/button" title="Sponsor Dependency Injector" height="32" width="114" style="border: 0; border-radius: 6px;"></iframe>

View File

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

View File

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

View File

@ -84,4 +84,6 @@ Run the application
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-multiple-containers>`_. You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-multiple-containers>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -90,4 +90,6 @@ Run the application
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-single-container>`_. You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/application-single-container>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

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

View File

@ -129,4 +129,6 @@ Run the application
You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_. You can find the source code and instructions for running on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/decoupled-packages>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

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

View File

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

View File

@ -116,4 +116,6 @@ Sources
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_. The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy>`_.
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

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

View File

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

View File

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

View File

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

View File

@ -310,4 +310,6 @@ A few useful links related to a dependency injection design pattern for further
+ https://github.com/ets-labs/python-dependency-injector + https://github.com/ets-labs/python-dependency-injector
+ https://pypi.org/project/dependency-injector/ + https://pypi.org/project/dependency-injector/
.. include:: ../sponsor.rst
.. disqus:: .. disqus::

View File

@ -7,6 +7,48 @@ 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.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.

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 object using the ``Configuration`` provider can load configuration from a ``pydantic_settings.BaseSettings`` object using the
:py:meth:`Configuration.from_pydantic` method: :py:meth:`Configuration.from_pydantic` method:
.. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py .. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py
:language: python :language: python
:lines: 3- :lines: 3-
:emphasize-lines: 31 :emphasize-lines: 32
To get the data from pydantic settings ``Configuration`` provider calls ``Settings.dict()`` method. To get the data from pydantic settings ``Configuration`` provider calls its ``model_dump()`` method.
If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments. If you need to pass an argument to this call, use ``.from_pydantic()`` keyword arguments.
.. code-block:: python .. code-block:: python
container.config.from_pydantic(Settings(), exclude={"optional"}) container.config.from_pydantic(Settings(), exclude={"optional"})
Alternatively, you can provide a ``pydantic`` settings object over the configuration provider argument. In that case, Alternatively, you can provide a ``pydantic_settings.BaseSettings`` object over the configuration provider argument. In that case,
the container will call ``config.from_pydantic()`` automatically: the container will call ``config.from_pydantic()`` automatically:
.. code-block:: python .. code-block:: python
@ -215,18 +215,23 @@ the container will call ``config.from_pydantic()`` automatically:
.. note:: .. note::
``Dependency Injector`` doesn't install ``pydantic`` by default. ``Dependency Injector`` doesn't install ``pydantic-settings`` by default.
You can install the ``Dependency Injector`` with an extra dependency:: You can install the ``Dependency Injector`` with an extra dependency::
pip install dependency-injector[pydantic] pip install dependency-injector[pydantic2]
or install ``pydantic`` directly:: or install ``pydantic-settings`` directly::
pip install pydantic pip install pydantic-settings
*Don't forget to mirror the changes in the requirements file.* *Don't forget to mirror the changes in the requirements file.*
.. note::
For backward-compatibility, Pydantic v1 is still supported.
Passing ``pydantic.BaseSettings`` instances will work just as fine as ``pydantic_settings.BaseSettings``.
Loading from a dictionary Loading from a dictionary
------------------------- -------------------------

View File

@ -24,7 +24,7 @@ returns it on the rest of the calls.
.. note:: .. note::
``Singleton`` provider makes dependencies injection only when creates an object. When an object ``Singleton`` provider makes dependencies injection only when it creates an object. When an object
is created and memorized ``Singleton`` provider just returns it without applying injections. is created and memorized ``Singleton`` provider just returns it without applying injections.
Specialization of the provided type and abstract singletons work the same like like for the Specialization of the provided type and abstract singletons work the same like like for the

7
docs/sponsor.rst Normal file
View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,4 +2,5 @@ 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.10-buster FROM python:3.13-bookworm
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,15 +59,16 @@ To run the tests do:
.. code-block:: bash .. code-block:: bash
docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
The output should be something like: The output should be something like:
.. code-block:: .. code-block::
platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
rootdir: /code rootdir: /code
plugins: asyncio-0.16.0, cov-3.0.0 plugins: cov-6.0.0, asyncio-0.24.0
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 2 items collected 2 items
monitoringdaemon/tests.py .. [100%] monitoringdaemon/tests.py .. [100%]

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, event_loop): async def test_dispatcher(container, caplog):
caplog.set_level("INFO") caplog.set_level("INFO")
example_monitor_mock = mock.AsyncMock() example_monitor_mock = mock.AsyncMock()
@ -72,6 +72,7 @@ async def test_dispatcher(container, caplog, event_loop):
httpbin_monitor=httpbin_monitor_mock, httpbin_monitor=httpbin_monitor_mock,
): ):
dispatcher = container.dispatcher() dispatcher = container.dispatcher()
event_loop = asyncio.get_running_loop()
event_loop.create_task(dispatcher.start()) event_loop.create_task(dispatcher.start())
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
dispatcher.stop() dispatcher.stop()

View File

@ -1,4 +1,4 @@
FROM python:3.10-buster FROM python:3.13-bookworm
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.10.9, pytest-7.2.0, pluggy-1.0.0 platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
rootdir: /code rootdir: /code
plugins: cov-4.0.0, asyncio-0.20.3 plugins: cov-6.0.0, asyncio-0.24.0, anyio-4.7.0
collected 1 item asyncio: mode=Mode.STRICT, default_loop_scope=None
fastapiredis/tests.py . [100%] fastapiredis/tests.py . [100%]

View File

@ -1,6 +1,6 @@
from typing import AsyncIterator from typing import AsyncIterator
from aioredis import from_url, Redis from redis.asyncio import from_url, Redis
async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]: async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]:

View File

@ -1,6 +1,6 @@
"""Services module.""" """Services module."""
from aioredis import Redis from redis.asyncio 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 AsyncClient from httpx import ASGITransport, AsyncClient
from .application import app, container from .application import app, container
from .services import Service from .services import Service
@ -11,7 +11,10 @@ from .services import Service
@pytest.fixture @pytest.fixture
def client(event_loop): def client(event_loop):
client = AsyncClient(app=app, base_url="http://test") client = AsyncClient(
transport=ASGITransport(app=app),
base_url="http://test",
)
yield client yield client
event_loop.run_until_complete(client.aclose()) event_loop.run_until_complete(client.aclose())

View File

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

View File

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

View File

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

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.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 platform linux -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
rootdir: /code rootdir: /code
plugins: cov-3.0.0 plugins: cov-6.0.0, anyio-4.7.0
collected 7 items collected 7 items
webapp/tests.py ....... [100%] webapp/tests.py ....... [100%]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -0,0 +1,39 @@
Integration With Starlette-based Frameworks
===========================================
This is a `Starlette <https://www.starlette.io/>`_ +
`Dependency Injector <https://python-dependency-injector.ets-labs.org/>`_ example application
utilizing `lifespan API <https://www.starlette.io/lifespan/>`_.
.. note::
Pretty much `any framework built on top of Starlette <https://www.starlette.io/third-party-packages/#frameworks>`_
supports this feature (`FastAPI <https://fastapi.tiangolo.com/advanced/events/#lifespan>`_,
`Xpresso <https://xpresso-api.dev/latest/tutorial/lifespan/>`_, etc...).
Run
---
Create virtual environment:
.. code-block:: bash
python -m venv env
. env/bin/activate
Install requirements:
.. code-block:: bash
pip install -r requirements.txt
To run the application do:
.. code-block:: bash
python example.py
# or (logging won't be configured):
uvicorn --factory example:container.app
After that visit http://127.0.0.1:8000/ in your browser or use CLI command (``curl``, ``httpie``,
etc).

View File

@ -0,0 +1,59 @@
#!/usr/bin/env python
from logging import basicConfig, getLogger
from dependency_injector.containers import DeclarativeContainer
from dependency_injector.ext.starlette import Lifespan
from dependency_injector.providers import Factory, Resource, Self, Singleton
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
count = 0
def init():
log = getLogger(__name__)
log.info("Inittializing resources")
yield
log.info("Cleaning up resources")
async def homepage(request: Request) -> JSONResponse:
global count
response = JSONResponse({"hello": "world", "count": count})
count += 1
return response
class Container(DeclarativeContainer):
__self__ = Self()
lifespan = Singleton(Lifespan, __self__)
logging = Resource(
basicConfig,
level="DEBUG",
datefmt="%Y-%m-%d %H:%M",
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
init = Resource(init)
app = Factory(
Starlette,
debug=True,
lifespan=lifespan,
routes=[Route("/", homepage)],
)
container = Container()
if __name__ == "__main__":
import uvicorn
uvicorn.run(
container.app,
factory=True,
# NOTE: `None` prevents uvicorn from configuring logging, which is
# impossible via CLI
log_config=None,
)

View File

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

View File

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

101
pyproject.toml Normal file
View File

@ -0,0 +1,101 @@
[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,9 +1,11 @@
cython==0.29.32 cython==3.0.11
setuptools
pytest pytest
pytest-asyncio pytest-asyncio
tox tox
coverage coverage
flake8 flake8
flake8-pyproject
pydocstyle pydocstyle
sphinx_autobuild sphinx_autobuild
pip pip
@ -11,7 +13,7 @@ mypy
pyyaml pyyaml
httpx httpx
fastapi fastapi
pydantic pydantic==1.10.17
numpy numpy
scipy scipy
boto3 boto3

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<3.5.0 sphinx
# TODO: unpin jinja2 after sphinx update to 4+ # TODO: unpin jinja2 after sphinx update to 4+
jinja2<3.1 jinja2
-e git+https://github.com/rmk135/sphinxcontrib-disqus.git#egg=sphinxcontrib-disqus sphinx-disqus==1.3.0
-r requirements-ext.txt -r requirements-ext.txt

View File

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

View File

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

154
setup.py
View File

@ -1,130 +1,42 @@
"""`Dependency injector` setup script.""" """`Dependency injector` setup script."""
import os import os
import re
import sys
from setuptools import setup, Extension from Cython.Build import cythonize
from Cython.Compiler import Options
from setuptools import Extension, setup
debug = os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1"
def _open(filename): defined_macros = []
if sys.version_info[0] == 2: compiler_directives = {
return open(filename) "language_level": 3,
return open(filename, encoding="utf-8") "profile": debug,
"linetrace": debug,
}
# Defining setup variables: Options.annotate = debug
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 os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1": if debug:
defined_macros["CYTHON_TRACE"] = 1 defined_macros.extend(
defined_macros["CYTHON_TRACE_NOGIL"] = 1 [
defined_macros["CYTHON_CLINE_IN_TRACEBACK"] = 1 ("CYTHON_TRACE", "1"),
("CYTHON_TRACE_NOGIL", "1"),
("CYTHON_CLINE_IN_TRACEBACK", "1"),
]
)
setup(name="dependency-injector", setup(
version=version, ext_modules=cythonize(
description="Dependency injection framework for Python", [
long_description=description, Extension(
author="Roman Mogylatov", "*",
author_email="rmogilatov@gmail.com", ["src/**/*.pyx"],
maintainer="Roman Mogylatov", define_macros=defined_macros,
maintainer_email="rmogilatov@gmail.com", ),
url="https://github.com/ets-labs/python-dependency-injector", ],
download_url="https://pypi.python.org/pypi/dependency_injector", annotate=debug,
packages=[ show_all_warnings=True,
"dependency_injector", compiler_directives=compiler_directives,
"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.41.0" __version__ = "4.45.0"
"""Version number. """Version number.
:type: str :type: str

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -18,8 +18,6 @@ try:
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
@ -201,7 +199,7 @@ class DynamicContainer(Container):
:rtype: None :rtype: None
""" """
for name, provider in six.iteritems(providers): for name, provider in providers.items():
setattr(self, name, provider) setattr(self, name, provider)
def set_provider(self, name, provider): def set_provider(self, name, provider):
@ -234,7 +232,7 @@ class DynamicContainer(Container):
self.overridden += (overriding,) self.overridden += (overriding,)
for name, provider in six.iteritems(overriding.providers): for name, provider in overriding.providers.items():
try: try:
getattr(self, name).override(provider) getattr(self, name).override(provider)
except AttributeError: except AttributeError:
@ -250,7 +248,7 @@ class DynamicContainer(Container):
:rtype: None :rtype: None
""" """
overridden_providers = [] overridden_providers = []
for name, overriding_provider in six.iteritems(overriding_providers): for name, overriding_provider in overriding_providers.items():
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)
@ -266,7 +264,7 @@ class DynamicContainer(Container):
self.overridden = self.overridden[:-1] self.overridden = self.overridden[:-1]
for provider in six.itervalues(self.providers): for provider in self.providers.values():
provider.reset_last_overriding() provider.reset_last_overriding()
def reset_override(self): def reset_override(self):
@ -276,7 +274,7 @@ class DynamicContainer(Container):
""" """
self.overridden = tuple() self.overridden = tuple()
for provider in six.itervalues(self.providers): for provider in self.providers.values():
provider.reset_override() provider.reset_override()
def is_auto_wiring_enabled(self): def is_auto_wiring_enabled(self):
@ -495,13 +493,13 @@ class DeclarativeContainerMetaClass(type):
containers = { containers = {
name: container name: container
for name, container in six.iteritems(attributes) for name, container in attributes.items()
if is_container(container) if is_container(container)
} }
cls_providers = { cls_providers = {
name: provider name: provider
for name, provider in six.iteritems(attributes) for name, provider in attributes.items()
if isinstance(provider, providers.Provider) and not isinstance(provider, providers.Self) if isinstance(provider, providers.Provider) and not isinstance(provider, providers.Self)
} }
@ -509,7 +507,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 six.iteritems(base.providers) for name, provider in base.providers.items()
} }
all_providers = {} all_providers = {}
@ -536,10 +534,10 @@ class DeclarativeContainerMetaClass(type):
self.set_container(cls) self.set_container(cls)
cls.__self__ = self cls.__self__ = self
for provider in six.itervalues(cls.providers): for provider in cls.providers.values():
_check_provider_type(cls, provider) _check_provider_type(cls, provider)
for provider in six.itervalues(cls.cls_providers): for provider in cls.cls_providers.values():
if isinstance(provider, providers.CHILD_PROVIDERS): if isinstance(provider, providers.CHILD_PROVIDERS):
provider.assign_parent(cls) provider.assign_parent(cls)
@ -641,8 +639,7 @@ class DeclarativeContainerMetaClass(type):
return self return self
@six.add_metaclass(DeclarativeContainerMetaClass) class DeclarativeContainer(Container, metaclass=DeclarativeContainerMetaClass):
class DeclarativeContainer(Container):
"""Declarative inversion of control container. """Declarative inversion of control container.
.. code-block:: python .. code-block:: python
@ -767,7 +764,7 @@ class DeclarativeContainer(Container):
cls.overridden += (overriding,) cls.overridden += (overriding,)
for name, provider in six.iteritems(overriding.cls_providers): for name, provider in overriding.cls_providers.items():
try: try:
getattr(cls, name).override(provider) getattr(cls, name).override(provider)
except AttributeError: except AttributeError:
@ -784,7 +781,7 @@ class DeclarativeContainer(Container):
cls.overridden = cls.overridden[:-1] cls.overridden = cls.overridden[:-1]
for provider in six.itervalues(cls.providers): for provider in cls.providers.values():
provider.reset_last_overriding() provider.reset_last_overriding()
@classmethod @classmethod
@ -795,7 +792,7 @@ class DeclarativeContainer(Container):
""" """
cls.overridden = tuple() cls.overridden = tuple()
for provider in six.itervalues(cls.providers): for provider in cls.providers.values():
provider.reset_override() provider.reset_override()
@ -858,7 +855,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 six.iteritems(new_providers): for new_provider_name, new_provider in new_providers.items():
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]
@ -877,7 +874,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 six.iteritems(new_providers): for name, provider in new_providers.items():
setattr(new_container, name, provider) setattr(new_container, name, provider)
return new_container return new_container

View File

@ -10,3 +10,24 @@ 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

@ -0,0 +1,52 @@
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

@ -20,10 +20,10 @@ cdef tuple __COROUTINE_TYPES
# Base providers # Base providers
cdef class Provider(object): 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)
@ -34,32 +34,32 @@ cdef class Provider(object):
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):
@ -67,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)
@ -117,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):
@ -129,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)
@ -167,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):
@ -181,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)
@ -215,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(object): 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):
@ -303,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)
@ -314,12 +314,12 @@ cpdef tuple parse_named_injections(dict kwargs)
# Utils # Utils
cdef class OverridingContext(object): cdef class OverridingContext(object):
cdef Provider __overridden cdef Provider _overridden
cdef Provider __overriding cdef Provider _overriding
cdef class BaseSingletonResetContext(object): cdef class BaseSingletonResetContext(object):
cdef object __singleton cdef object _singleton
cdef class SingletonResetContext(BaseSingletonResetContext): cdef class SingletonResetContext(BaseSingletonResetContext):
@ -356,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):
@ -633,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,
) )
@ -648,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

@ -530,7 +530,21 @@ 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(instance: Any, memo: Optional[_Dict[Any, Any]] = None) -> Any: ...
def deepcopy_args(
provider: Provider[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]: ...

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,12 @@
testpaths = tests/unit/ testpaths = tests/unit/
python_files = test_*_py3*.py python_files = test_*_py3*.py
asyncio_mode = auto asyncio_mode = auto
markers =
pydantic: Tests with Pydantic as a dependency
filterwarnings = filterwarnings =
ignore:Module \"dependency_injector.ext.aiohttp\" is deprecated since version 4\.0\.0:DeprecationWarning 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:Module \"dependency_injector.ext.flask\" is deprecated since version 4\.0\.0:DeprecationWarning
ignore:Please use \`.*?\` from the \`scipy.*?\`(.*?)namespace is deprecated\.:DeprecationWarning ignore:Please use \`.*?\` from the \`scipy.*?\`(.*?)namespace is deprecated\.:DeprecationWarning
ignore:The \`scipy(.*?)\` namespace is deprecated(.*):DeprecationWarning ignore:Please import \`.*?\` from the \`scipy(.*?)\` namespace(.*):DeprecationWarning
ignore:\`scipy(.*?)\` is deprecated(.*):DeprecationWarning
ignore:ssl\.PROTOCOL_TLS is deprecated:DeprecationWarning:botocore.* ignore:ssl\.PROTOCOL_TLS is deprecated:DeprecationWarning:botocore.*

View File

@ -1,12 +1,13 @@
from pathlib import Path from pathlib import Path
from typing import Any
from dependency_injector import providers from dependency_injector import providers
from pydantic import BaseSettings as PydanticSettings from pydantic_settings import BaseSettings as PydanticSettings
# Test 1: to check the getattr # Test 1: to check the getattr
config1 = providers.Configuration() config1: providers.Configuration = providers.Configuration()
provider1 = providers.Factory(dict, a=config1.a) provider1: providers.Provider[dict] = providers.Factory(dict, a=config1.a)
# Test 2: to check the from_*() method # Test 2: to check the from_*() method
config2 = providers.Configuration() config2 = providers.Configuration()

View File

@ -4,6 +4,7 @@ from aiohttp import web, test_utils
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.ext import aiohttp from dependency_injector.ext import aiohttp
from pytest import fixture, mark from pytest import fixture, mark
from pytest_asyncio import fixture as aio_fixture
async def index_view(_): async def index_view(_):
@ -63,7 +64,7 @@ def app():
return app return app
@fixture @aio_fixture
async def client(app): async def client(app):
async with test_utils.TestClient(test_utils.TestServer(app)) as client: async with test_utils.TestClient(test_utils.TestServer(app)) as client:
yield client yield client

View File

@ -0,0 +1,41 @@
from typing import AsyncIterator, Iterator
from unittest.mock import ANY
from pytest import mark
from dependency_injector.containers import DeclarativeContainer
from dependency_injector.ext.starlette import Lifespan
from dependency_injector.providers import Resource
class TestLifespan:
@mark.parametrize("sync", [False, True])
@mark.asyncio
async def test_context_manager(self, sync: bool) -> None:
init, shutdown = False, False
def sync_resource() -> Iterator[None]:
nonlocal init, shutdown
init = True
yield
shutdown = True
async def async_resource() -> AsyncIterator[None]:
nonlocal init, shutdown
init = True
yield
shutdown = True
class Container(DeclarativeContainer):
x = Resource(sync_resource if sync else async_resource)
container = Container()
lifespan = Lifespan(container)
async with lifespan(ANY) as scope:
assert scope is None
assert init
assert shutdown

View File

@ -1,41 +1,60 @@
"""Configuration.from_pydantic() tests.""" """Configuration.from_pydantic() tests."""
import pydantic from pydantic import BaseModel
from dependency_injector import providers, errors
try:
from pydantic_settings import (
BaseSettings, # type: ignore[import-not-found,unused-ignore]
)
except ImportError:
try:
from pydantic import BaseSettings # type: ignore[no-redef,unused-ignore]
except ImportError:
class BaseSettings: # type: ignore[no-redef]
"""No-op fallback"""
from pytest import fixture, mark, raises from pytest import fixture, mark, raises
from dependency_injector import errors, providers
class Section11(pydantic.BaseModel): pytestmark = mark.pydantic
value1 = 1
class Section12(pydantic.BaseModel): class Section11(BaseModel):
value2 = 2 value1: int = 1
class Settings1(pydantic.BaseSettings): class Section12(BaseModel):
section1 = Section11() value2: int = 2
section2 = Section12()
class Section21(pydantic.BaseModel): class Settings1(BaseSettings):
value1 = 11 section1: Section11 = Section11()
value11 = 11 section2: Section12 = Section12()
class Section3(pydantic.BaseModel): class Section21(BaseModel):
value3 = 3 value1: int = 11
value11: int = 11
class Settings2(pydantic.BaseSettings): class Section3(BaseModel):
section1 = Section21() value3: int = 3
section3 = Section3()
class Settings2(BaseSettings):
section1: Section21 = Section21()
section3: Section3 = Section3()
@fixture @fixture
def no_pydantic_module_installed(): def no_pydantic_module_installed():
providers.pydantic = None has_pydantic_settings = providers.has_pydantic_settings
providers.has_pydantic_settings = False
yield yield
providers.pydantic = pydantic providers.has_pydantic_settings = has_pydantic_settings
def test(config): def test(config):
@ -82,66 +101,70 @@ def test_merge(config):
def test_empty_settings(config): def test_empty_settings(config):
config.from_pydantic(pydantic.BaseSettings()) config.from_pydantic(BaseSettings())
assert config() == {} assert config() == {}
@mark.parametrize("config_type", ["strict"]) @mark.parametrize("config_type", ["strict"])
def test_empty_settings_strict_mode(config): def test_empty_settings_strict_mode(config):
with raises(ValueError): with raises(ValueError):
config.from_pydantic(pydantic.BaseSettings()) config.from_pydantic(BaseSettings())
def test_option_empty_settings(config): def test_option_empty_settings(config):
config.option.from_pydantic(pydantic.BaseSettings()) config.option.from_pydantic(BaseSettings())
assert config.option() == {} assert config.option() == {}
@mark.parametrize("config_type", ["strict"]) @mark.parametrize("config_type", ["strict"])
def test_option_empty_settings_strict_mode(config): def test_option_empty_settings_strict_mode(config):
with raises(ValueError): with raises(ValueError):
config.option.from_pydantic(pydantic.BaseSettings()) config.option.from_pydantic(BaseSettings())
def test_required_empty_settings(config): def test_required_empty_settings(config):
with raises(ValueError): with raises(ValueError):
config.from_pydantic(pydantic.BaseSettings(), required=True) config.from_pydantic(BaseSettings(), required=True)
def test_required_option_empty_settings(config): def test_required_option_empty_settings(config):
with raises(ValueError): with raises(ValueError):
config.option.from_pydantic(pydantic.BaseSettings(), required=True) config.option.from_pydantic(BaseSettings(), required=True)
@mark.parametrize("config_type", ["strict"]) @mark.parametrize("config_type", ["strict"])
def test_not_required_empty_settings_strict_mode(config): def test_not_required_empty_settings_strict_mode(config):
config.from_pydantic(pydantic.BaseSettings(), required=False) config.from_pydantic(BaseSettings(), required=False)
assert config() == {} assert config() == {}
@mark.parametrize("config_type", ["strict"]) @mark.parametrize("config_type", ["strict"])
def test_not_required_option_empty_settings_strict_mode(config): def test_not_required_option_empty_settings_strict_mode(config):
config.option.from_pydantic(pydantic.BaseSettings(), required=False) config.option.from_pydantic(BaseSettings(), required=False)
assert config.option() == {} assert config.option() == {}
assert config() == {"option": {}} assert config() == {"option": {}}
def test_not_instance_of_settings(config): def test_not_instance_of_settings(config):
with raises(errors.Error) as error: with raises(
errors.Error,
match=(
r"Unable to recognize settings instance, expect \"pydantic(?:_settings)?\.BaseSettings\", "
r"got {0} instead".format({})
),
):
config.from_pydantic({}) config.from_pydantic({})
assert error.value.args[0] == (
"Unable to recognize settings instance, expect \"pydantic.BaseSettings\", "
"got {0} instead".format({})
)
def test_option_not_instance_of_settings(config): def test_option_not_instance_of_settings(config):
with raises(errors.Error) as error: with raises(
errors.Error,
match=(
r"Unable to recognize settings instance, expect \"pydantic(?:_settings)?\.BaseSettings\", "
"got {0} instead".format({})
),
):
config.option.from_pydantic({}) config.option.from_pydantic({})
assert error.value.args[0] == (
"Unable to recognize settings instance, expect \"pydantic.BaseSettings\", "
"got {0} instead".format({})
)
def test_subclass_instead_of_instance(config): def test_subclass_instead_of_instance(config):
@ -164,21 +187,25 @@ def test_option_subclass_instead_of_instance(config):
@mark.usefixtures("no_pydantic_module_installed") @mark.usefixtures("no_pydantic_module_installed")
def test_no_pydantic_installed(config): def test_no_pydantic_installed(config):
with raises(errors.Error) as error: with raises(
errors.Error,
match=(
r"Unable to load pydantic configuration - pydantic(?:_settings)? is not installed\. "
r"Install pydantic or install Dependency Injector with pydantic extras: "
r"\"pip install dependency-injector\[pydantic2?\]\""
),
):
config.from_pydantic(Settings1()) config.from_pydantic(Settings1())
assert error.value.args[0] == (
"Unable to load pydantic configuration - pydantic is not installed. "
"Install pydantic or install Dependency Injector with pydantic extras: "
"\"pip install dependency-injector[pydantic]\""
)
@mark.usefixtures("no_pydantic_module_installed") @mark.usefixtures("no_pydantic_module_installed")
def test_option_no_pydantic_installed(config): def test_option_no_pydantic_installed(config):
with raises(errors.Error) as error: with raises(
errors.Error,
match=(
r"Unable to load pydantic configuration - pydantic(?:_settings)? is not installed\. "
r"Install pydantic or install Dependency Injector with pydantic extras: "
r"\"pip install dependency-injector\[pydantic2?\]\""
),
):
config.option.from_pydantic(Settings1()) config.option.from_pydantic(Settings1())
assert error.value.args[0] == (
"Unable to load pydantic configuration - pydantic is not installed. "
"Install pydantic or install Dependency Injector with pydantic extras: "
"\"pip install dependency-injector[pydantic]\""
)

View File

@ -1,35 +1,52 @@
"""Configuration.from_pydantic() tests.""" """Configuration.from_pydantic() tests."""
import pydantic from pydantic import BaseModel
from dependency_injector import providers
try:
from pydantic_settings import (
BaseSettings, # type: ignore[import-not-found,unused-ignore]
)
except ImportError:
try:
from pydantic import BaseSettings # type: ignore[no-redef,unused-ignore]
except ImportError:
class BaseSettings: # type: ignore[no-redef]
"""No-op fallback"""
from pytest import fixture, mark, raises from pytest import fixture, mark, raises
from dependency_injector import providers
class Section11(pydantic.BaseModel): pytestmark = mark.pydantic
value1 = 1
class Section12(pydantic.BaseModel): class Section11(BaseModel):
value2 = 2 value1: int = 1
class Settings1(pydantic.BaseSettings): class Section12(BaseModel):
section1 = Section11() value2: int = 2
section2 = Section12()
class Section21(pydantic.BaseModel): class Settings1(BaseSettings):
value1 = 11 section1: Section11 = Section11()
value11 = 11 section2: Section12 = Section12()
class Section3(pydantic.BaseModel): class Section21(BaseModel):
value3 = 3 value1: int = 11
value11: int = 11
class Settings2(pydantic.BaseSettings): class Section3(BaseModel):
section1 = Section21() value3: int = 3
section3 = Section3()
class Settings2(BaseSettings):
section1: Section21 = Section21()
section3: Section3 = Section3()
@fixture @fixture
@ -86,10 +103,10 @@ def test_copy(config, pydantic_settings_1, pydantic_settings_2):
def test_set_pydantic_settings(config): def test_set_pydantic_settings(config):
class Settings3(pydantic.BaseSettings): class Settings3(BaseSettings):
... ...
class Settings4(pydantic.BaseSettings): class Settings4(BaseSettings):
... ...
settings_3 = Settings3() settings_3 = Settings3()
@ -100,27 +117,27 @@ def test_set_pydantic_settings(config):
def test_file_does_not_exist(config): def test_file_does_not_exist(config):
config.set_pydantic_settings([pydantic.BaseSettings()]) config.set_pydantic_settings([BaseSettings()])
config.load() config.load()
assert config() == {} assert config() == {}
@mark.parametrize("config_type", ["strict"]) @mark.parametrize("config_type", ["strict"])
def test_file_does_not_exist_strict_mode(config): def test_file_does_not_exist_strict_mode(config):
config.set_pydantic_settings([pydantic.BaseSettings()]) config.set_pydantic_settings([BaseSettings()])
with raises(ValueError): with raises(ValueError):
config.load() config.load()
assert config() == {} assert config() == {}
def test_required_file_does_not_exist(config): def test_required_file_does_not_exist(config):
config.set_pydantic_settings([pydantic.BaseSettings()]) config.set_pydantic_settings([BaseSettings()])
with raises(ValueError): with raises(ValueError):
config.load(required=True) config.load(required=True)
@mark.parametrize("config_type", ["strict"]) @mark.parametrize("config_type", ["strict"])
def test_not_required_file_does_not_exist_strict_mode(config): def test_not_required_file_does_not_exist_strict_mode(config):
config.set_pydantic_settings([pydantic.BaseSettings()]) config.set_pydantic_settings([BaseSettings()])
config.load(required=False) config.load(required=False)
assert config() == {} assert config() == {}

View File

@ -0,0 +1,20 @@
import pytest
from dependency_injector.containers import Container
from dependency_injector.providers import ThreadLocalSingleton
class FailingClass:
def __init__(self):
raise ValueError("FAILING CLASS")
class TestContainer(Container):
failing_class = ThreadLocalSingleton(FailingClass)
def test_on_failure_value_error_is_raised():
container = TestContainer()
with pytest.raises(ValueError, match="FAILING CLASS"):
container.failing_class()

View File

@ -0,0 +1,65 @@
import sys
from typing import Any, Dict, NoReturn
from pytest import raises
from dependency_injector.errors import NonCopyableArgumentError
from dependency_injector.providers import (
Provider,
deepcopy,
deepcopy_args,
deepcopy_kwargs,
)
class NonCopiable:
def __deepcopy__(self, memo: Dict[int, Any]) -> NoReturn:
raise NotImplementedError
def test_deepcopy_streams_not_copied() -> None:
l = [sys.stdin, sys.stdout, sys.stderr]
assert deepcopy(l) == l
def test_deepcopy_args() -> None:
provider = Provider[None]()
copiable = NonCopiable()
memo: Dict[int, Any] = {id(copiable): copiable}
assert deepcopy_args(provider, (1, copiable), memo) == (1, copiable)
def test_deepcopy_args_non_copiable() -> None:
provider = Provider[None]()
copiable = NonCopiable()
memo: Dict[int, Any] = {id(copiable): copiable}
with raises(
NonCopyableArgumentError,
match=r"^Couldn't copy argument at index 3 for provider ",
):
deepcopy_args(provider, (1, copiable, object(), NonCopiable()), memo)
def test_deepcopy_kwargs() -> None:
provider = Provider[None]()
copiable = NonCopiable()
memo: Dict[int, Any] = {id(copiable): copiable}
assert deepcopy_kwargs(provider, {"x": 1, "y": copiable}, memo) == {
"x": 1,
"y": copiable,
}
def test_deepcopy_kwargs_non_copiable() -> None:
provider = Provider[None]()
copiable = NonCopiable()
memo: Dict[int, Any] = {id(copiable): copiable}
with raises(
NonCopyableArgumentError,
match=r"^Couldn't copy keyword argument z for provider ",
):
deepcopy_kwargs(provider, {"x": 1, "y": copiable, "z": NonCopiable()}, memo)

View File

@ -1,11 +1,9 @@
from flask import Flask, jsonify, request, current_app, session, g from flask import Flask, jsonify, request, current_app, session, g
from flask import _request_ctx_stack, _app_ctx_stack
from dependency_injector import containers, providers from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import inject, Provide
# This is here for testing wiring bypasses these objects without crashing # This is here for testing wiring bypasses these objects without crashing
request, current_app, session, g # noqa request, current_app, session, g # noqa
_request_ctx_stack, _app_ctx_stack # noqa
class Service: class Service:

View File

@ -1,5 +1,6 @@
from httpx import AsyncClient from httpx import ASGITransport, AsyncClient
from pytest import fixture, mark from pytest import fixture, mark
from pytest_asyncio import fixture as aio_fixture
# Runtime import to avoid syntax errors in samples on Python < 3.5 and reach top-dir # Runtime import to avoid syntax errors in samples on Python < 3.5 and reach top-dir
import os import os
@ -16,9 +17,9 @@ sys.path.append(_SAMPLES_DIR)
from wiringfastapi import web from wiringfastapi import web
@fixture @aio_fixture
async def async_client(): async def async_client():
client = AsyncClient(app=web.app, base_url="http://test") client = AsyncClient(transport=ASGITransport(app=web.app), base_url="http://test")
yield client yield client
await client.aclose() await client.aclose()

90
tox.ini
View File

@ -1,87 +1,89 @@
[tox] [tox]
parallel_show_output = true
envlist= envlist=
coveralls, pylint, flake8, pydocstyle, 2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, pypy2.7, pypy3.9 coveralls, pylint, flake8, pydocstyle, pydantic-v1, pydantic-v2, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, pypy3.9, pypy3.10
[testenv] [testenv]
deps= deps=
pytest pytest
pytest-asyncio pytest-asyncio
# TODO: Hotfix, remove when fixed https://github.com/aio-libs/aiohttp/issues/5107
typing_extensions
httpx httpx
fastapi fastapi
flask
aiohttp
numpy numpy
scipy scipy
boto3 boto3
mypy_boto3_s3 mypy_boto3_s3
pydantic-settings
werkzeug
extras= extras=
yaml yaml
pydantic
flask
aiohttp
commands = pytest -c tests/.configs/pytest.ini commands = pytest -c tests/.configs/pytest.ini
python_files = test_*_py3*.py python_files = test_*_py3*.py
setenv =
COVERAGE_RCFILE = pyproject.toml
[testenv:coveralls] [testenv:.pkg]
passenv = GITHUB_*, COVERALLS_* passenv = DEPENDENCY_INJECTOR_*
basepython=python3.11
usedevelop=True
deps=
{[testenv]deps}
cython
coverage
coveralls
commands=
coverage erase
coverage run --rcfile=./.coveragerc -m pytest -c tests/.configs/pytest.ini
coverage report --rcfile=./.coveragerc
coveralls
[testenv:2.7] [testenv:pydantic-{v1,v2}]
deps= description = run tests with different pydantic versions
pytest base_python = python3.12
extras= deps =
yaml v1: pydantic<2
flask v2: pydantic-settings
commands = pytest -c tests/.configs/pytest-py27.ini
[testenv:3.5]
deps=
pytest pytest
pytest-asyncio pytest-asyncio
contextvars typing_extensions
extras= httpx
yaml fastapi
flask flask
commands = pytest -c tests/.configs/pytest-py35.ini aiohttp
numpy
scipy
boto3
mypy_boto3_s3
werkzeug
commands = pytest -c tests/.configs/pytest.ini -m pydantic
[testenv:pypy2.7] [testenv:coveralls]
passenv = GITHUB_*, COVERALLS_*, DEPENDENCY_INJECTOR_*
basepython=python3.12 # TODO: Upgrade to version 3.13 is blocked by coveralls 4.0.1 not supporting Python 3.13
deps= deps=
pytest {[testenv]deps}
extras= cython>=3,<4
yaml coverage>=7
flask coveralls>=4
commands = pytest -c tests/.configs/pytest-py27.ini commands=
coverage erase
coverage run -m pytest -c tests/.configs/pytest.ini
coverage report
coveralls
[testenv:pypy3.9] [testenv:pypy3.9]
deps= deps=
pytest pytest
pytest-asyncio pytest-asyncio
httpx httpx
flask
pydantic-settings
werkzeug
fastapi fastapi
boto3 boto3
mypy_boto3_s3 mypy_boto3_s3
extras= extras=
yaml yaml
flask commands = pytest -c tests/.configs/pytest-py35.ini
commands = pytest -c tests/.configs/pytest-py27.ini
[testenv:pylint] [testenv:pylint]
deps= deps=
pylint pylint
flask
werkzeug
commands= commands=
- pylint -f colorized --rcfile=./.pylintrc src/dependency_injector - pylint -f colorized src/dependency_injector
[testenv:flake8] [testenv:flake8]
deps= deps=
@ -99,6 +101,8 @@ commands=
[testenv:mypy] [testenv:mypy]
deps= deps=
typing_extensions
pydantic-settings
mypy mypy
commands= commands=
mypy tests/typing mypy tests/typing