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

View File

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

14
.gitignore vendored
View File

@ -63,13 +63,13 @@ venv*/
# Vim Rope
.ropeproject/
# C extensions
src/dependency_injector/*.h
src/dependency_injector/*.so
src/dependency_injector/containers/*.h
src/dependency_injector/containers/*.so
src/dependency_injector/providers/*.h
src/dependency_injector/providers/*.so
# Cython artifacts
src/**/*.c
src/**/*.h
src/**/*.so
src/**/*.html
# Workspace for samples
.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)
+ Thiago Hiromi (thiromi)
+ 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.
Redistribution and use in source and binary forms, with or without

View File

@ -1,14 +1,6 @@
VERSION := $(shell python setup.py --version)
CYTHON_SRC := $(shell find src/dependency_injector -name '*.pyx')
CYTHON_DIRECTIVES = -Xlanguage_level=2
ifdef DEPENDENCY_INJECTOR_DEBUG_MODE
CYTHON_DIRECTIVES += -Xprofile=True
CYTHON_DIRECTIVES += -Xlinetrace=True
endif
export COVERAGE_RCFILE := pyproject.toml
clean:
# Clean sources
@ -25,21 +17,17 @@ clean:
find examples -name '*.py[co]' -delete
find examples -name '__pycache__' -delete
cythonize:
# Compile Cython to C
cython -a $(CYTHON_DIRECTIVES) $(CYTHON_SRC)
build: clean
# Compile C extensions
python setup.py build_ext --inplace
# Move all Cython html reports
mkdir -p reports/cython/
find src -name '*.html' -exec mv {} reports/cython/ \;
build: clean cythonize
# Compile C extensions
python setup.py build_ext --inplace
docs-live:
sphinx-autobuild docs docs/_build/html
install: uninstall clean cythonize
install: uninstall clean build
pip install -ve .
uninstall:
@ -48,9 +36,9 @@ uninstall:
test:
# Unit tests with coverage report
coverage erase
coverage run --rcfile=./.coveragerc -m pytest -c tests/.configs/pytest.ini
coverage report --rcfile=./.coveragerc
coverage html --rcfile=./.coveragerc
coverage run -m pytest -c tests/.configs/pytest.ini
coverage report
coverage html
check:
flake8 src/dependency_injector/
@ -61,9 +49,9 @@ check:
mypy tests/typing
test-publish: cythonize
test-publish: build
# Create distributions
python setup.py sdist
python -m build --sdist
# Upload distributions to PyPI
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 = [
"alabaster",
"sphinx.ext.autodoc",
"sphinxcontrib.disqus",
"sphinx_disqus.disqus",
]
# Add any paths that contain templates here, relative to this directory.
@ -52,7 +52,7 @@ master_doc = "index"
# General information about the project.
project = "Dependency Injector"
copyright = "2022, Roman Mogylatov"
copyright = "2024, Roman Mogylatov"
author = "Roman Mogylatov"
# 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,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_css_files = [
"custom.css",
]
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
@ -306,4 +309,5 @@ html_theme_options = {
"description": "Dependency injection framework for Python by Roman Mogylatov",
"code_font_size": "10pt",
"analytics_id": "UA-67012059-1",
"donate_url": "https://github.com/sponsors/rmk135",
}

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>`_.
.. include:: ../sponsor.rst
.. 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>`_.
.. include:: ../sponsor.rst
.. 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>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -17,4 +17,6 @@ Listing of ``boto3_session_example.py``:
.. literalinclude:: ../../examples/miniapps/boto3-session/boto3_session_example.py
:language: python
.. include:: ../sponsor.rst
.. 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>`_.
.. include:: ../sponsor.rst
.. 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>`_.
.. include:: ../sponsor.rst
.. disqus::

View File

