mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-06-13 01:53:15 +03:00
Merge branch 'release/4.47.0'
This commit is contained in:
commit
193249f7ec
29
.cursor/rules/coding-guide.mdc
Normal file
29
.cursor/rules/coding-guide.mdc
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
description: Code in Python and Cython
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
- Follow PEP 8 rules
|
||||||
|
- When you write imports, split system, 3rd-party, and local imports with a new line
|
||||||
|
- Have two empty lines between the import block and the rest of the code
|
||||||
|
- Have an empty line (\n) at the end of every file
|
||||||
|
- If a file is supposed to be run, always add ``if __name__ == 'main'``
|
||||||
|
- Always follow a consistent pattern of using double or single quotes
|
||||||
|
- When there is a class without a docblock, leave one blank line before its members, e.g.:
|
||||||
|
```python
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
service = providers.Factory(Service)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Avoid shortcuts in names unless absolutely necessary, exceptions:
|
||||||
|
```
|
||||||
|
arg
|
||||||
|
args
|
||||||
|
kwarg
|
||||||
|
kwargs
|
||||||
|
obj
|
||||||
|
cls
|
||||||
|
```
|
||||||
|
|
||||||
|
- Avoid inline comments unless absolutely necessary
|
7
.cursor/rules/makefile-commands.mdc
Normal file
7
.cursor/rules/makefile-commands.mdc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
description: Build and run tests
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
- Use Makefile commands to build, test, lint and other similar operations when they are available.
|
||||||
|
- Activate virtualenv before running any commands by ``. venv/bin/actvate``
|
8
.cursor/rules/run-examples.mdc
Normal file
8
.cursor/rules/run-examples.mdc
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
description: Run examples
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
- When you run an example from the ``examples/`` folder, switch to the example folder and run it from there.
|
||||||
|
- If there are instructions on running the examples or its tests in readme, follow them
|
||||||
|
- Activate virtualenv before running any commands by ``. venv/bin/actvate``
|
37
.github/workflows/publishing.yml
vendored
37
.github/workflows/publishing.yml
vendored
|
@ -62,20 +62,26 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2019, macos-14]
|
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2019, macos-14]
|
||||||
env:
|
env:
|
||||||
CIBW_SKIP: cp27-*
|
CIBW_ENABLE: pypy
|
||||||
|
CIBW_ENVIRONMENT: >-
|
||||||
|
PIP_CONFIG_SETTINGS="build_ext=-j4"
|
||||||
|
DEPENDENCY_INJECTOR_LIMITED_API="1"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: pypa/cibuildwheel@v2.20.0
|
uses: pypa/cibuildwheel@v2.23.3
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: cibw-wheels-x86-${{ matrix.os }}-${{ strategy.job-index }}
|
name: cibw-wheels-x86-${{ matrix.os }}-${{ strategy.job-index }}
|
||||||
path: ./wheelhouse/*.whl
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
publish:
|
test-publish:
|
||||||
name: Publish on PyPI
|
name: Upload release to TestPyPI
|
||||||
needs: [build-sdist, build-wheels]
|
needs: [build-sdist, build-wheels]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-latest
|
||||||
|
environment: test-pypi
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
@ -84,11 +90,22 @@ jobs:
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
with:
|
with:
|
||||||
user: __token__
|
repository-url: https://test.pypi.org/legacy/
|
||||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
||||||
# For publishing to Test PyPI, uncomment next two lines:
|
publish:
|
||||||
# password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
name: Upload release to PyPI
|
||||||
# repository_url: https://test.pypi.org/legacy/
|
needs: [build-sdist, build-wheels, test-publish]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: pypi
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: cibw-*
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
|
||||||
publish-docs:
|
publish-docs:
|
||||||
name: Publish docs
|
name: Publish docs
|
||||||
|
|
21
.github/workflows/tests-and-linters.yml
vendored
21
.github/workflows/tests-and-linters.yml
vendored
|
@ -4,28 +4,12 @@ on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
tests-on-legacy-versions:
|
|
||||||
name: Run tests on legacy versions
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: [3.7]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
- run: pip install tox
|
|
||||||
- run: tox
|
|
||||||
env:
|
|
||||||
TOXENV: ${{ matrix.python-version }}
|
|
||||||
|
|
||||||
test-on-different-versions:
|
test-on-different-versions:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.8, 3.9, "3.10", 3.11, 3.12, 3.13]
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
|
@ -34,6 +18,7 @@ jobs:
|
||||||
- run: pip install tox
|
- run: pip install tox
|
||||||
- run: tox
|
- run: tox
|
||||||
env:
|
env:
|
||||||
|
DEPENDENCY_INJECTOR_LIMITED_API: 1
|
||||||
TOXENV: ${{ matrix.python-version }}
|
TOXENV: ${{ matrix.python-version }}
|
||||||
|
|
||||||
test-different-pydantic-versions:
|
test-different-pydantic-versions:
|
||||||
|
@ -60,7 +45,7 @@ jobs:
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.12
|
python-version: 3.12
|
||||||
- run: pip install tox 'cython>=3,<4'
|
- run: pip install tox
|
||||||
- run: tox -vv
|
- run: tox -vv
|
||||||
env:
|
env:
|
||||||
TOXENV: coveralls
|
TOXENV: coveralls
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -15,6 +15,7 @@ lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
wheelhouse/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
recursive-include src/dependency_injector *.py* *.c
|
recursive-include src/dependency_injector *.py* *.c py.typed
|
||||||
recursive-include tests *.py
|
recursive-include tests *.py
|
||||||
include README.rst
|
include README.rst
|
||||||
include CONTRIBUTORS.rst
|
include CONTRIBUTORS.rst
|
||||||
include LICENSE.rst
|
include LICENSE.rst
|
||||||
include requirements.txt
|
|
||||||
include setup.py
|
include setup.py
|
||||||
include tox.ini
|
include tox.ini
|
||||||
include py.typed
|
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -36,7 +36,7 @@ uninstall:
|
||||||
test:
|
test:
|
||||||
# Unit tests with coverage report
|
# Unit tests with coverage report
|
||||||
coverage erase
|
coverage erase
|
||||||
coverage run -m pytest -c tests/.configs/pytest.ini
|
coverage run -m pytest
|
||||||
coverage report
|
coverage report
|
||||||
coverage html
|
coverage html
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,18 @@ 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.47.0
|
||||||
|
-------
|
||||||
|
|
||||||
|
- Add support for ``Annotated`` type for module and class attribute injection in wiring,
|
||||||
|
with updated documentation and examples.
|
||||||
|
See discussion:
|
||||||
|
https://github.com/ets-labs/python-dependency-injector/pull/721#issuecomment-2025263718
|
||||||
|
- Fix ``root`` property shadowing in ``ConfigurationOption`` (`#875 https://github.com/ets-labs/python-dependency-injector/pull/875`_)
|
||||||
|
- Fix incorrect monkeypatching during ``wire()`` that could violate MRO in some classes (`#886 https://github.com/ets-labs/python-dependency-injector/pull/886`_)
|
||||||
|
- ABI3 wheels are now published for CPython.
|
||||||
|
- Drop support of Python 3.7.
|
||||||
|
|
||||||
4.46.0
|
4.46.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -254,13 +254,43 @@ To inject a container use special identifier ``<container>``:
|
||||||
Making injections into modules and class attributes
|
Making injections into modules and class attributes
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
You can use wiring to make injections into modules and class attributes.
|
You can use wiring to make injections into modules and class attributes. Both the classic marker
|
||||||
|
syntax and the ``Annotated`` form are supported.
|
||||||
|
|
||||||
|
Classic marker syntax:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
service: Service = Provide[Container.service]
|
||||||
|
|
||||||
|
class Main:
|
||||||
|
service: Service = Provide[Container.service]
|
||||||
|
|
||||||
|
Full example of the classic marker syntax:
|
||||||
|
|
||||||
.. literalinclude:: ../examples/wiring/example_attribute.py
|
.. literalinclude:: ../examples/wiring/example_attribute.py
|
||||||
:language: python
|
:language: python
|
||||||
:lines: 3-
|
:lines: 3-
|
||||||
:emphasize-lines: 14,19
|
:emphasize-lines: 14,19
|
||||||
|
|
||||||
|
Annotated form (Python 3.9+):
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
|
||||||
|
class Main:
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
|
||||||
|
Full example of the annotated form:
|
||||||
|
|
||||||
|
.. literalinclude:: ../examples/wiring/example_attribute_annotated.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 16,21
|
||||||
|
|
||||||
You could also use string identifiers to avoid a dependency on a container:
|
You could also use string identifiers to avoid a dependency on a container:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
31
examples/wiring/example_attribute_annotated.py
Normal file
31
examples/wiring/example_attribute_annotated.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
"""Wiring attribute example with Annotated."""
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from dependency_injector.wiring import Provide
|
||||||
|
|
||||||
|
|
||||||
|
class Service:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
service = providers.Factory(Service)
|
||||||
|
|
||||||
|
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
|
||||||
|
|
||||||
|
class Main:
|
||||||
|
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
container = Container()
|
||||||
|
container.wire(modules=[__name__])
|
||||||
|
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
assert isinstance(Main.service, Service)
|
|
@ -1,5 +1,5 @@
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools", "Cython"]
|
requires = ["setuptools", "Cython>=3.1.1"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
|
@ -13,7 +13,7 @@ maintainers = [
|
||||||
description = "Dependency injection framework for Python"
|
description = "Dependency injection framework for Python"
|
||||||
readme = {file = "README.rst", content-type = "text/x-rst"}
|
readme = {file = "README.rst", content-type = "text/x-rst"}
|
||||||
license = {file = "LICENSE.rst", content-type = "text/x-rst"}
|
license = {file = "LICENSE.rst", content-type = "text/x-rst"}
|
||||||
requires-python = ">=3.7"
|
requires-python = ">=3.8"
|
||||||
keywords = [
|
keywords = [
|
||||||
"Dependency injection",
|
"Dependency injection",
|
||||||
"DI",
|
"DI",
|
||||||
|
@ -31,7 +31,6 @@ classifiers = [
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
|
@ -99,3 +98,18 @@ ignore = ["tests"]
|
||||||
[tool.pylint.design]
|
[tool.pylint.design]
|
||||||
min-public-methods = 0
|
min-public-methods = 0
|
||||||
max-public-methods = 30
|
max-public-methods = 30
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests/unit/"]
|
||||||
|
asyncio_mode = "auto"
|
||||||
|
asyncio_default_fixture_loop_scope = "function"
|
||||||
|
markers = [
|
||||||
|
"pydantic: Tests with Pydantic as a dependency",
|
||||||
|
]
|
||||||
|
filterwarnings = [
|
||||||
|
"ignore:Module \"dependency_injector.ext.aiohttp\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
|
||||||
|
"ignore:Module \"dependency_injector.ext.flask\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
|
||||||
|
"ignore:Please use \\`.*?\\` from the \\`scipy.*?\\`(.*?)namespace is deprecated\\.:DeprecationWarning",
|
||||||
|
"ignore:Please import \\`.*?\\` from the \\`scipy(.*?)\\` namespace(.*):DeprecationWarning",
|
||||||
|
"ignore:\\`scipy(.*?)\\` is deprecated(.*):DeprecationWarning",
|
||||||
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
cython==3.0.11
|
cython==3.1.1
|
||||||
setuptools
|
setuptools
|
||||||
pytest
|
pytest
|
||||||
pytest-asyncio
|
pytest-asyncio
|
||||||
|
@ -13,7 +13,8 @@ mypy
|
||||||
pyyaml
|
pyyaml
|
||||||
httpx
|
httpx
|
||||||
fastapi
|
fastapi
|
||||||
pydantic==1.10.17
|
pydantic
|
||||||
|
pydantic-settings
|
||||||
numpy
|
numpy
|
||||||
scipy
|
scipy
|
||||||
boto3
|
boto3
|
||||||
|
|
|
@ -8,7 +8,7 @@ per-file-ignores =
|
||||||
examples/containers/traverse.py: E501
|
examples/containers/traverse.py: E501
|
||||||
examples/providers/async.py: F841
|
examples/providers/async.py: F841
|
||||||
examples/providers/async_overriding.py: F841
|
examples/providers/async_overriding.py: F841
|
||||||
examples/wiring/*: F841
|
examples/wiring/*: F821,F841
|
||||||
|
|
||||||
[pydocstyle]
|
[pydocstyle]
|
||||||
ignore = D100,D101,D102,D105,D106,D107,D203,D213
|
ignore = D100,D101,D102,D105,D106,D107,D203,D213
|
||||||
|
|
13
setup.py
13
setup.py
|
@ -1,13 +1,19 @@
|
||||||
"""`Dependency injector` setup script."""
|
"""`Dependency injector` setup script."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from Cython.Build import cythonize
|
from Cython.Build import cythonize
|
||||||
from Cython.Compiler import Options
|
from Cython.Compiler import Options
|
||||||
from setuptools import Extension, setup
|
from setuptools import Extension, setup
|
||||||
|
|
||||||
debug = os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1"
|
debug = os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1"
|
||||||
|
limited_api = (
|
||||||
|
os.environ.get("DEPENDENCY_INJECTOR_LIMITED_API") == "1"
|
||||||
|
and sys.implementation.name == "cpython"
|
||||||
|
)
|
||||||
defined_macros = []
|
defined_macros = []
|
||||||
|
options = {}
|
||||||
compiler_directives = {
|
compiler_directives = {
|
||||||
"language_level": 3,
|
"language_level": 3,
|
||||||
"profile": debug,
|
"profile": debug,
|
||||||
|
@ -17,6 +23,7 @@ Options.annotate = debug
|
||||||
|
|
||||||
# Adding debug options:
|
# Adding debug options:
|
||||||
if debug:
|
if debug:
|
||||||
|
limited_api = False # line tracing is not part of the Limited API
|
||||||
defined_macros.extend(
|
defined_macros.extend(
|
||||||
[
|
[
|
||||||
("CYTHON_TRACE", "1"),
|
("CYTHON_TRACE", "1"),
|
||||||
|
@ -25,14 +32,20 @@ if debug:
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if limited_api:
|
||||||
|
options.setdefault("bdist_wheel", {})
|
||||||
|
options["bdist_wheel"]["py_limited_api"] = "cp38"
|
||||||
|
defined_macros.append(("Py_LIMITED_API", "0x03080000"))
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
options=options,
|
||||||
ext_modules=cythonize(
|
ext_modules=cythonize(
|
||||||
[
|
[
|
||||||
Extension(
|
Extension(
|
||||||
"*",
|
"*",
|
||||||
["src/**/*.pyx"],
|
["src/**/*.pyx"],
|
||||||
define_macros=defined_macros,
|
define_macros=defined_macros,
|
||||||
|
py_limited_api=limited_api,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
annotate=debug,
|
annotate=debug,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Top-level package."""
|
"""Top-level package."""
|
||||||
|
|
||||||
__version__ = "4.46.0"
|
__version__ = "4.47.0"
|
||||||
"""Version number.
|
"""Version number.
|
||||||
|
|
||||||
:type: str
|
:type: str
|
||||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import absolute_import
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import builtins
|
import builtins
|
||||||
import contextvars
|
|
||||||
import copy
|
import copy
|
||||||
import errno
|
import errno
|
||||||
import functools
|
import functools
|
||||||
|
@ -17,6 +16,7 @@ import sys
|
||||||
import threading
|
import threading
|
||||||
import warnings
|
import warnings
|
||||||
from configparser import ConfigParser as IniConfigParser
|
from configparser import ConfigParser as IniConfigParser
|
||||||
|
from contextvars import ContextVar
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from inspect import _is_coroutine_mark as _is_coroutine_marker
|
from inspect import _is_coroutine_mark as _is_coroutine_marker
|
||||||
|
@ -1592,8 +1592,7 @@ cdef class ConfigurationOption(Provider):
|
||||||
segment() if is_provider(segment) else segment for segment in self._name
|
segment() if is_provider(segment) else segment for segment in self._name
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
def _get_root(self):
|
||||||
def root(self):
|
|
||||||
return self._root
|
return self._root
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
|
@ -3224,15 +3223,10 @@ cdef class ContextLocalSingleton(BaseSingleton):
|
||||||
:param provides: Provided type.
|
:param provides: Provided type.
|
||||||
:type provides: type
|
:type provides: type
|
||||||
"""
|
"""
|
||||||
if not contextvars:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Contextvars library not found. This provider "
|
|
||||||
"requires Python 3.7 or a backport of contextvars. "
|
|
||||||
"To install a backport run \"pip install contextvars\"."
|
|
||||||
)
|
|
||||||
|
|
||||||
super(ContextLocalSingleton, self).__init__(provides, *args, **kwargs)
|
super(ContextLocalSingleton, self).__init__(provides, *args, **kwargs)
|
||||||
self._storage = contextvars.ContextVar("_storage", default=self._none)
|
self._storage = ContextVar("_storage", default=self._none)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Reset cached instance, if any.
|
"""Reset cached instance, if any.
|
||||||
|
|
|
@ -314,7 +314,7 @@ class ProvidersMap:
|
||||||
original: providers.ConfigurationOption,
|
original: providers.ConfigurationOption,
|
||||||
as_: Any = None,
|
as_: Any = None,
|
||||||
) -> Optional[providers.Provider]:
|
) -> Optional[providers.Provider]:
|
||||||
original_root = original.root
|
original_root = original._get_root()
|
||||||
new = self._resolve_provider(original_root)
|
new = self._resolve_provider(original_root)
|
||||||
if new is None:
|
if new is None:
|
||||||
return None
|
return None
|
||||||
|
@ -415,7 +415,7 @@ def wire( # noqa: C901
|
||||||
providers_map = ProvidersMap(container)
|
providers_map = ProvidersMap(container)
|
||||||
|
|
||||||
for module in modules:
|
for module in modules:
|
||||||
for member_name, member in inspect.getmembers(module):
|
for member_name, member in _get_members_and_annotated(module):
|
||||||
if _inspect_filter.is_excluded(member):
|
if _inspect_filter.is_excluded(member):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -426,7 +426,7 @@ def wire( # noqa: C901
|
||||||
elif inspect.isclass(member):
|
elif inspect.isclass(member):
|
||||||
cls = member
|
cls = member
|
||||||
try:
|
try:
|
||||||
cls_members = inspect.getmembers(cls)
|
cls_members = _get_members_and_annotated(cls)
|
||||||
except Exception: # noqa
|
except Exception: # noqa
|
||||||
# Hotfix, see: https://github.com/ets-labs/python-dependency-injector/issues/441
|
# Hotfix, see: https://github.com/ets-labs/python-dependency-injector/issues/441
|
||||||
continue
|
continue
|
||||||
|
@ -523,6 +523,10 @@ def _patch_method(
|
||||||
|
|
||||||
_bind_injections(fn, providers_map)
|
_bind_injections(fn, providers_map)
|
||||||
|
|
||||||
|
if fn is method:
|
||||||
|
# Hotfix, see: https://github.com/ets-labs/python-dependency-injector/issues/884
|
||||||
|
return
|
||||||
|
|
||||||
if isinstance(method, (classmethod, staticmethod)):
|
if isinstance(method, (classmethod, staticmethod)):
|
||||||
fn = type(method)(fn)
|
fn = type(method)(fn)
|
||||||
|
|
||||||
|
@ -575,7 +579,11 @@ def _unpatch_attribute(patched: PatchedAttribute) -> None:
|
||||||
|
|
||||||
def _extract_marker(parameter: inspect.Parameter) -> Optional["_Marker"]:
|
def _extract_marker(parameter: inspect.Parameter) -> Optional["_Marker"]:
|
||||||
if get_origin(parameter.annotation) is Annotated:
|
if get_origin(parameter.annotation) is Annotated:
|
||||||
marker = get_args(parameter.annotation)[1]
|
args = get_args(parameter.annotation)
|
||||||
|
if len(args) > 1:
|
||||||
|
marker = args[1]
|
||||||
|
else:
|
||||||
|
marker = None
|
||||||
else:
|
else:
|
||||||
marker = parameter.default
|
marker = parameter.default
|
||||||
|
|
||||||
|
@ -628,21 +636,6 @@ def _fetch_reference_injections( # noqa: C901
|
||||||
return injections, closing
|
return injections, closing
|
||||||
|
|
||||||
|
|
||||||
def _locate_dependent_closing_args(
|
|
||||||
provider: providers.Provider, closing_deps: Dict[str, providers.Provider]
|
|
||||||
) -> Dict[str, providers.Provider]:
|
|
||||||
for arg in [
|
|
||||||
*getattr(provider, "args", []),
|
|
||||||
*getattr(provider, "kwargs", {}).values(),
|
|
||||||
]:
|
|
||||||
if not isinstance(arg, providers.Provider):
|
|
||||||
continue
|
|
||||||
if isinstance(arg, providers.Resource):
|
|
||||||
closing_deps[str(id(arg))] = arg
|
|
||||||
|
|
||||||
_locate_dependent_closing_args(arg, closing_deps)
|
|
||||||
|
|
||||||
|
|
||||||
def _bind_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> None:
|
def _bind_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> None:
|
||||||
patched_callable = _patched_registry.get_callable(fn)
|
patched_callable = _patched_registry.get_callable(fn)
|
||||||
if patched_callable is None:
|
if patched_callable is None:
|
||||||
|
@ -664,10 +657,9 @@ def _bind_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> Non
|
||||||
|
|
||||||
if injection in patched_callable.reference_closing:
|
if injection in patched_callable.reference_closing:
|
||||||
patched_callable.add_closing(injection, provider)
|
patched_callable.add_closing(injection, provider)
|
||||||
deps = {}
|
|
||||||
_locate_dependent_closing_args(provider, deps)
|
for resource in provider.traverse(types=[providers.Resource]):
|
||||||
for key, dep in deps.items():
|
patched_callable.add_closing(str(id(resource)), resource)
|
||||||
patched_callable.add_closing(key, dep)
|
|
||||||
|
|
||||||
|
|
||||||
def _unbind_injections(fn: Callable[..., Any]) -> None:
|
def _unbind_injections(fn: Callable[..., Any]) -> None:
|
||||||
|
@ -1037,3 +1029,23 @@ def _get_sync_patched(fn: F, patched: PatchedCallable) -> F:
|
||||||
patched.closing,
|
patched.closing,
|
||||||
)
|
)
|
||||||
return cast(F, _patched)
|
return cast(F, _patched)
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 10):
|
||||||
|
def _get_annotations(obj: Any) -> Dict[str, Any]:
|
||||||
|
return inspect.get_annotations(obj)
|
||||||
|
else:
|
||||||
|
def _get_annotations(obj: Any) -> Dict[str, Any]:
|
||||||
|
return getattr(obj, "__annotations__", {})
|
||||||
|
|
||||||
|
|
||||||
|
def _get_members_and_annotated(obj: Any) -> Iterable[Tuple[str, Any]]:
|
||||||
|
members = inspect.getmembers(obj)
|
||||||
|
annotations = _get_annotations(obj)
|
||||||
|
for annotation_name, annotation in annotations.items():
|
||||||
|
if get_origin(annotation) is Annotated:
|
||||||
|
args = get_args(annotation)
|
||||||
|
if len(args) > 1:
|
||||||
|
member = args[1]
|
||||||
|
members.append((annotation_name, member))
|
||||||
|
return members
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
[pytest]
|
|
||||||
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:Please import \`.*?\` from the \`scipy(.*?)\` namespace(.*):DeprecationWarning
|
|
||||||
ignore:\`scipy(.*?)\` is deprecated(.*):DeprecationWarning
|
|
||||||
ignore:ssl\.PROTOCOL_TLS is deprecated:DeprecationWarning:botocore.*
|
|
|
@ -11,7 +11,7 @@ def index():
|
||||||
return "Hello World!"
|
return "Hello World!"
|
||||||
|
|
||||||
|
|
||||||
def test():
|
def _test():
|
||||||
return "Test!"
|
return "Test!"
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class ApplicationContainer(containers.DeclarativeContainer):
|
||||||
app = flask.Application(Flask, __name__)
|
app = flask.Application(Flask, __name__)
|
||||||
|
|
||||||
index_view = flask.View(index)
|
index_view = flask.View(index)
|
||||||
test_view = flask.View(test)
|
test_view = flask.View(_test)
|
||||||
test_class_view = flask.ClassBasedView(Test)
|
test_class_view = flask.ClassBasedView(Test)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@ async def test_init_async_function():
|
||||||
|
|
||||||
|
|
||||||
@mark.asyncio
|
@mark.asyncio
|
||||||
@mark.skipif(sys.version_info < (3, 6), reason="requires Python 3.6+")
|
|
||||||
async def test_init_async_generator():
|
async def test_init_async_generator():
|
||||||
resource = object()
|
resource = object()
|
||||||
|
|
||||||
|
|
126
tests/unit/samples/wiring/module_annotated.py
Normal file
126
tests/unit/samples/wiring/module_annotated.py
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
"""Test module for wiring with Annotated."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
if sys.version_info < (3, 9):
|
||||||
|
pytest.skip("Annotated is only available in Python 3.9+", allow_module_level=True)
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Callable, Annotated
|
||||||
|
|
||||||
|
from dependency_injector import providers
|
||||||
|
from dependency_injector.wiring import inject, Provide, Provider
|
||||||
|
|
||||||
|
from .container import Container, SubContainer
|
||||||
|
from .service import Service
|
||||||
|
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
service_provider: Annotated[Callable[..., Service], Provider[Container.service]]
|
||||||
|
undefined: Annotated[Callable, Provide[providers.Provider()]]
|
||||||
|
|
||||||
|
class TestClass:
|
||||||
|
service: Annotated[Service, Provide[Container.service]]
|
||||||
|
service_provider: Annotated[Callable[..., Service], Provider[Container.service]]
|
||||||
|
undefined: Annotated[Callable, Provide[providers.Provider()]]
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def __init__(self, service: Annotated[Service, Provide[Container.service]]):
|
||||||
|
self.service = service
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def method(self, service: Annotated[Service, Provide[Container.service]]):
|
||||||
|
return service
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@inject
|
||||||
|
def class_method(cls, service: Annotated[Service, Provide[Container.service]]):
|
||||||
|
return service
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@inject
|
||||||
|
def static_method(service: Annotated[Service, Provide[Container.service]]):
|
||||||
|
return service
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def test_function(service: Annotated[Service, Provide[Container.service]]):
|
||||||
|
return service
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def test_function_provider(service_provider: Annotated[Callable[..., Service], Provider[Container.service]]):
|
||||||
|
service = service_provider()
|
||||||
|
return service
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def test_config_value(
|
||||||
|
value_int: Annotated[int, Provide[Container.config.a.b.c.as_int()]],
|
||||||
|
value_float: Annotated[float, Provide[Container.config.a.b.c.as_float()]],
|
||||||
|
value_str: Annotated[str, Provide[Container.config.a.b.c.as_(str)]],
|
||||||
|
value_decimal: Annotated[Decimal, Provide[Container.config.a.b.c.as_(Decimal)]],
|
||||||
|
value_required: Annotated[str, Provide[Container.config.a.b.c.required()]],
|
||||||
|
value_required_int: Annotated[int, Provide[Container.config.a.b.c.required().as_int()]],
|
||||||
|
value_required_float: Annotated[float, Provide[Container.config.a.b.c.required().as_float()]],
|
||||||
|
value_required_str: Annotated[str, Provide[Container.config.a.b.c.required().as_(str)]],
|
||||||
|
value_required_decimal: Annotated[str, Provide[Container.config.a.b.c.required().as_(Decimal)]],
|
||||||
|
):
|
||||||
|
return (
|
||||||
|
value_int,
|
||||||
|
value_float,
|
||||||
|
value_str,
|
||||||
|
value_decimal,
|
||||||
|
value_required,
|
||||||
|
value_required_int,
|
||||||
|
value_required_float,
|
||||||
|
value_required_str,
|
||||||
|
value_required_decimal,
|
||||||
|
)
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def test_config_value_required_undefined(
|
||||||
|
value_required: Annotated[int, Provide[Container.config.a.b.c.required()]],
|
||||||
|
):
|
||||||
|
return value_required
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def test_provide_provider(service_provider: Annotated[Callable[..., Service], Provide[Container.service.provider]]):
|
||||||
|
service = service_provider()
|
||||||
|
return service
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def test_provider_provider(service_provider: Annotated[Callable[..., Service], Provider[Container.service.provider]]):
|
||||||
|
service = service_provider()
|
||||||
|
return service
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def test_provided_instance(some_value: Annotated[int, Provide[Container.service.provided.foo["bar"].call()]]):
|
||||||
|
return some_value
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def test_subcontainer_provider(some_value: Annotated[int, Provide[Container.sub.int_object]]):
|
||||||
|
return some_value
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def test_config_invariant(some_value: Annotated[int, Provide[Container.config.option[Container.config.switch]]]):
|
||||||
|
return some_value
|
||||||
|
|
||||||
|
@inject
|
||||||
|
def test_provide_from_different_containers(
|
||||||
|
service: Annotated[Service, Provide[Container.service]],
|
||||||
|
some_value: Annotated[int, Provide[SubContainer.int_object]],
|
||||||
|
):
|
||||||
|
return service, some_value
|
||||||
|
|
||||||
|
class ClassDecorator:
|
||||||
|
def __init__(self, fn):
|
||||||
|
self._fn = fn
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return self._fn(*args, **kwargs)
|
||||||
|
|
||||||
|
@ClassDecorator
|
||||||
|
@inject
|
||||||
|
def test_class_decorator(service: Annotated[Service, Provide[Container.service]]):
|
||||||
|
return service
|
||||||
|
|
||||||
|
def test_container(container: Annotated[Container, Provide[Container]]):
|
||||||
|
return container.service()
|
|
@ -59,12 +59,13 @@ def init_service(counter: Counter, _list: List[int], _dict: Dict[str, int]):
|
||||||
|
|
||||||
|
|
||||||
class Container(containers.DeclarativeContainer):
|
class Container(containers.DeclarativeContainer):
|
||||||
|
config = providers.Configuration(default={"a": 1, "b": 4})
|
||||||
counter = providers.Singleton(Counter)
|
counter = providers.Singleton(Counter)
|
||||||
_list = providers.List(
|
_list = providers.List(
|
||||||
providers.Callable(lambda a: a, a=1), providers.Callable(lambda b: b, 2)
|
providers.Callable(lambda a: a, a=config.a), providers.Callable(lambda b: b, 2)
|
||||||
)
|
)
|
||||||
_dict = providers.Dict(
|
_dict = providers.Dict(
|
||||||
a=providers.Callable(lambda a: a, a=3), b=providers.Callable(lambda b: b, 4)
|
a=providers.Callable(lambda a: a, a=3), b=providers.Callable(lambda b: b, config.b)
|
||||||
)
|
)
|
||||||
service = providers.Resource(init_service, counter, _list, _dict=_dict)
|
service = providers.Resource(init_service, counter, _list, _dict=_dict)
|
||||||
service2 = providers.Resource(init_service, counter, _list, _dict=_dict)
|
service2 = providers.Resource(init_service, counter, _list, _dict=_dict)
|
||||||
|
|
176
tests/unit/wiring/provider_ids/test_main_annotated_py36.py
Normal file
176
tests/unit/wiring/provider_ids/test_main_annotated_py36.py
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
"""Main wiring tests for Annotated attribute and argument injection."""
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from dependency_injector import errors
|
||||||
|
from dependency_injector.wiring import Closing, Provide, Provider, wire
|
||||||
|
from pytest import fixture, mark, raises
|
||||||
|
|
||||||
|
from samples.wiring import module_annotated as module, package, resourceclosing
|
||||||
|
from samples.wiring.service import Service
|
||||||
|
from samples.wiring.container import Container, SubContainer
|
||||||
|
|
||||||
|
@fixture(autouse=True)
|
||||||
|
def container():
|
||||||
|
container = Container(config={"a": {"b": {"c": 10}}})
|
||||||
|
container.wire(
|
||||||
|
modules=[module],
|
||||||
|
packages=[package],
|
||||||
|
)
|
||||||
|
yield container
|
||||||
|
container.unwire()
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def subcontainer():
|
||||||
|
container = SubContainer()
|
||||||
|
container.wire(
|
||||||
|
modules=[module],
|
||||||
|
packages=[package],
|
||||||
|
)
|
||||||
|
yield container
|
||||||
|
container.unwire()
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def resourceclosing_container():
|
||||||
|
container = resourceclosing.Container()
|
||||||
|
container.wire(modules=[resourceclosing])
|
||||||
|
yield container
|
||||||
|
container.unwire()
|
||||||
|
|
||||||
|
def test_module_attributes_wiring():
|
||||||
|
assert isinstance(module.service, Service)
|
||||||
|
assert isinstance(module.service_provider(), Service)
|
||||||
|
assert isinstance(module.__annotations__['undefined'], typing._AnnotatedAlias)
|
||||||
|
|
||||||
|
def test_class_wiring():
|
||||||
|
test_class_object = module.TestClass()
|
||||||
|
assert isinstance(test_class_object.service, Service)
|
||||||
|
|
||||||
|
def test_class_wiring_context_arg(container: Container):
|
||||||
|
test_service = container.service()
|
||||||
|
test_class_object = module.TestClass(service=test_service)
|
||||||
|
assert test_class_object.service is test_service
|
||||||
|
|
||||||
|
def test_class_method_wiring():
|
||||||
|
test_class_object = module.TestClass()
|
||||||
|
service = test_class_object.method()
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
|
||||||
|
def test_class_classmethod_wiring():
|
||||||
|
service = module.TestClass.class_method()
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
|
||||||
|
def test_instance_classmethod_wiring():
|
||||||
|
instance = module.TestClass()
|
||||||
|
service = instance.class_method()
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
|
||||||
|
def test_class_staticmethod_wiring():
|
||||||
|
service = module.TestClass.static_method()
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
|
||||||
|
def test_instance_staticmethod_wiring():
|
||||||
|
instance = module.TestClass()
|
||||||
|
service = instance.static_method()
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
|
||||||
|
def test_class_attribute_wiring():
|
||||||
|
assert isinstance(module.TestClass.service, Service)
|
||||||
|
assert isinstance(module.TestClass.service_provider(), Service)
|
||||||
|
assert isinstance(module.TestClass.__annotations__['undefined'], typing._AnnotatedAlias)
|
||||||
|
|
||||||
|
def test_function_wiring():
|
||||||
|
service = module.test_function()
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
|
||||||
|
def test_function_wiring_context_arg(container: Container):
|
||||||
|
test_service = container.service()
|
||||||
|
service = module.test_function(service=test_service)
|
||||||
|
assert service is test_service
|
||||||
|
|
||||||
|
def test_function_wiring_provider():
|
||||||
|
service = module.test_function_provider()
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
|
||||||
|
def test_function_wiring_provider_context_arg(container: Container):
|
||||||
|
test_service = container.service()
|
||||||
|
service = module.test_function_provider(service_provider=lambda: test_service)
|
||||||
|
assert service is test_service
|
||||||
|
|
||||||
|
def test_configuration_option():
|
||||||
|
(
|
||||||
|
value_int,
|
||||||
|
value_float,
|
||||||
|
value_str,
|
||||||
|
value_decimal,
|
||||||
|
value_required,
|
||||||
|
value_required_int,
|
||||||
|
value_required_float,
|
||||||
|
value_required_str,
|
||||||
|
value_required_decimal,
|
||||||
|
) = module.test_config_value()
|
||||||
|
|
||||||
|
assert value_int == 10
|
||||||
|
assert value_float == 10.0
|
||||||
|
assert value_str == "10"
|
||||||
|
assert value_decimal == Decimal(10)
|
||||||
|
assert value_required == 10
|
||||||
|
assert value_required_int == 10
|
||||||
|
assert value_required_float == 10.0
|
||||||
|
assert value_required_str == "10"
|
||||||
|
assert value_required_decimal == Decimal(10)
|
||||||
|
|
||||||
|
def test_configuration_option_required_undefined(container: Container):
|
||||||
|
container.config.reset_override()
|
||||||
|
with raises(errors.Error, match="Undefined configuration option \"config.a.b.c\""):
|
||||||
|
module.test_config_value_required_undefined()
|
||||||
|
|
||||||
|
def test_provide_provider():
|
||||||
|
service = module.test_provide_provider()
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
|
||||||
|
def test_provider_provider():
|
||||||
|
service = module.test_provider_provider()
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
|
||||||
|
def test_provided_instance(container: Container):
|
||||||
|
class TestService:
|
||||||
|
foo = {"bar": lambda: 10}
|
||||||
|
|
||||||
|
with container.service.override(TestService()):
|
||||||
|
some_value = module.test_provided_instance()
|
||||||
|
assert some_value == 10
|
||||||
|
|
||||||
|
def test_subcontainer():
|
||||||
|
some_value = module.test_subcontainer_provider()
|
||||||
|
assert some_value == 1
|
||||||
|
|
||||||
|
def test_config_invariant(container: Container):
|
||||||
|
config = {
|
||||||
|
"option": {
|
||||||
|
"a": 1,
|
||||||
|
"b": 2,
|
||||||
|
},
|
||||||
|
"switch": "a",
|
||||||
|
}
|
||||||
|
container.config.from_dict(config)
|
||||||
|
|
||||||
|
value_default = module.test_config_invariant()
|
||||||
|
assert value_default == 1
|
||||||
|
|
||||||
|
with container.config.switch.override("a"):
|
||||||
|
value_a = module.test_config_invariant()
|
||||||
|
assert value_a == 1
|
||||||
|
|
||||||
|
with container.config.switch.override("b"):
|
||||||
|
value_b = module.test_config_invariant()
|
||||||
|
assert value_b == 2
|
||||||
|
|
||||||
|
def test_class_decorator():
|
||||||
|
service = module.test_class_decorator()
|
||||||
|
assert isinstance(service, Service)
|
||||||
|
|
||||||
|
def test_container():
|
||||||
|
service = module.test_container()
|
||||||
|
assert isinstance(service, Service)
|
40
tests/unit/wiring/test_no_interference.py
Normal file
40
tests/unit/wiring/test_no_interference.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
from pytest import fixture
|
||||||
|
|
||||||
|
from dependency_injector.containers import DeclarativeContainer
|
||||||
|
from dependency_injector.providers import Object
|
||||||
|
from dependency_injector.wiring import Provide, inject
|
||||||
|
|
||||||
|
|
||||||
|
class A:
|
||||||
|
@inject
|
||||||
|
def foo(self, value: str = Provide["value"]) -> str:
|
||||||
|
return "A" + value
|
||||||
|
|
||||||
|
|
||||||
|
class B(A): ...
|
||||||
|
|
||||||
|
|
||||||
|
class C(A):
|
||||||
|
def foo(self, *args: Any, **kwargs: Any) -> str:
|
||||||
|
return "C" + super().foo()
|
||||||
|
|
||||||
|
|
||||||
|
class D(B, C): ...
|
||||||
|
|
||||||
|
|
||||||
|
class Container(DeclarativeContainer):
|
||||||
|
value = Object("X")
|
||||||
|
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def container() -> Iterator[Container]:
|
||||||
|
c = Container()
|
||||||
|
c.wire(modules=[__name__])
|
||||||
|
yield c
|
||||||
|
c.unwire()
|
||||||
|
|
||||||
|
|
||||||
|
def test_preserve_mro(container: Container) -> None:
|
||||||
|
assert D().foo() == "CAX"
|
29
tox.ini
29
tox.ini
|
@ -1,7 +1,7 @@
|
||||||
[tox]
|
[tox]
|
||||||
parallel_show_output = true
|
parallel_show_output = true
|
||||||
envlist=
|
envlist=
|
||||||
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
|
coveralls, pylint, flake8, pydocstyle, pydantic-v1, pydantic-v2, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, pypy3.9, pypy3.10, pypy3.11
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps=
|
deps=
|
||||||
|
@ -19,8 +19,7 @@ deps=
|
||||||
werkzeug
|
werkzeug
|
||||||
extras=
|
extras=
|
||||||
yaml
|
yaml
|
||||||
commands = pytest -c tests/.configs/pytest.ini
|
commands = pytest
|
||||||
python_files = test_*_py3*.py
|
|
||||||
setenv =
|
setenv =
|
||||||
COVERAGE_RCFILE = pyproject.toml
|
COVERAGE_RCFILE = pyproject.toml
|
||||||
|
|
||||||
|
@ -45,7 +44,7 @@ deps =
|
||||||
boto3
|
boto3
|
||||||
mypy_boto3_s3
|
mypy_boto3_s3
|
||||||
werkzeug
|
werkzeug
|
||||||
commands = pytest -c tests/.configs/pytest.ini -m pydantic
|
commands = pytest -m pydantic
|
||||||
|
|
||||||
[testenv:coveralls]
|
[testenv:coveralls]
|
||||||
passenv = GITHUB_*, COVERALLS_*, DEPENDENCY_INJECTOR_*
|
passenv = GITHUB_*, COVERALLS_*, DEPENDENCY_INJECTOR_*
|
||||||
|
@ -57,26 +56,10 @@ deps=
|
||||||
coveralls>=4
|
coveralls>=4
|
||||||
commands=
|
commands=
|
||||||
coverage erase
|
coverage erase
|
||||||
coverage run -m pytest -c tests/.configs/pytest.ini
|
coverage run -m pytest
|
||||||
coverage report
|
coverage report
|
||||||
coveralls
|
coveralls
|
||||||
|
|
||||||
[testenv:pypy3.9]
|
|
||||||
deps=
|
|
||||||
pytest
|
|
||||||
pytest-asyncio
|
|
||||||
httpx
|
|
||||||
flask
|
|
||||||
pydantic-settings
|
|
||||||
werkzeug
|
|
||||||
fastapi
|
|
||||||
boto3
|
|
||||||
mypy_boto3_s3
|
|
||||||
extras=
|
|
||||||
yaml
|
|
||||||
commands = pytest -c tests/.configs/pytest-py35.ini
|
|
||||||
|
|
||||||
|
|
||||||
[testenv:pylint]
|
[testenv:pylint]
|
||||||
deps=
|
deps=
|
||||||
pylint
|
pylint
|
||||||
|
@ -89,8 +72,8 @@ commands=
|
||||||
deps=
|
deps=
|
||||||
flake8
|
flake8
|
||||||
commands=
|
commands=
|
||||||
flake8 --max-complexity=10 src/dependency_injector/
|
flake8 src/dependency_injector/
|
||||||
flake8 --max-complexity=10 examples/
|
flake8 examples/
|
||||||
|
|
||||||
[testenv:pydocstyle]
|
[testenv:pydocstyle]
|
||||||
deps=
|
deps=
|
||||||
|
|
Loading…
Reference in New Issue
Block a user