mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-06-10 08:33:17 +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:
|
||||
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2019, macos-14]
|
||||
env:
|
||||
CIBW_SKIP: cp27-*
|
||||
CIBW_ENABLE: pypy
|
||||
CIBW_ENVIRONMENT: >-
|
||||
PIP_CONFIG_SETTINGS="build_ext=-j4"
|
||||
DEPENDENCY_INJECTOR_LIMITED_API="1"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v2.20.0
|
||||
uses: pypa/cibuildwheel@v2.23.3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cibw-wheels-x86-${{ matrix.os }}-${{ strategy.job-index }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
publish:
|
||||
name: Publish on PyPI
|
||||
test-publish:
|
||||
name: Upload release to TestPyPI
|
||||
needs: [build-sdist, build-wheels]
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
environment: test-pypi
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
@ -84,11 +90,22 @@ jobs:
|
|||
merge-multiple: true
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
# For publishing to Test PyPI, uncomment next two lines:
|
||||
# password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||
# repository_url: https://test.pypi.org/legacy/
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
|
||||
publish:
|
||||
name: Upload release to PyPI
|
||||
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:
|
||||
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:
|
||||
|
||||
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:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
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:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
|
@ -34,6 +18,7 @@ jobs:
|
|||
- run: pip install tox
|
||||
- run: tox
|
||||
env:
|
||||
DEPENDENCY_INJECTOR_LIMITED_API: 1
|
||||
TOXENV: ${{ matrix.python-version }}
|
||||
|
||||
test-different-pydantic-versions:
|
||||
|
@ -60,7 +45,7 @@ jobs:
|
|||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.12
|
||||
- run: pip install tox 'cython>=3,<4'
|
||||
- run: pip install tox
|
||||
- run: tox -vv
|
||||
env:
|
||||
TOXENV: coveralls
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -15,6 +15,7 @@ lib64/
|
|||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheelhouse/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.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
|
||||
include README.rst
|
||||
include CONTRIBUTORS.rst
|
||||
include LICENSE.rst
|
||||
include requirements.txt
|
||||
include setup.py
|
||||
include tox.ini
|
||||
include py.typed
|
||||
|
|
2
Makefile
2
Makefile
|
@ -36,7 +36,7 @@ uninstall:
|
|||
test:
|
||||
# Unit tests with coverage report
|
||||
coverage erase
|
||||
coverage run -m pytest -c tests/.configs/pytest.ini
|
||||
coverage run -m pytest
|
||||
coverage report
|
||||
coverage html
|
||||
|
||||
|
|
|
@ -7,6 +7,18 @@ that were made in every particular version.
|
|||
From version 0.7.6 *Dependency Injector* framework strictly
|
||||
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
|
||||
------
|
||||
|
||||
|
|
|
@ -254,13 +254,43 @@ To inject a container use special identifier ``<container>``:
|
|||
Making injections into modules and class attributes
|
||||
---------------------------------------------------
|
||||
|
||||
You can use wiring to make injections into modules and class attributes.
|
||||
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
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 14,19
|
||||
|
||||
Annotated form (Python 3.9+):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
service: Annotated[Service, Provide[Container.service]]
|
||||
|
||||
class Main:
|
||||
service: Annotated[Service, Provide[Container.service]]
|
||||
|
||||
Full example of the annotated form:
|
||||
|
||||
.. literalinclude:: ../examples/wiring/example_attribute_annotated.py
|
||||
:language: python
|
||||
:lines: 3-
|
||||
:emphasize-lines: 16,21
|
||||
|
||||
You could also use string identifiers to avoid a dependency on a container:
|
||||
|
||||
.. code-block:: python
|
||||
|
|
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]
|
||||
requires = ["setuptools", "Cython"]
|
||||
requires = ["setuptools", "Cython>=3.1.1"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
|
@ -13,7 +13,7 @@ maintainers = [
|
|||
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"
|
||||
requires-python = ">=3.8"
|
||||
keywords = [
|
||||
"Dependency injection",
|
||||
"DI",
|
||||
|
@ -31,7 +31,6 @@ classifiers = [
|
|||
"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",
|
||||
|
@ -99,3 +98,18 @@ ignore = ["tests"]
|
|||
[tool.pylint.design]
|
||||
min-public-methods = 0
|
||||
max-public-methods = 30
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests/unit/"]
|
||||
asyncio_mode = "auto"
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
markers = [
|
||||
"pydantic: Tests with Pydantic as a dependency",
|
||||
]
|
||||
filterwarnings = [
|
||||
"ignore: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
|
||||
pytest
|
||||
pytest-asyncio
|
||||
|
@ -13,7 +13,8 @@ mypy
|
|||
pyyaml
|
||||
httpx
|
||||
fastapi
|
||||
pydantic==1.10.17
|
||||
pydantic
|
||||
pydantic-settings
|
||||
numpy
|
||||
scipy
|
||||
boto3
|
||||
|
|
|
@ -8,7 +8,7 @@ per-file-ignores =
|
|||
examples/containers/traverse.py: E501
|
||||
examples/providers/async.py: F841
|
||||
examples/providers/async_overriding.py: F841
|
||||
examples/wiring/*: F841
|
||||
examples/wiring/*: F821,F841
|
||||
|
||||
[pydocstyle]
|
||||
ignore = D100,D101,D102,D105,D106,D107,D203,D213
|
||||
|
|
13
setup.py
13
setup.py
|
@ -1,13 +1,19 @@
|
|||
"""`Dependency injector` setup script."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from Cython.Build import cythonize
|
||||
from Cython.Compiler import Options
|
||||
from setuptools import Extension, setup
|
||||
|
||||
debug = os.environ.get("DEPENDENCY_INJECTOR_DEBUG_MODE") == "1"
|
||||
limited_api = (
|
||||
os.environ.get("DEPENDENCY_INJECTOR_LIMITED_API") == "1"
|
||||
and sys.implementation.name == "cpython"
|
||||
)
|
||||
defined_macros = []
|
||||
options = {}
|
||||
compiler_directives = {
|
||||
"language_level": 3,
|
||||
"profile": debug,
|
||||
|
@ -17,6 +23,7 @@ Options.annotate = debug
|
|||
|
||||
# Adding debug options:
|
||||
if debug:
|
||||
limited_api = False # line tracing is not part of the Limited API
|
||||
defined_macros.extend(
|
||||
[
|
||||
("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(
|
||||
options=options,
|
||||
ext_modules=cythonize(
|
||||
[
|
||||
Extension(
|
||||
"*",
|
||||
["src/**/*.pyx"],
|
||||
define_macros=defined_macros,
|
||||
py_limited_api=limited_api,
|
||||
),
|
||||
],
|
||||
annotate=debug,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Top-level package."""
|
||||
|
||||
__version__ = "4.46.0"
|
||||
__version__ = "4.47.0"
|
||||
"""Version number.
|
||||
|
||||
:type: str
|
||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import absolute_import
|
|||
|
||||
import asyncio
|
||||
import builtins
|
||||
import contextvars
|
||||
import copy
|
||||
import errno
|
||||
import functools
|
||||
|
@ -17,6 +16,7 @@ import sys
|
|||
import threading
|
||||
import warnings
|
||||
from configparser import ConfigParser as IniConfigParser
|
||||
from contextvars import ContextVar
|
||||
|
||||
try:
|
||||
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
|
||||
)
|
||||
|
||||
@property
|
||||
def root(self):
|
||||
def _get_root(self):
|
||||
return self._root
|
||||
|
||||
def get_name(self):
|
||||
|
@ -3224,15 +3223,10 @@ cdef class ContextLocalSingleton(BaseSingleton):
|
|||
:param provides: Provided 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)
|
||||
self._storage = contextvars.ContextVar("_storage", default=self._none)
|
||||
self._storage = ContextVar("_storage", default=self._none)
|
||||
|
||||
def reset(self):
|
||||
"""Reset cached instance, if any.
|
||||
|
|
|
@ -314,7 +314,7 @@ class ProvidersMap:
|
|||
original: providers.ConfigurationOption,
|
||||
as_: Any = None,
|
||||
) -> Optional[providers.Provider]:
|
||||
original_root = original.root
|
||||
original_root = original._get_root()
|
||||
new = self._resolve_provider(original_root)
|
||||
if new is None:
|
||||
return None
|
||||
|
@ -415,7 +415,7 @@ def wire( # noqa: C901
|
|||
providers_map = ProvidersMap(container)
|
||||
|
||||
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):
|
||||
continue
|
||||
|
||||
|
@ -426,7 +426,7 @@ def wire( # noqa: C901
|
|||
elif inspect.isclass(member):
|
||||
cls = member
|
||||
try:
|
||||
cls_members = inspect.getmembers(cls)
|
||||
cls_members = _get_members_and_annotated(cls)
|
||||
except Exception: # noqa
|
||||
# Hotfix, see: https://github.com/ets-labs/python-dependency-injector/issues/441
|
||||
continue
|
||||
|
@ -523,6 +523,10 @@ def _patch_method(
|
|||
|
||||
_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)):
|
||||
fn = type(method)(fn)
|
||||
|
||||
|
@ -575,7 +579,11 @@ def _unpatch_attribute(patched: PatchedAttribute) -> None:
|
|||
|
||||
def _extract_marker(parameter: inspect.Parameter) -> Optional["_Marker"]:
|
||||
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:
|
||||
marker = parameter.default
|
||||
|
||||
|
@ -628,21 +636,6 @@ def _fetch_reference_injections( # noqa: C901
|
|||
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:
|
||||
patched_callable = _patched_registry.get_callable(fn)
|
||||
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:
|
||||
patched_callable.add_closing(injection, provider)
|
||||
deps = {}
|
||||
_locate_dependent_closing_args(provider, deps)
|
||||
for key, dep in deps.items():
|
||||
patched_callable.add_closing(key, dep)
|
||||
|
||||
for resource in provider.traverse(types=[providers.Resource]):
|
||||
patched_callable.add_closing(str(id(resource)), resource)
|
||||
|
||||
|
||||
def _unbind_injections(fn: Callable[..., Any]) -> None:
|
||||
|
@ -1037,3 +1029,23 @@ def _get_sync_patched(fn: F, patched: PatchedCallable) -> F:
|
|||
patched.closing,
|
||||
)
|
||||
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!"
|
||||
|
||||
|
||||
def test():
|
||||
def _test():
|
||||
return "Test!"
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ class ApplicationContainer(containers.DeclarativeContainer):
|
|||
app = flask.Application(Flask, __name__)
|
||||
|
||||
index_view = flask.View(index)
|
||||
test_view = flask.View(test)
|
||||
test_view = flask.View(_test)
|
||||
test_class_view = flask.ClassBasedView(Test)
|
||||
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@ async def test_init_async_function():
|
|||
|
||||
|
||||
@mark.asyncio
|
||||
@mark.skipif(sys.version_info < (3, 6), reason="requires Python 3.6+")
|
||||
async def test_init_async_generator():
|
||||
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):
|
||||
config = providers.Configuration(default={"a": 1, "b": 4})
|
||||
counter = providers.Singleton(Counter)
|
||||
_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(
|
||||
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)
|
||||
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]
|
||||
parallel_show_output = true
|
||||
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]
|
||||
deps=
|
||||
|
@ -19,8 +19,7 @@ deps=
|
|||
werkzeug
|
||||
extras=
|
||||
yaml
|
||||
commands = pytest -c tests/.configs/pytest.ini
|
||||
python_files = test_*_py3*.py
|
||||
commands = pytest
|
||||
setenv =
|
||||
COVERAGE_RCFILE = pyproject.toml
|
||||
|
||||
|
@ -45,7 +44,7 @@ deps =
|
|||
boto3
|
||||
mypy_boto3_s3
|
||||
werkzeug
|
||||
commands = pytest -c tests/.configs/pytest.ini -m pydantic
|
||||
commands = pytest -m pydantic
|
||||
|
||||
[testenv:coveralls]
|
||||
passenv = GITHUB_*, COVERALLS_*, DEPENDENCY_INJECTOR_*
|
||||
|
@ -57,26 +56,10 @@ deps=
|
|||
coveralls>=4
|
||||
commands=
|
||||
coverage erase
|
||||
coverage run -m pytest -c tests/.configs/pytest.ini
|
||||
coverage run -m pytest
|
||||
coverage report
|
||||
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]
|
||||
deps=
|
||||
pylint
|
||||
|
@ -89,8 +72,8 @@ commands=
|
|||
deps=
|
||||
flake8
|
||||
commands=
|
||||
flake8 --max-complexity=10 src/dependency_injector/
|
||||
flake8 --max-complexity=10 examples/
|
||||
flake8 src/dependency_injector/
|
||||
flake8 examples/
|
||||
|
||||
[testenv:pydocstyle]
|
||||
deps=
|
||||
|
|
Loading…
Reference in New Issue
Block a user