@ -95,4 +95,6 @@ See also:
- Resource provider :ref:`resource-async-initializers`
- Wiring :ref:`async-injections-wiring`
.. include:: ../sponsor.rst
.. 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>`_.
.. include:: ../sponsor.rst
.. 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>`_.
.. include:: ../sponsor.rst
.. 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>`_.
.. include:: ../sponsor.rst
.. 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>`_.
.. include:: ../sponsor.rst
.. 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>`_.
.. include:: ../sponsor.rst
.. 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://pypi.org/project/dependency-injector/
.. include:: ../sponsor.rst
.. disqus::

View File

@ -7,6 +7,48 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
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
------
- Add support of Python 3.11.

View File

@ -183,22 +183,22 @@ See also: :ref:`configuration-envs-interpolation`.
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:
.. literalinclude:: ../../examples/providers/configuration/configuration_pydantic.py
:language: python
: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.
.. code-block:: python
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:
.. code-block:: python
@ -215,18 +215,23 @@ the container will call ``config.from_pydantic()`` automatically:
.. 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::
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.*
.. 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
-------------------------

View File

@ -24,7 +24,7 @@ returns it on the rest of the calls.
.. 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.
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`
- Go to the :ref:`contents`
.. include:: ../sponsor.rst
.. disqus::

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,13 +13,13 @@ Build the Docker image:
.. code-block:: bash
docker-compose build
docker compose build
Run the docker-compose environment:
.. code-block:: bash
docker-compose up
docker compose up
The output should be something like:
@ -59,15 +59,16 @@ To run the tests do:
.. code-block:: bash
docker-compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
docker compose run --rm monitor py.test monitoringdaemon/tests.py --cov=monitoringdaemon
The output should be something like:
.. code-block::
platform linux -- Python 3.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
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
monitoringdaemon/tests.py .. [100%]

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
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]:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,15 +3,19 @@
from unittest import mock
import pytest
from httpx import AsyncClient
import pytest_asyncio
from httpx import ASGITransport, AsyncClient
from giphynavigator.application import app
from giphynavigator.giphy import GiphyClient
@pytest.fixture
@pytest_asyncio.fixture
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
import os
from dependency_injector import containers, providers
from pydantic import BaseSettings, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
# Emulate environment variables
os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
@ -11,15 +11,16 @@ os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
class AwsSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="aws_")
access_key_id: str = Field(env="aws_access_key_id")
secret_access_key: str = Field(env="aws_secret_access_key")
access_key_id: str
secret_access_key: str
class Settings(BaseSettings):
aws: AwsSettings = AwsSettings()
optional: str = Field(default="default_value")
optional: str = "default_value"
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-asyncio
tox
coverage
flake8
flake8-pyproject
pydocstyle
sphinx_autobuild
pip
@ -11,7 +13,7 @@ mypy
pyyaml
httpx
fastapi
pydantic
pydantic==1.10.17
numpy
scipy
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
sphinx<3.5.0
sphinx
# 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

View File

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

View File

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

152
setup.py
View File

@ -1,130 +1,42 @@
"""`Dependency injector` setup script."""
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
def _open(filename):
if sys.version_info[0] == 2:
return open(filename)
return open(filename, encoding="utf-8")
# Defining setup variables:
defined_macros = dict()
defined_macros["CYTHON_CLINE_IN_TRACEBACK"] = 0
# Getting description:
with _open("README.rst") as readme_file:
description = readme_file.read()
# Getting requirements:
with _open("requirements.txt") as requirements_file:
requirements = requirements_file.readlines()
# Getting version:
with _open("src/dependency_injector/__init__.py") as init_file:
version = re.search("__version__ = \"(.*?)\"", init_file.read()).group(1)
debug = os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1"
defined_macros = []
compiler_directives = {
"language_level": 3,
"profile": debug,
"linetrace": debug,
}
Options.annotate = debug
# Adding debug options:
if os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1":
defined_macros["CYTHON_TRACE"] = 1
defined_macros["CYTHON_TRACE_NOGIL"] = 1
defined_macros["CYTHON_CLINE_IN_TRACEBACK"] = 1
if debug:
defined_macros.extend(
[
("CYTHON_TRACE", "1"),
("CYTHON_TRACE_NOGIL", "1"),
("CYTHON_CLINE_IN_TRACEBACK", "1"),
]
)
setup(name="dependency-injector",
version=version,
description="Dependency injection framework for Python",
long_description=description,
author="Roman Mogylatov",
author_email="rmogilatov@gmail.com",
maintainer="Roman Mogylatov",
maintainer_email="rmogilatov@gmail.com",
url="https://github.com/ets-labs/python-dependency-injector",
download_url="https://pypi.python.org/pypi/dependency_injector",
packages=[
"dependency_injector",
"dependency_injector.ext",
setup(
ext_modules=cythonize(
[
Extension(
"*",
["src/**/*.pyx"],
define_macros=defined_macros,
),
],
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",
])
annotate=debug,
show_all_warnings=True,
compiler_directives=compiler_directives,
),
)

View File

@ -1,6 +1,6 @@
"""Top-level package."""
__version__ = "4.41.0"
__version__ = "4.45.0"
"""Version number.
: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:
yaml = None
import six
from . import providers, errors
from .providers cimport __is_future_or_coroutine
@ -201,7 +199,7 @@ class DynamicContainer(Container):
:rtype: None
"""
for name, provider in six.iteritems(providers):
for name, provider in providers.items():
setattr(self, name, provider)
def set_provider(self, name, provider):
@ -234,7 +232,7 @@ class DynamicContainer(Container):
self.overridden += (overriding,)
for name, provider in six.iteritems(overriding.providers):
for name, provider in overriding.providers.items():
try:
getattr(self, name).override(provider)
except AttributeError:
@ -250,7 +248,7 @@ class DynamicContainer(Container):
:rtype: None
"""
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.override(overriding_provider)
overridden_providers.append(container_provider)
@ -266,7 +264,7 @@ class DynamicContainer(Container):
self.overridden = self.overridden[:-1]
for provider in six.itervalues(self.providers):
for provider in self.providers.values():
provider.reset_last_overriding()
def reset_override(self):
@ -276,7 +274,7 @@ class DynamicContainer(Container):
"""
self.overridden = tuple()
for provider in six.itervalues(self.providers):
for provider in self.providers.values():
provider.reset_override()
def is_auto_wiring_enabled(self):
@ -495,13 +493,13 @@ class DeclarativeContainerMetaClass(type):
containers = {
name: container
for name, container in six.iteritems(attributes)
for name, container in attributes.items()
if is_container(container)
}
cls_providers = {
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)
}
@ -509,7 +507,7 @@ class DeclarativeContainerMetaClass(type):
name: provider
for base in bases
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 = {}
@ -536,10 +534,10 @@ class DeclarativeContainerMetaClass(type):
self.set_container(cls)
cls.__self__ = self
for provider in six.itervalues(cls.providers):
for provider in cls.providers.values():
_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):
provider.assign_parent(cls)
@ -641,8 +639,7 @@ class DeclarativeContainerMetaClass(type):
return self
@six.add_metaclass(DeclarativeContainerMetaClass)
class DeclarativeContainer(Container):
class DeclarativeContainer(Container, metaclass=DeclarativeContainerMetaClass):
"""Declarative inversion of control container.
.. code-block:: python
@ -767,7 +764,7 @@ class DeclarativeContainer(Container):
cls.overridden += (overriding,)
for name, provider in six.iteritems(overriding.cls_providers):
for name, provider in overriding.cls_providers.items():
try:
getattr(cls, name).override(provider)
except AttributeError:
@ -784,7 +781,7 @@ class DeclarativeContainer(Container):
cls.overridden = cls.overridden[:-1]
for provider in six.itervalues(cls.providers):
for provider in cls.providers.values():
provider.reset_last_overriding()
@classmethod
@ -795,7 +792,7 @@ class DeclarativeContainer(Container):
"""
cls.overridden = tuple()
for provider in six.itervalues(cls.providers):
for provider in cls.providers.values():
provider.reset_override()
@ -858,7 +855,7 @@ def copy(object base_container):
"""
def _get_memo_for_matching_names(new_providers, base_providers):
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:
continue
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(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)
return new_container

View File

@ -10,3 +10,24 @@ class Error(Exception):
class NoSuchProviderError(Error, AttributeError):
"""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
cdef class Provider(object):
cdef tuple __overridden
cdef Provider __last_overriding
cdef tuple __overrides
cdef int __async_mode
cdef tuple _overridden
cdef Provider _last_overriding
cdef tuple _overrides
cdef int _async_mode
cpdef bint is_async_mode_enabled(self)
cpdef bint is_async_mode_disabled(self)
@ -34,32 +34,32 @@ cdef class Provider(object):
cdef class Object(Provider):
cdef object __provides
cdef object _provides
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Self(Provider):
cdef object __container
cdef tuple __alt_names
cdef object _container
cdef tuple _alt_names
cdef class Delegate(Provider):
cdef object __provides
cdef object _provides
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Aggregate(Provider):
cdef dict __providers
cdef dict _providers
cdef Provider __get_provider(self, object provider_name)
cdef class Dependency(Provider):
cdef object __instance_of
cdef object __default
cdef object __parent
cdef object _instance_of
cdef object _default
cdef object _parent
cdef class ExternalDependency(Dependency):
@ -67,21 +67,21 @@ cdef class ExternalDependency(Dependency):
cdef class DependenciesContainer(Object):
cdef dict __providers
cdef object __parent
cdef dict _providers
cdef object _parent
cpdef object _override_providers(self, object container)
# Callable providers
cdef class Callable(Provider):
cdef object __provides
cdef object _provides
cdef tuple __args
cdef int __args_len
cdef tuple _args
cdef int _args_len
cdef tuple __kwargs
cdef int __kwargs_len
cdef tuple _kwargs
cdef int _kwargs_len
cpdef object _provide(self, tuple args, dict kwargs)
@ -117,11 +117,11 @@ cdef class CoroutineDelegate(Delegate):
# Configuration providers
cdef class ConfigurationOption(Provider):
cdef tuple __name
cdef Configuration __root
cdef dict __children
cdef bint __required
cdef object __cache
cdef tuple _name
cdef Configuration _root
cdef dict _children
cdef bint _required
cdef object _cache
cdef class TypedConfigurationOption(Callable):
@ -129,22 +129,22 @@ cdef class TypedConfigurationOption(Callable):
cdef class Configuration(Object):
cdef str __name
cdef str _name
cdef bint __strict
cdef dict __children
cdef list __ini_files
cdef list __yaml_files
cdef list __json_files
cdef list __pydantic_settings
cdef dict _children
cdef list _ini_files
cdef list _yaml_files
cdef list _json_files
cdef list _pydantic_settings
cdef object __weakref__
# Factory providers
cdef class Factory(Provider):
cdef Callable __instantiator
cdef Callable _instantiator
cdef tuple __attributes
cdef int __attributes_len
cdef tuple _attributes
cdef int _attributes_len
cpdef object _provide(self, tuple args, dict kwargs)
@ -167,8 +167,8 @@ cdef class FactoryAggregate(Aggregate):
# Singleton providers
cdef class BaseSingleton(Provider):
cdef Factory __instantiator
cdef object __storage
cdef Factory _instantiator
cdef object _storage
cdef class Singleton(BaseSingleton):
@ -181,7 +181,7 @@ cdef class DelegatedSingleton(Singleton):
cdef class ThreadSafeSingleton(BaseSingleton):
cdef object __storage_lock
cdef object _storage_lock
cpdef object _provide(self, tuple args, dict kwargs)
@ -215,87 +215,87 @@ cdef class SingletonDelegate(Delegate):
# Miscellaneous providers
cdef class List(Provider):
cdef tuple __args
cdef int __args_len
cdef tuple _args
cdef int _args_len
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Dict(Provider):
cdef tuple __kwargs
cdef int __kwargs_len
cdef tuple _kwargs
cdef int _kwargs_len
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Resource(Provider):
cdef object __provides
cdef bint __initialized
cdef object __shutdowner
cdef object __resource
cdef object _provides
cdef bint _initialized
cdef object _shutdowner
cdef object _resource
cdef tuple __args
cdef int __args_len
cdef tuple _args
cdef int _args_len
cdef tuple __kwargs
cdef int __kwargs_len
cdef tuple _kwargs
cdef int _kwargs_len
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Container(Provider):
cdef object __container_cls
cdef dict __overriding_providers
cdef object __container
cdef object __parent
cdef object _container_cls
cdef dict _overriding_providers
cdef object _container
cdef object _parent
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Selector(Provider):
cdef object __selector
cdef dict __providers
cdef object _selector
cdef dict _providers
cpdef object _provide(self, tuple args, dict kwargs)
# Provided instance
cdef class ProvidedInstance(Provider):
cdef object __provides
cdef object _provides
cpdef object _provide(self, tuple args, dict kwargs)
cdef class AttributeGetter(Provider):
cdef object __provides
cdef object __name
cdef object _provides
cdef object _name
cpdef object _provide(self, tuple args, dict kwargs)
cdef class ItemGetter(Provider):
cdef object __provides
cdef object __name
cdef object _provides
cdef object _name
cpdef object _provide(self, tuple args, dict kwargs)
cdef class MethodCaller(Provider):
cdef object __provides
cdef tuple __args
cdef int __args_len
cdef tuple __kwargs
cdef int __kwargs_len
cdef object _provides
cdef tuple _args
cdef int _args_len
cdef tuple _kwargs
cdef int _kwargs_len
cpdef object _provide(self, tuple args, dict kwargs)
# Injections
cdef class Injection(object):
cdef object __value
cdef int __is_provider
cdef int __is_delegated
cdef int __call
cdef object _value
cdef int _is_provider
cdef int _is_delegated
cdef int _call
cdef class PositionalInjection(Injection):
@ -303,7 +303,7 @@ cdef class PositionalInjection(Injection):
cdef class NamedInjection(Injection):
cdef object __name
cdef object _name
cpdef tuple parse_positional_injections(tuple args)
@ -314,12 +314,12 @@ cpdef tuple parse_named_injections(dict kwargs)
# Utils
cdef class OverridingContext(object):
cdef Provider __overridden
cdef Provider __overriding
cdef Provider _overridden
cdef Provider _overriding
cdef class BaseSingletonResetContext(object):
cdef object __singleton
cdef object _singleton
cdef class SingletonResetContext(BaseSingletonResetContext):
@ -356,19 +356,19 @@ cpdef object deepcopy(object instance, dict memo=*)
# Inline helper functions
cdef inline object __get_name(NamedInjection self):
return self.__name
return self._name
cdef inline object __get_value(Injection self):
if self.__call == 0:
return self.__value
return self.__value()
if self._call == 0:
return self._value
return self._value()
cdef inline object __get_value_kwargs(Injection self, dict kwargs):
if self.__call == 0:
return self.__value
return self.__value(**kwargs)
if self._call == 0:
return self._value
return self._value(**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, ):
return __call(
self.__provides,
self._provides,
args,
self.__args,
self.__args_len,
self._args,
self._args_len,
kwargs,
self.__kwargs,
self.__kwargs_len,
self.__async_mode,
self._kwargs,
self._kwargs_len,
self._async_mode,
)
@ -648,18 +648,18 @@ cdef inline object __factory_call(Factory self, tuple args, dict kwargs):
cdef object instance
instance = __call(
self.__instantiator.__provides,
self._instantiator._provides,
args,
self.__instantiator.__args,
self.__instantiator.__args_len,
self._instantiator._args,
self._instantiator._args_len,
kwargs,
self.__instantiator.__kwargs,
self.__instantiator.__kwargs_len,
self.__async_mode,
self._instantiator._kwargs,
self._instantiator._kwargs_len,
self._async_mode,
)
if self.__attributes_len > 0:
attributes = __provide_attributes(self.__attributes, self.__attributes_len)
if self._attributes_len > 0:
attributes = __provide_attributes(self._attributes, self._attributes_len)
is_future_instance = __is_future_or_coroutine(instance)
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 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]: ...

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -4,6 +4,7 @@ from aiohttp import web, test_utils
from dependency_injector import containers, providers
from dependency_injector.ext import aiohttp
from pytest import fixture, mark
from pytest_asyncio import fixture as aio_fixture
async def index_view(_):
@ -63,7 +64,7 @@ def app():
return app
@fixture
@aio_fixture
async def client(app):
async with test_utils.TestClient(test_utils.TestServer(app)) as 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."""
import pydantic
from dependency_injector import providers, errors
from pydantic import BaseModel
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 dependency_injector import errors, providers
class Section11(pydantic.BaseModel):
value1 = 1
pytestmark = mark.pydantic
class Section12(pydantic.BaseModel):
value2 = 2
class Section11(BaseModel):
value1: int = 1
class Settings1(pydantic.BaseSettings):
section1 = Section11()
section2 = Section12()
class Section12(BaseModel):
value2: int = 2
class Section21(pydantic.BaseModel):
value1 = 11
value11 = 11
class Settings1(BaseSettings):
section1: Section11 = Section11()
section2: Section12 = Section12()
class Section3(pydantic.BaseModel):
value3 = 3
class Section21(BaseModel):
value1: int = 11
value11: int = 11
class Settings2(pydantic.BaseSettings):
section1 = Section21()
section3 = Section3()
class Section3(BaseModel):
value3: int = 3
class Settings2(BaseSettings):
section1: Section21 = Section21()
section3: Section3 = Section3()
@fixture
def no_pydantic_module_installed():
providers.pydantic = None
has_pydantic_settings = providers.has_pydantic_settings
providers.has_pydantic_settings = False
yield
providers.pydantic = pydantic
providers.has_pydantic_settings = has_pydantic_settings
def test(config):
@ -82,66 +101,70 @@ def test_merge(config):
def test_empty_settings(config):
config.from_pydantic(pydantic.BaseSettings())
config.from_pydantic(BaseSettings())
assert config() == {}
@mark.parametrize("config_type", ["strict"])
def test_empty_settings_strict_mode(config):
with raises(ValueError):
config.from_pydantic(pydantic.BaseSettings())
config.from_pydantic(BaseSettings())
def test_option_empty_settings(config):
config.option.from_pydantic(pydantic.BaseSettings())
config.option.from_pydantic(BaseSettings())
assert config.option() == {}
@mark.parametrize("config_type", ["strict"])
def test_option_empty_settings_strict_mode(config):
with raises(ValueError):
config.option.from_pydantic(pydantic.BaseSettings())
config.option.from_pydantic(BaseSettings())
def test_required_empty_settings(config):
with raises(ValueError):
config.from_pydantic(pydantic.BaseSettings(), required=True)
config.from_pydantic(BaseSettings(), required=True)
def test_required_option_empty_settings(config):
with raises(ValueError):
config.option.from_pydantic(pydantic.BaseSettings(), required=True)
config.option.from_pydantic(BaseSettings(), required=True)
@mark.parametrize("config_type", ["strict"])
def test_not_required_empty_settings_strict_mode(config):
config.from_pydantic(pydantic.BaseSettings(), required=False)
config.from_pydantic(BaseSettings(), required=False)
assert config() == {}
@mark.parametrize("config_type", ["strict"])
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": {}}
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({})
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):
with raises(errors.Error) as error:
config.option.from_pydantic({})
assert error.value.args[0] == (
"Unable to recognize settings instance, expect \"pydantic.BaseSettings\", "
with raises(
errors.Error,
match=(
r"Unable to recognize settings instance, expect \"pydantic(?:_settings)?\.BaseSettings\", "
"got {0} instead".format({})
)
),
):
config.option.from_pydantic({})
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")
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())
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")
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())
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."""
import pydantic
from dependency_injector import providers
from pydantic import BaseModel
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 dependency_injector import providers
class Section11(pydantic.BaseModel):
value1 = 1
pytestmark = mark.pydantic
class Section12(pydantic.BaseModel):
value2 = 2
class Section11(BaseModel):
value1: int = 1
class Settings1(pydantic.BaseSettings):
section1 = Section11()
section2 = Section12()
class Section12(BaseModel):
value2: int = 2
class Section21(pydantic.BaseModel):
value1 = 11
value11 = 11
class Settings1(BaseSettings):
section1: Section11 = Section11()
section2: Section12 = Section12()
class Section3(pydantic.BaseModel):
value3 = 3
class Section21(BaseModel):
value1: int = 11
value11: int = 11
class Settings2(pydantic.BaseSettings):
section1 = Section21()
section3 = Section3()
class Section3(BaseModel):
value3: int = 3
class Settings2(BaseSettings):
section1: Section21 = Section21()
section3: Section3 = Section3()
@fixture
@ -86,10 +103,10 @@ def test_copy(config, pydantic_settings_1, pydantic_settings_2):
def test_set_pydantic_settings(config):
class Settings3(pydantic.BaseSettings):
class Settings3(BaseSettings):
...
class Settings4(pydantic.BaseSettings):
class Settings4(BaseSettings):
...
settings_3 = Settings3()
@ -100,27 +117,27 @@ def test_set_pydantic_settings(config):
def test_file_does_not_exist(config):
config.set_pydantic_settings([pydantic.BaseSettings()])
config.set_pydantic_settings([BaseSettings()])
config.load()
assert config() == {}
@mark.parametrize("config_type", ["strict"])
def test_file_does_not_exist_strict_mode(config):
config.set_pydantic_settings([pydantic.BaseSettings()])
config.set_pydantic_settings([BaseSettings()])
with raises(ValueError):
config.load()
assert config() == {}
def test_required_file_does_not_exist(config):
config.set_pydantic_settings([pydantic.BaseSettings()])
config.set_pydantic_settings([BaseSettings()])
with raises(ValueError):
config.load(required=True)
@mark.parametrize("config_type", ["strict"])
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)
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 _request_ctx_stack, _app_ctx_stack
from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
# This is here for testing wiring bypasses these objects without crashing
request, current_app, session, g # noqa
_request_ctx_stack, _app_ctx_stack # noqa
class Service:

View File

@ -1,5 +1,6 @@
from httpx import AsyncClient
from httpx import ASGITransport, AsyncClient
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
import os
@ -16,9 +17,9 @@ sys.path.append(_SAMPLES_DIR)
from wiringfastapi import web
@fixture
@aio_fixture
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
await client.aclose()

88
tox.ini
View File

@ -1,87 +1,89 @@
[tox]
parallel_show_output = true
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]
deps=
pytest
pytest-asyncio
# TODO: Hotfix, remove when fixed https://github.com/aio-libs/aiohttp/issues/5107
typing_extensions
httpx
fastapi
flask
aiohttp
numpy
scipy
boto3
mypy_boto3_s3
pydantic-settings
werkzeug
extras=
yaml
pydantic
flask
aiohttp
commands = pytest -c tests/.configs/pytest.ini
python_files = test_*_py3*.py
setenv =
COVERAGE_RCFILE = pyproject.toml
[testenv:coveralls]
passenv = GITHUB_*, COVERALLS_*
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:.pkg]
passenv = DEPENDENCY_INJECTOR_*
[testenv:2.7]
deps=
pytest
extras=
yaml
flask
commands = pytest -c tests/.configs/pytest-py27.ini
[testenv:3.5]
[testenv:pydantic-{v1,v2}]
description = run tests with different pydantic versions
base_python = python3.12
deps =
v1: pydantic<2
v2: pydantic-settings
pytest
pytest-asyncio
contextvars
extras=
yaml
typing_extensions
httpx
fastapi
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=
pytest
extras=
yaml
flask
commands = pytest -c tests/.configs/pytest-py27.ini
{[testenv]deps}
cython>=3,<4
coverage>=7
coveralls>=4
commands=
coverage erase
coverage run -m pytest -c tests/.configs/pytest.ini
coverage report
coveralls
[testenv:pypy3.9]
deps=
pytest
pytest-asyncio
httpx
flask
pydantic-settings
werkzeug
fastapi
boto3
mypy_boto3_s3
extras=
yaml
flask
commands = pytest -c tests/.configs/pytest-py27.ini
commands = pytest -c tests/.configs/pytest-py35.ini
[testenv:pylint]
deps=
pylint
flask
werkzeug
commands=
- pylint -f colorized --rcfile=./.pylintrc src/dependency_injector
- pylint -f colorized src/dependency_injector
[testenv:flake8]
deps=
@ -99,6 +101,8 @@ commands=
[testenv:mypy]
deps=
typing_extensions
pydantic-settings
mypy
commands=
mypy tests/typing