Compare commits

..

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

83 changed files with 765 additions and 1919 deletions

View File

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

View File

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

View File

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

View File

@ -1,9 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{py,pyi,pxd,pyx}]
ij_visual_guides = 80,88

View File

@ -60,46 +60,22 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2022, macos-14]
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2019, macos-14]
env:
CIBW_ENABLE: pypy
CIBW_ENVIRONMENT: >-
PIP_CONFIG_SETTINGS="build_ext=-j4"
DEPENDENCY_INJECTOR_LIMITED_API="1"
CFLAGS="-g0"
CIBW_SKIP: cp27-*
steps:
- uses: actions/checkout@v3
- name: Build wheels
uses: pypa/cibuildwheel@v3.0.0
uses: pypa/cibuildwheel@v2.20.0
- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
name: cibw-wheels-x86-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl
test-publish:
name: Upload release to TestPyPI
needs: [build-sdist, build-wheels]
runs-on: ubuntu-latest
environment: test-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
with:
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
name: Publish on PyPI
needs: [build-sdist, build-wheels]
runs-on: ubuntu-24.04
steps:
- uses: actions/download-artifact@v4
with:
@ -107,6 +83,12 @@ jobs:
path: dist
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/
publish-docs:
name: Publish docs

View File

@ -4,12 +4,28 @@ on: [push, pull_request, workflow_dispatch]
jobs:
test-on-different-versions:
name: Run tests
runs-on: ubuntu-latest
tests-on-legacy-versions:
name: Run tests on legacy versions
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
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]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
@ -18,7 +34,6 @@ jobs:
- run: pip install tox
- run: tox
env:
DEPENDENCY_INJECTOR_LIMITED_API: 1
TOXENV: ${{ matrix.python-version }}
test-different-pydantic-versions:
@ -45,7 +60,7 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: 3.12
- run: pip install tox
- run: pip install tox 'cython>=3,<4'
- run: tox -vv
env:
TOXENV: coveralls

1
.gitignore vendored
View File

@ -15,7 +15,6 @@ lib64/
parts/
sdist/
var/
wheelhouse/
*.egg-info/
.installed.cfg
*.egg

View File

@ -1,7 +1,9 @@
recursive-include src/dependency_injector *.py* *.c py.typed
recursive-include src/dependency_injector *.py* *.c
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

View File

@ -36,7 +36,7 @@ uninstall:
test:
# Unit tests with coverage report
coverage erase
coverage run -m pytest
coverage run -m pytest -c tests/.configs/pytest.ini
coverage report
coverage html

View File

@ -1,9 +0,0 @@
dependency_injector.ext.starlette
=================================
.. automodule:: dependency_injector.ext.starlette
:members:
:inherited-members:
:show-inheritance:
.. disqus::

View File

@ -2,11 +2,10 @@ API Documentation
=================
.. toctree::
:maxdepth: 2
:maxdepth: 2
top-level
providers
containers
wiring
errors
asgi-lifespan

View File

@ -72,7 +72,7 @@ release = version
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:

View File

@ -78,7 +78,7 @@ Container is wired to the ``views`` module in the app config ``web/apps.py``:
.. literalinclude:: ../../examples/miniapps/django/web/apps.py
:language: python
:emphasize-lines: 12
:emphasize-lines: 13
Tests
-----

View File

@ -1,48 +0,0 @@
.. _fastdepends-example:
FastDepends example
===================
.. meta::
:keywords: Python,Dependency Injection,FastDepends,Example
:description: This example demonstrates a usage of the FastDepends and Dependency Injector.
This example demonstrates how to use ``Dependency Injector`` with `FastDepends <https://github.com/Lancetnik/FastDepends>`_, a lightweight dependency injection framework inspired by FastAPI's dependency system, but without the web framework components.
Basic Usage
-----------
The integration between FastDepends and Dependency Injector is straightforward. Simply use Dependency Injector's ``Provide`` marker within FastDepends' ``Depends`` function:
.. code-block:: python
import sys
from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
from fast_depends import Depends
class CoefficientService:
@staticmethod
def get_coefficient() -> float:
return 1.2
class Container(containers.DeclarativeContainer):
service = providers.Factory(CoefficientService)
@inject
def apply_coefficient(
a: int,
coefficient_provider: CoefficientService = Depends(Provide[Container.service]),
) -> float:
return a * coefficient_provider.get_coefficient()
container = Container()
container.wire(modules=[sys.modules[__name__]])
apply_coefficient(100) == 120.0

View File

@ -22,6 +22,5 @@ Explore the examples to see the ``Dependency Injector`` in action.
fastapi
fastapi-redis
fastapi-sqlalchemy
fastdepends
.. disqus::

View File

@ -31,7 +31,7 @@ Key features of the ``Dependency Injector``:
The framework stands on the `PEP20 (The Zen of Python) <https://www.python.org/dev/peps/pep-0020/>`_ principle:
.. code-block:: text
.. code-block:: plain
Explicit is better than implicit

View File

@ -7,42 +7,6 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
4.48.1
------
* Improve performance of ``dependency_injector._cwiring.DependencyResolver``
* Add ``typing-extensions`` as a dependency for older Python versions (<3.11)
* Produce warning on ``@inject``s without ``Provide[...]`` marks
* Add support for `resource_type` in ``Lifespan``s
4.48.0
------
- Improve performance of wiring (`#897 <https://github.com/ets-labs/python-dependency-injector/pull/897>`_)
- Add Context Manager support to Resource provider (`#899 <https://github.com/ets-labs/python-dependency-injector/pull/899>`_)
- Add support for async generator injections (`#900 <https://github.com/ets-labs/python-dependency-injector/pull/900>`_)
- Fix unintended dependency on ``typing_extensions`` (`#902 <https://github.com/ets-labs/python-dependency-injector/pull/902>`_)
- Add support for Fast Depends (`#898 <https://github.com/ets-labs/python-dependency-injector/pull/898>`_)
- Add ``resource_type`` parameter to init and shutdown resources using specialized providers (`#858 <https://github.com/ets-labs/python-dependency-injector/pull/858>`_)
4.47.1
------
- Fix typing for wiring marker (`#892 <https://github.com/ets-labs/python-dependency-injector/pull/896>`_)
- Strip debug symbols in wheels
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
------
@ -395,8 +359,8 @@ Many thanks to `ZipFile <https://github.com/ZipFile>`_ for both contributions.
- Make refactoring of wiring module and tests.
See PR # `#406 <https://github.com/ets-labs/python-dependency-injector/issues/406>`_.
Thanks to `@withshubh <https://github.com/withshubh>`_ for the contribution:
- Remove unused imports in tests.
- Use literal syntax to create data structure in tests.
- Remove unused imports in tests.
- Use literal syntax to create data structure in tests.
- Add integration with a static analysis tool `DeepSource <https://deepsource.io/>`_.
4.26.0

View File

@ -61,12 +61,11 @@ When you call ``.shutdown()`` method on a resource provider, it will remove the
if any, and switch to uninitialized state. Some of resource initializer types support specifying custom
resource shutdown.
Resource provider supports 4 types of initializers:
Resource provider supports 3 types of initializers:
- Function
- Context Manager
- Generator (legacy)
- Subclass of ``resources.Resource`` (legacy)
- Generator
- Subclass of ``resources.Resource``
Function initializer
--------------------
@ -104,44 +103,8 @@ you configure global resource:
Function initializer does not provide a way to specify custom resource shutdown.
Context Manager initializer
---------------------------
This is an extension to the Function initializer. Resource provider automatically detects if the initializer returns a
context manager and uses it to manage the resource lifecycle.
.. code-block:: python
from dependency_injector import containers, providers
class DatabaseConnection:
def __init__(self, host, port, user, password):
self.host = host
self.port = port
self.user = user
self.password = password
def __enter__(self):
print(f"Connecting to {self.host}:{self.port} as {self.user}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Closing connection")
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
db = providers.Resource(
DatabaseConnection,
host=config.db.host,
port=config.db.port,
user=config.db.user,
password=config.db.password,
)
Generator initializer (legacy)
------------------------------
Generator initializer
---------------------
Resource provider can use 2-step generators:
@ -191,13 +154,8 @@ object is not mandatory. You can leave ``yield`` statement empty:
argument2=...,
)
.. note::
Generator initializers are automatically wrapped with ``contextmanager`` or ``asynccontextmanager`` decorator when
provided to a ``Resource`` provider.
Subclass initializer (legacy)
-----------------------------
Subclass initializer
--------------------
You can create resource initializer by implementing a subclass of the ``resources.Resource``:
@ -252,72 +210,6 @@ first argument.
.. _resource-provider-wiring-closing:
Scoping Resources using specialized subclasses
----------------------------------------------
You can use specialized subclasses of ``Resource`` provider to initialize and shutdown resources by type.
Allowing for example to only initialize a subgroup of resources.
.. code-block:: python
class ScopedResource(resources.Resource):
pass
def init_service(name) -> Service:
print(f"Init {name}")
yield Service()
print(f"Shutdown {name}")
class Container(containers.DeclarativeContainer):
scoped = ScopedResource(
init_service,
"scoped",
)
generic = providers.Resource(
init_service,
"generic",
)
To initialize resources by type you can use ``init_resources(resource_type)`` and ``shutdown_resources(resource_type)``
methods adding the resource type as an argument:
.. code-block:: python
def main():
container = Container()
container.init_resources(ScopedResource)
# Generates:
# >>> Init scoped
container.shutdown_resources(ScopedResource)
# Generates:
# >>> Shutdown scoped
And to initialize all resources you can use ``init_resources()`` and ``shutdown_resources()`` without arguments:
.. code-block:: python
def main():
container = Container()
container.init_resources()
# Generates:
# >>> Init scoped
# >>> Init generic
container.shutdown_resources()
# Generates:
# >>> Shutdown scoped
# >>> Shutdown generic
It works using the ``traverse()`` method to find all resources of the specified type, selecting all resources
which are instances of the specified type.
Resources, wiring, and per-function execution scope
---------------------------------------------------
@ -371,11 +263,10 @@ Asynchronous function initializer:
argument2=...,
)
Asynchronous Context Manager initializer:
Asynchronous generator initializer:
.. code-block:: python
@asynccontextmanager
async def init_async_resource(argument1=..., argument2=...):
connection = await connect()
yield connection
@ -467,54 +358,5 @@ See also:
- Wiring :ref:`async-injections-wiring`
- :ref:`fastapi-redis-example`
ASGI Lifespan Protocol Support
------------------------------
The :mod:`dependency_injector.ext.starlette` module provides a :class:`~dependency_injector.ext.starlette.Lifespan`
class that integrates resource providers with ASGI applications using the `Lifespan Protocol`_. This allows resources to
be automatically initialized at application startup and properly shut down when the application stops.
.. code-block:: python
from contextlib import asynccontextmanager
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
from dependency_injector.ext.starlette import Lifespan
from fastapi import FastAPI, Request, Depends, APIRouter
class Connection: ...
@asynccontextmanager
async def init_database():
print("opening database connection")
yield Connection()
print("closing database connection")
router = APIRouter()
@router.get("/")
@inject
async def index(request: Request, db: Connection = Depends(Provide["db"])):
# use the database connection here
return "OK!"
class Container(containers.DeclarativeContainer):
__self__ = providers.Self()
db = providers.Resource(init_database)
lifespan = providers.Singleton(Lifespan, __self__)
app = providers.Singleton(FastAPI, lifespan=lifespan)
_include_router = providers.Resource(
app.provided.include_router.call(),
router,
)
if __name__ == "__main__":
import uvicorn
container = Container()
app = container.app()
uvicorn.run(app, host="localhost", port=8000)
.. _Lifespan Protocol: https://asgi.readthedocs.io/en/latest/specs/lifespan.html
.. disqus::

View File

@ -257,7 +257,7 @@ Let's check that it works. Open another terminal session and use ``httpie``:
You should see:
.. code-block:: http
.. code-block:: json
HTTP/1.1 200 OK
Content-Length: 844
@ -596,7 +596,7 @@ and make a request to the API in the terminal:
You should see:
.. code-block:: http
.. code-block:: json
HTTP/1.1 200 OK
Content-Length: 492

View File

@ -84,7 +84,7 @@ Create next structure in the project root directory. All files are empty. That's
Initial project layout:
.. code-block:: text
.. code-block:: bash
./
├── movies/
@ -109,7 +109,7 @@ Now it's time to install the project requirements. We will use next packages:
Put next lines into the ``requirements.txt`` file:
.. code-block:: text
.. code-block:: bash
dependency-injector
pyyaml
@ -134,7 +134,7 @@ We will create a script that creates database files.
First add the folder ``data/`` in the root of the project and then add the file
``fixtures.py`` inside of it:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 2-3
./
@ -205,13 +205,13 @@ Now run in the terminal:
You should see:
.. code-block:: text
.. code-block:: bash
OK
Check that files ``movies.csv`` and ``movies.db`` have appeared in the ``data/`` folder:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 4-5
./
@ -289,7 +289,7 @@ After each step we will add the provider to the container.
Create the ``entities.py`` in the ``movies`` package:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 10
./
@ -356,7 +356,7 @@ Let's move on to the finders.
Create the ``finders.py`` in the ``movies`` package:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 11
./
@ -465,7 +465,7 @@ The configuration file is ready. Move on to the lister.
Create the ``listers.py`` in the ``movies`` package:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 12
./
@ -613,7 +613,7 @@ Run in the terminal:
You should see:
.. code-block:: text
.. code-block:: plain
Francis Lawrence movies:
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
@ -752,7 +752,7 @@ Run in the terminal:
You should see:
.. code-block:: text
.. code-block:: plain
Francis Lawrence movies:
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
@ -868,7 +868,7 @@ Run in the terminal line by line:
The output should be similar for each command:
.. code-block:: text
.. code-block:: plain
Francis Lawrence movies:
- Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')
@ -888,7 +888,7 @@ We will use `pytest <https://docs.pytest.org/en/stable/>`_ and
Create ``tests.py`` in the ``movies`` package:
.. code-block:: text
.. code-block:: bash
:emphasize-lines: 13
./
@ -977,7 +977,7 @@ Run in the terminal:
You should see:
.. code-block:: text
.. 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

View File

@ -280,7 +280,7 @@ Now let's fill in the layout.
Put next into the ``base.html``:
.. code-block:: jinja
.. code-block:: html
<!doctype html>
<html lang="en">
@ -313,7 +313,7 @@ And put something to the index page.
Put next into the ``index.html``:
.. code-block:: jinja
.. code-block:: html
{% extends "base.html" %}

View File

@ -127,7 +127,6 @@ To inject the provider itself use ``Provide[foo.provider]``:
def foo(bar_provider: Factory[Bar] = Provide[Container.bar.provider]):
bar = bar_provider(argument="baz")
...
You can also use ``Provider[foo]`` for injecting the provider itself:
.. code-block:: python
@ -255,43 +254,13 @@ 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. 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:
You can use wiring to make injections into modules and class attributes.
.. 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
@ -632,36 +601,6 @@ or with a single container ``register_loader_containers(container)`` multiple ti
To unregister a container use ``unregister_loader_containers(container)``.
Wiring module will uninstall the import hook when unregister last container.
Few notes on performance
------------------------
``.wire()`` utilize caching to speed up the wiring process. At the end it clears the cache to avoid memory leaks.
But this may not always be desirable, when you want to keep the cache for the next wiring
(e.g. due to usage of multiple containers or during unit tests).
To keep the cache after wiring, you can set flag ``keep_cache=True`` (works with ``WiringConfiguration`` too):
.. code-block:: python
container1.wire(
modules=["yourapp.module1", "yourapp.module2"],
keep_cache=True,
)
container2.wire(
modules=["yourapp.module2", "yourapp.module3"],
keep_cache=True,
)
...
and then clear it manually when you need it:
.. code-block:: python
from dependency_injector.wiring import clear_cache
clear_cache()
Integration with other frameworks
---------------------------------
@ -693,6 +632,5 @@ Take a look at other application examples:
- :ref:`fastapi-example`
- :ref:`fastapi-redis-example`
- :ref:`fastapi-sqlalchemy-example`
- :ref:`fastdepends-example`
.. disqus::

View File

@ -18,9 +18,10 @@ SQLITE_FILE = DIR / "movies.db"
def create_csv(movies_data, path):
with open(path, "w", newline="") as opened_file:
with open(path, "w") as opened_file:
writer = csv.writer(opened_file)
writer.writerows(movies_data)
for row in movies_data:
writer.writerow(row)
def create_sqlite(movies_data, path):

View File

@ -29,7 +29,7 @@ class CsvMovieFinder(MovieFinder):
super().__init__(movie_factory)
def find_all(self) -> List[Movie]:
with open(self._csv_file_path, newline="") as csv_file:
with open(self._csv_file_path) as csv_file:
csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
return [self._movie_factory(*row) for row in csv_reader]

View File

@ -3,12 +3,10 @@
import sys
import logging
from concurrent.futures import ThreadPoolExecutor
from contextlib import contextmanager
from dependency_injector import containers, providers
@contextmanager
def init_thread_pool(max_workers: int):
thread_pool = ThreadPoolExecutor(max_workers=max_workers)
yield thread_pool

View File

@ -1,31 +0,0 @@
"""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)

View File

@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools", "Cython>=3.1.1"]
requires = ["setuptools", "Cython"]
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.8"
requires-python = ">=3.7"
keywords = [
"Dependency injection",
"DI",
@ -31,6 +31,7 @@ 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",
@ -52,11 +53,6 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
]
dynamic = ["version"]
dependencies = [
# typing.Annotated since v3.9
# typing.Self since v3.11
"typing-extensions; python_version<'3.11'",
]
[project.optional-dependencies]
yaml = ["pyyaml"]
@ -96,7 +92,6 @@ show_missing = true
[tool.isort]
profile = "black"
combine_as_imports = true
[tool.pylint.main]
ignore = ["tests"]
@ -104,19 +99,3 @@ 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::dependency_injector.wiring.DIWiringWarning",
"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",
]

View File

@ -1,4 +1,4 @@
cython==3.1.1
cython==3.0.11
setuptools
pytest
pytest-asyncio
@ -13,13 +13,11 @@ mypy
pyyaml
httpx
fastapi
pydantic
pydantic-settings
pydantic==1.10.17
numpy
scipy
boto3
mypy_boto3_s3
typing_extensions
fast-depends
-r requirements-ext.txt

View File

@ -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/*: F821,F841
examples/wiring/*: F841
[pydocstyle]
ignore = D100,D101,D102,D105,D106,D107,D203,D213

View File

@ -1,19 +1,13 @@
"""`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,
@ -23,7 +17,6 @@ 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"),
@ -32,20 +25,14 @@ 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,

View File

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

View File

View File

@ -1,18 +0,0 @@
from typing import Any, Dict
from .providers import Provider
class DependencyResolver:
def __init__(
self,
kwargs: Dict[str, Any],
injections: Dict[str, Provider[Any]],
closings: Dict[str, Provider[Any]],
/,
) -> None: ...
def __enter__(self) -> Dict[str, Any]: ...
def __exit__(self, *exc_info: Any) -> None: ...
async def __aenter__(self) -> Dict[str, Any]: ...
async def __aexit__(self, *exc_info: Any) -> None: ...
def _isawaitable(instance: Any) -> bool: ...

View File

@ -1,93 +1,83 @@
"""Wiring optimizations module."""
from asyncio import gather
from collections.abc import Awaitable
from inspect import CO_ITERABLE_COROUTINE
from types import CoroutineType, GeneratorType
import asyncio
import collections.abc
import inspect
import types
from .providers cimport Provider, Resource
from .wiring import _Marker
cdef inline bint _is_injectable(dict kwargs, object name):
return name not in kwargs or isinstance(kwargs[name], _Marker)
from .providers cimport Provider, Resource
cdef class DependencyResolver:
cdef dict kwargs
def _sync_inject(object fn, tuple args, dict kwargs, dict injections, dict closings, /):
cdef object result
cdef dict to_inject
cdef dict injections
cdef dict closings
cdef object arg_key
cdef Provider provider
def __init__(self, dict kwargs, dict injections, dict closings, /):
self.kwargs = kwargs
self.to_inject = kwargs.copy()
self.injections = injections
self.closings = closings
to_inject = kwargs.copy()
for arg_key, provider in injections.items():
if arg_key not in kwargs or isinstance(kwargs[arg_key], _Marker):
to_inject[arg_key] = provider()
async def _await_injection(self, name: str, value: object, /) -> None:
self.to_inject[name] = await value
result = fn(*args, **to_inject)
cdef void _handle_injections_sync(self):
cdef Provider provider
if closings:
for arg_key, provider in closings.items():
if arg_key in kwargs and not isinstance(kwargs[arg_key], _Marker):
continue
if not isinstance(provider, Resource):
continue
provider.shutdown()
for name, provider in self.injections.items():
if _is_injectable(self.kwargs, name):
self.to_inject[name] = provider()
return result
cdef list _handle_injections_async(self):
cdef list to_await = []
cdef Provider provider
for name, provider in self.injections.items():
if _is_injectable(self.kwargs, name):
provide = provider()
async def _async_inject(object fn, tuple args, dict kwargs, dict injections, dict closings, /):
cdef object result
cdef dict to_inject
cdef list to_inject_await = []
cdef list to_close_await = []
cdef object arg_key
cdef Provider provider
if provider.is_async_mode_enabled() or _isawaitable(provide):
to_await.append(self._await_injection(name, provide))
else:
self.to_inject[name] = provide
to_inject = kwargs.copy()
for arg_key, provider in injections.items():
if arg_key not in kwargs or isinstance(kwargs[arg_key], _Marker):
provide = provider()
if provider.is_async_mode_enabled():
to_inject_await.append((arg_key, provide))
elif _isawaitable(provide):
to_inject_await.append((arg_key, provide))
else:
to_inject[arg_key] = provide
return to_await
if to_inject_await:
async_to_inject = await asyncio.gather(*(provide for _, provide in to_inject_await))
for provide, (injection, _) in zip(async_to_inject, to_inject_await):
to_inject[injection] = provide
cdef void _handle_closings_sync(self):
cdef Provider provider
result = await fn(*args, **to_inject)
for name, provider in self.closings.items():
if _is_injectable(self.kwargs, name) and isinstance(provider, Resource):
provider.shutdown()
if closings:
for arg_key, provider in closings.items():
if arg_key in kwargs and isinstance(kwargs[arg_key], _Marker):
continue
if not isinstance(provider, Resource):
continue
shutdown = provider.shutdown()
if _isawaitable(shutdown):
to_close_await.append(shutdown)
cdef list _handle_closings_async(self):
cdef list to_await = []
cdef Provider provider
await asyncio.gather(*to_close_await)
for name, provider in self.closings.items():
if _is_injectable(self.kwargs, name) and isinstance(provider, Resource):
if _isawaitable(shutdown := provider.shutdown()):
to_await.append(shutdown)
return to_await
def __enter__(self):
self._handle_injections_sync()
return self.to_inject
def __exit__(self, *_):
self._handle_closings_sync()
async def __aenter__(self):
if to_await := self._handle_injections_async():
await gather(*to_await)
return self.to_inject
async def __aexit__(self, *_):
if to_await := self._handle_closings_async():
await gather(*to_await)
return result
cdef bint _isawaitable(object instance):
"""Return true if object can be passed to an ``await`` expression."""
return (isinstance(instance, CoroutineType) or
isinstance(instance, GeneratorType) and
bool(instance.gi_code.co_flags & CO_ITERABLE_COROUTINE) or
isinstance(instance, Awaitable))
return (isinstance(instance, types.CoroutineType) or
isinstance(instance, types.GeneratorType) and
bool(instance.gi_code.co_flags & inspect.CO_ITERABLE_COROUTINE) or
isinstance(instance, collections.abc.Awaitable))

View File

@ -1,28 +1,23 @@
from pathlib import Path
from typing import (
Any,
Awaitable,
Callable as _Callable,
ClassVar,
Dict,
Generic,
Type,
Dict,
List,
Tuple,
Optional,
Any,
Union,
ClassVar,
Callable as _Callable,
Iterable,
Iterator,
List,
Optional,
Tuple,
Type,
TypeVar,
Union,
Awaitable,
overload,
)
try:
from typing import Self as _Self
except ImportError:
from typing_extensions import Self as _Self
from .providers import Provider, Resource, Self, ProviderParent
from .providers import Provider, Self, ProviderParent
C_Base = TypeVar("C_Base", bound="Container")
C = TypeVar("C", bound="DeclarativeContainer")
@ -35,34 +30,32 @@ class WiringConfiguration:
packages: List[Any]
from_package: Optional[str]
auto_wire: bool
keep_cache: bool
def __init__(
self,
modules: Optional[Iterable[Any]] = None,
packages: Optional[Iterable[Any]] = None,
from_package: Optional[str] = None,
auto_wire: bool = True,
keep_cache: bool = False,
) -> None: ...
class Container:
provider_type: Type[Provider[Any]] = Provider
providers: Dict[str, Provider[Any]]
dependencies: Dict[str, Provider[Any]]
overridden: Tuple[Provider[Any], ...]
provider_type: Type[Provider] = Provider
providers: Dict[str, Provider]
dependencies: Dict[str, Provider]
overridden: Tuple[Provider]
wiring_config: WiringConfiguration
auto_load_config: bool = True
__self__: Self
def __init__(self) -> None: ...
def __deepcopy__(self, memo: Optional[Dict[str, Any]]) -> _Self: ...
def __setattr__(self, name: str, value: Union[Provider[Any], Any]) -> None: ...
def __getattr__(self, name: str) -> Provider[Any]: ...
def __deepcopy__(self, memo: Optional[Dict[str, Any]]) -> Provider: ...
def __setattr__(self, name: str, value: Union[Provider, Any]) -> None: ...
def __getattr__(self, name: str) -> Provider: ...
def __delattr__(self, name: str) -> None: ...
def set_providers(self, **providers: Provider[Any]) -> None: ...
def set_provider(self, name: str, provider: Provider[Any]) -> None: ...
def set_providers(self, **providers: Provider): ...
def set_provider(self, name: str, provider: Provider) -> None: ...
def override(self, overriding: Union[Container, Type[Container]]) -> None: ...
def override_providers(
self, **overriding_providers: Union[Provider[Any], Any]
self, **overriding_providers: Union[Provider, Any]
) -> ProvidersOverridingContext[C_Base]: ...
def reset_last_overriding(self) -> None: ...
def reset_override(self) -> None: ...
@ -74,8 +67,8 @@ class Container:
from_package: Optional[str] = None,
) -> None: ...
def unwire(self) -> None: ...
def init_resources(self, resource_type: Type[Resource[Any]] = Resource) -> Optional[Awaitable[None]]: ...
def shutdown_resources(self, resource_type: Type[Resource[Any]] = Resource) -> Optional[Awaitable[None]]: ...
def init_resources(self) -> Optional[Awaitable]: ...
def shutdown_resources(self) -> Optional[Awaitable]: ...
def load_config(self) -> None: ...
def apply_container_providers_overridings(self) -> None: ...
def reset_singletons(self) -> SingletonResetContext[C_Base]: ...
@ -86,10 +79,10 @@ class Container:
) -> None: ...
def from_json_schema(self, filepath: Union[Path, str]) -> None: ...
@overload
def resolve_provider_name(self, provider: Provider[Any]) -> str: ...
def resolve_provider_name(self, provider: Provider) -> str: ...
@classmethod
@overload
def resolve_provider_name(cls, provider: Provider[Any]) -> str: ...
def resolve_provider_name(cls, provider: Provider) -> str: ...
@property
def parent(self) -> Optional[ProviderParent]: ...
@property
@ -104,14 +97,14 @@ class Container:
class DynamicContainer(Container): ...
class DeclarativeContainer(Container):
cls_providers: ClassVar[Dict[str, Provider[Any]]]
inherited_providers: ClassVar[Dict[str, Provider[Any]]]
def __init__(self, **overriding_providers: Union[Provider[Any], Any]) -> None: ...
cls_providers: ClassVar[Dict[str, Provider]]
inherited_providers: ClassVar[Dict[str, Provider]]
def __init__(self, **overriding_providers: Union[Provider, Any]) -> None: ...
@classmethod
def override(cls, overriding: Union[Container, Type[Container]]) -> None: ...
@classmethod
def override_providers(
cls, **overriding_providers: Union[Provider[Any], Any]
cls, **overriding_providers: Union[Provider, Any]
) -> ProvidersOverridingContext[C_Base]: ...
@classmethod
def reset_last_overriding(cls) -> None: ...
@ -120,7 +113,7 @@ class DeclarativeContainer(Container):
class ProvidersOverridingContext(Generic[T]):
def __init__(
self, container: T, overridden_providers: Iterable[Union[Provider[Any], Any]]
self, container: T, overridden_providers: Iterable[Union[Provider, Any]]
) -> None: ...
def __enter__(self) -> T: ...
def __exit__(self, *_: Any) -> None: ...

View File

@ -1,11 +1,17 @@
"""Containers module."""
import asyncio
import contextlib
import copy as copy_module
import json
import sys
import importlib
import inspect
import warnings
try:
import asyncio
except ImportError:
asyncio = None
try:
import yaml
@ -14,24 +20,40 @@ except ImportError:
from . import providers, errors
from .providers cimport __is_future_or_coroutine
from .wiring import wire, unwire
if sys.version_info[:2] >= (3, 6):
from .wiring import wire, unwire
else:
def wire(*args, **kwargs):
raise NotImplementedError("Wiring requires Python 3.6 or above")
def unwire(*args, **kwargs):
raise NotImplementedError("Wiring requires Python 3.6 or above")
if sys.version_info[:2] == (3, 5):
warnings.warn(
"Dependency Injector will drop support of Python 3.5 after Jan 1st of 2022. "
"This does not mean that there will be any immediate breaking changes, "
"but tests will no longer be executed on Python 3.5, and bugs will not be addressed.",
category=DeprecationWarning,
)
class WiringConfiguration:
"""Container wiring configuration."""
def __init__(self, modules=None, packages=None, from_package=None, auto_wire=True, keep_cache=False):
def __init__(self, modules=None, packages=None, from_package=None, auto_wire=True):
self.modules = [*modules] if modules else []
self.packages = [*packages] if packages else []
self.from_package = from_package
self.auto_wire = auto_wire
self.keep_cache = keep_cache
def __deepcopy__(self, memo=None):
return self.__class__(self.modules, self.packages, self.from_package, self.auto_wire, self.keep_cache)
return self.__class__(self.modules, self.packages, self.from_package, self.auto_wire)
class Container:
class Container(object):
"""Abstract container."""
@ -259,7 +281,7 @@ class DynamicContainer(Container):
"""Check if auto wiring is needed."""
return self.wiring_config.auto_wire is True
def wire(self, modules=None, packages=None, from_package=None, keep_cache=None):
def wire(self, modules=None, packages=None, from_package=None):
"""Wire container providers with provided packages and modules.
:rtype: None
@ -290,14 +312,10 @@ class DynamicContainer(Container):
if not modules and not packages:
return
if keep_cache is None:
keep_cache = self.wiring_config.keep_cache
wire(
container=self,
modules=modules,
packages=packages,
keep_cache=keep_cache,
)
if modules:
@ -315,15 +333,11 @@ class DynamicContainer(Container):
self.wired_to_modules.clear()
self.wired_to_packages.clear()
def init_resources(self, resource_type=providers.Resource):
def init_resources(self):
"""Initialize all container resources."""
if not issubclass(resource_type, providers.Resource):
raise TypeError("resource_type must be a subclass of Resource provider")
futures = []
for provider in self.traverse(types=[resource_type]):
for provider in self.traverse(types=[providers.Resource]):
resource = provider.init()
if __is_future_or_coroutine(resource):
@ -332,12 +346,8 @@ class DynamicContainer(Container):
if futures:
return asyncio.gather(*futures)
def shutdown_resources(self, resource_type=providers.Resource):
def shutdown_resources(self):
"""Shutdown all container resources."""
if not issubclass(resource_type, providers.Resource):
raise TypeError("resource_type must be a subclass of Resource provider")
def _independent_resources(resources):
for resource in resources:
for other_resource in resources:
@ -368,7 +378,7 @@ class DynamicContainer(Container):
for resource in resources_to_shutdown:
resource.shutdown()
resources = list(self.traverse(types=[resource_type]))
resources = list(self.traverse(types=[providers.Resource]))
if any(resource.is_async_mode_enabled() for resource in resources):
return _async_ordered_shutdown(resources)
else:

View File

@ -7,6 +7,7 @@ import warnings
from dependency_injector import providers
warnings.warn(
'Module "dependency_injector.ext.aiohttp" is deprecated since '
'version 4.0.0. Use "dependency_injector.wiring" module instead.',

View File

@ -1,16 +1,14 @@
from typing import Any, Awaitable as _Awaitable, TypeVar
from typing import Awaitable as _Awaitable
from dependency_injector import providers
T = TypeVar("T")
class Application(providers.Singleton): ...
class Extension(providers.Singleton): ...
class Middleware(providers.DelegatedCallable): ...
class MiddlewareFactory(providers.Factory): ...
class Application(providers.Singleton[T]): ...
class Extension(providers.Singleton[T]): ...
class Middleware(providers.DelegatedCallable[T]): ...
class MiddlewareFactory(providers.Factory[T]): ...
class View(providers.Callable):
def as_view(self) -> _Awaitable: ...
class View(providers.Callable[T]):
def as_view(self) -> _Awaitable[T]: ...
class ClassBasedView(providers.Factory[T]):
def as_view(self) -> _Awaitable[T]: ...
class ClassBasedView(providers.Factory):
def as_view(self) -> _Awaitable: ...

View File

@ -1,12 +1,12 @@
"""Flask extension module."""
from __future__ import absolute_import
import warnings
from flask import request as flask_request
from dependency_injector import errors, providers
from dependency_injector import providers, errors
warnings.warn(
'Module "dependency_injector.ext.flask" is deprecated since '

View File

@ -1,21 +1,19 @@
from typing import Any, Callable as _Callable, Optional, TypeVar, Union
from flask.wrappers import Request
from typing import Union, Optional, Callable as _Callable, Any
from flask import request as flask_request
from dependency_injector import providers
request: providers.Object[Request]
T = TypeVar("T")
request: providers.Object[flask_request]
class Application(providers.Singleton[T]): ...
class Extension(providers.Singleton[T]): ...
class Application(providers.Singleton): ...
class Extension(providers.Singleton): ...
class View(providers.Callable[T]):
def as_view(self) -> _Callable[..., T]: ...
class View(providers.Callable):
def as_view(self) -> _Callable[..., Any]: ...
class ClassBasedView(providers.Factory[T]):
def as_view(self, name: str) -> _Callable[..., T]: ...
class ClassBasedView(providers.Factory):
def as_view(self, name: str) -> _Callable[..., Any]: ...
def as_view(
provider: Union[View[T], ClassBasedView[T]], name: Optional[str] = None
) -> _Callable[..., T]: ...
provider: Union[View, ClassBasedView], name: Optional[str] = None
) -> _Callable[..., Any]: ...

View File

@ -1,5 +1,5 @@
import sys
from typing import Any, Type
from typing import Any
if sys.version_info >= (3, 11): # pragma: no cover
from typing import Self
@ -7,7 +7,6 @@ else: # pragma: no cover
from typing_extensions import Self
from dependency_injector.containers import Container
from dependency_injector.providers import Resource
class Lifespan:
@ -30,32 +29,24 @@ class Lifespan:
app = Factory(Starlette, lifespan=lifespan)
:param container: container instance
:param resource_type: A :py:class:`~dependency_injector.resources.Resource`
subclass. Limits the resources to be initialized and shutdown.
"""
container: Container
resource_type: Type[Resource[Any]]
def __init__(
self,
container: Container,
resource_type: Type[Resource[Any]] = Resource,
) -> None:
def __init__(self, container: Container) -> None:
self.container = container
self.resource_type = resource_type
def __call__(self, app: Any) -> Self:
return self
async def __aenter__(self) -> None:
result = self.container.init_resources(self.resource_type)
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(self.resource_type)
result = self.container.shutdown_resources()
if result is not None:
await result

View File

@ -1,6 +1,10 @@
"""Providers module."""
import asyncio
try:
import asyncio
except ImportError:
asyncio = None
import functools
cimport cython
@ -15,7 +19,7 @@ cdef tuple __COROUTINE_TYPES
# Base providers
cdef class Provider:
cdef class Provider(object):
cdef tuple _overridden
cdef Provider _last_overriding
cdef tuple _overrides
@ -287,7 +291,7 @@ cdef class MethodCaller(Provider):
# Injections
cdef class Injection:
cdef class Injection(object):
cdef object _value
cdef int _is_provider
cdef int _is_delegated
@ -309,12 +313,12 @@ cpdef tuple parse_named_injections(dict kwargs)
# Utils
cdef class OverridingContext:
cdef class OverridingContext(object):
cdef Provider _overridden
cdef Provider _overriding
cdef class BaseSingletonResetContext:
cdef class BaseSingletonResetContext(object):
cdef object _singleton
@ -697,10 +701,3 @@ cdef inline object __future_result(object instance):
future_result = asyncio.Future()
future_result.set_result(instance)
return future_result
cdef class NullAwaitable:
pass
cdef NullAwaitable NULL_AWAITABLE

View File

@ -3,7 +3,6 @@
from __future__ import absolute_import
import asyncio
import builtins
import copy
import errno
import functools
@ -14,12 +13,20 @@ import os
import re
import sys
import threading
import types
import warnings
from asyncio import ensure_future
from configparser import ConfigParser as IniConfigParser
from contextlib import asynccontextmanager, contextmanager
from contextvars import ContextVar
from inspect import isasyncgenfunction, isgeneratorfunction
try:
import contextvars
except ImportError:
contextvars = None
try:
import builtins
except ImportError:
# Python 2.7
import __builtin__ as builtins
try:
from inspect import _is_coroutine_mark as _is_coroutine_marker
@ -69,6 +76,24 @@ from .errors import (
cimport cython
if sys.version_info[0] == 3: # pragma: no cover
CLASS_TYPES = (type,)
else: # pragma: no cover
CLASS_TYPES = (type, types.ClassType)
copy._deepcopy_dispatch[types.MethodType] = \
lambda obj, memo: type(obj)(obj.im_func,
copy.deepcopy(obj.im_self, memo),
obj.im_class)
if sys.version_info[:2] == (3, 5):
warnings.warn(
"Dependency Injector will drop support of Python 3.5 after Jan 1st of 2022. "
"This does not mean that there will be any immediate breaking changes, "
"but tests will no longer be executed on Python 3.5, and bugs will not be addressed.",
category=DeprecationWarning,
)
config_env_marker_pattern = re.compile(
r"\${(?P<name>[^}^{:]+)(?P<separator>:?)(?P<default>.*?)}",
)
@ -128,7 +153,7 @@ cdef int ASYNC_MODE_ENABLED = 1
cdef int ASYNC_MODE_DISABLED = 2
cdef set __iscoroutine_typecache = set()
cdef tuple __COROUTINE_TYPES = asyncio.coroutines._COROUTINE_TYPES
cdef tuple __COROUTINE_TYPES = asyncio.coroutines._COROUTINE_TYPES if asyncio else tuple()
cdef dict pydantic_settings_to_dict(settings, dict kwargs):
if not has_pydantic_settings:
@ -138,7 +163,7 @@ cdef dict pydantic_settings_to_dict(settings, dict kwargs):
f"\"pip install dependency-injector[{pydantic_extra}]\""
)
if isinstance(settings, type) and issubclass(settings, PydanticSettings):
if isinstance(settings, CLASS_TYPES) and issubclass(settings, PydanticSettings):
raise Error(
"Got settings class, but expect instance: "
"instead \"{0}\" use \"{0}()\"".format(settings.__name__)
@ -156,7 +181,7 @@ cdef dict pydantic_settings_to_dict(settings, dict kwargs):
return settings.model_dump(mode="python", **kwargs)
cdef class Provider:
cdef class Provider(object):
"""Base provider class.
:py:class:`Provider` is callable (implements ``__call__`` method). Every
@ -878,9 +903,12 @@ cdef class Dependency(Provider):
def set_instance_of(self, instance_of):
"""Set type."""
if not isinstance(instance_of, type):
if not isinstance(instance_of, CLASS_TYPES):
raise TypeError(
f"\"instance_of\" is not a class (got {instance_of!r}))",
"\"instance_of\" has incorrect type (expected {0}, got {1}))".format(
CLASS_TYPES,
instance_of,
),
)
self._instance_of = instance_of
return self
@ -1442,6 +1470,8 @@ cdef class Coroutine(Callable):
def set_provides(self, provides):
"""Set provider provides."""
if not asyncio:
raise Error("Package asyncio is not available")
provides = _resolve_string_import(provides)
if provides and not asyncio.iscoroutinefunction(provides):
raise Error(f"Provider {_class_qualname(self)} expected to get coroutine function, "
@ -1595,7 +1625,8 @@ cdef class ConfigurationOption(Provider):
segment() if is_provider(segment) else segment for segment in self._name
)
def _get_root(self):
@property
def root(self):
return self._root
def get_name(self):
@ -3226,10 +3257,15 @@ 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 = ContextVar("_storage", default=self._none)
self._storage = contextvars.ContextVar("_storage", default=self._none)
def reset(self):
"""Reset cached instance, if any.
@ -3601,17 +3637,6 @@ cdef class Dict(Provider):
return __provide_keyword_args(kwargs, self._kwargs, self._kwargs_len, self._async_mode)
@cython.no_gc
cdef class NullAwaitable:
def __next__(self):
raise StopIteration from None
def __await__(self):
return self
cdef NullAwaitable NULL_AWAITABLE = NullAwaitable()
cdef class Resource(Provider):
"""Resource provider provides a component with initialization and shutdown."""
@ -3667,12 +3692,6 @@ cdef class Resource(Provider):
def set_provides(self, provides):
"""Set provider provides."""
provides = _resolve_string_import(provides)
if isasyncgenfunction(provides):
provides = asynccontextmanager(provides)
elif isgeneratorfunction(provides):
provides = contextmanager(provides)
self._provides = provides
return self
@ -3773,21 +3792,28 @@ cdef class Resource(Provider):
"""Shutdown resource."""
if not self._initialized:
if self._async_mode == ASYNC_MODE_ENABLED:
return NULL_AWAITABLE
result = asyncio.Future()
result.set_result(None)
return result
return
if self._shutdowner:
future = self._shutdowner(None, None, None)
if __is_future_or_coroutine(future):
return ensure_future(self._shutdown_async(future))
try:
shutdown = self._shutdowner(self._resource)
except StopIteration:
pass
else:
if inspect.isawaitable(shutdown):
return self._create_shutdown_future(shutdown)
self._resource = None
self._initialized = False
self._shutdowner = None
if self._async_mode == ASYNC_MODE_ENABLED:
return NULL_AWAITABLE
result = asyncio.Future()
result.set_result(None)
return result
@property
def related(self):
@ -3797,75 +3823,169 @@ cdef class Resource(Provider):
yield from filter(is_provider, self.kwargs.values())
yield from super().related
async def _shutdown_async(self, future) -> None:
try:
await future
finally:
self._resource = None
self._initialized = False
self._shutdowner = None
async def _handle_async_cm(self, obj) -> None:
try:
self._resource = resource = await obj.__aenter__()
self._shutdowner = obj.__aexit__
return resource
except:
self._initialized = False
raise
async def _provide_async(self, future) -> None:
try:
obj = await future
if hasattr(obj, '__aenter__') and hasattr(obj, '__aexit__'):
self._resource = await obj.__aenter__()
self._shutdowner = obj.__aexit__
elif hasattr(obj, '__enter__') and hasattr(obj, '__exit__'):
self._resource = obj.__enter__()
self._shutdowner = obj.__exit__
else:
self._resource = obj
self._shutdowner = None
return self._resource
except:
self._initialized = False
raise
cpdef object _provide(self, tuple args, dict kwargs):
if self._initialized:
return self._resource
obj = __call(
self._provides,
args,
self._args,
self._args_len,
kwargs,
self._kwargs,
self._kwargs_len,
self._async_mode,
)
if __is_future_or_coroutine(obj):
if self._is_resource_subclass(self._provides):
initializer = self._provides()
self._resource = __call(
initializer.init,
args,
self._args,
self._args_len,
kwargs,
self._kwargs,
self._kwargs_len,
self._async_mode,
)
self._shutdowner = initializer.shutdown
elif self._is_async_resource_subclass(self._provides):
initializer = self._provides()
async_init = __call(
initializer.init,
args,
self._args,
self._args_len,
kwargs,
self._kwargs,
self._kwargs_len,
self._async_mode,
)
self._initialized = True
self._resource = resource = ensure_future(self._provide_async(obj))
return resource
elif hasattr(obj, '__enter__') and hasattr(obj, '__exit__'):
self._resource = obj.__enter__()
self._shutdowner = obj.__exit__
elif hasattr(obj, '__aenter__') and hasattr(obj, '__aexit__'):
return self._create_init_future(async_init, initializer.shutdown)
elif inspect.isgeneratorfunction(self._provides):
initializer = __call(
self._provides,
args,
self._args,
self._args_len,
kwargs,
self._kwargs,
self._kwargs_len,
self._async_mode,
)
self._resource = next(initializer)
self._shutdowner = initializer.send
elif iscoroutinefunction(self._provides):
initializer = __call(
self._provides,
args,
self._args,
self._args_len,
kwargs,
self._kwargs,
self._kwargs_len,
self._async_mode,
)
self._initialized = True
self._resource = resource = ensure_future(self._handle_async_cm(obj))
return resource
return self._create_init_future(initializer)
elif isasyncgenfunction(self._provides):
initializer = __call(
self._provides,
args,
self._args,
self._args_len,
kwargs,
self._kwargs,
self._kwargs_len,
self._async_mode,
)
self._initialized = True
return self._create_async_gen_init_future(initializer)
elif callable(self._provides):
self._resource = __call(
self._provides,
args,
self._args,
self._args_len,
kwargs,
self._kwargs,
self._kwargs_len,
self._async_mode,
)
else:
self._resource = obj
self._shutdowner = None
raise Error("Unknown type of resource initializer")
self._initialized = True
return self._resource
def _create_init_future(self, future, shutdowner=None):
callback = self._async_init_callback
if shutdowner:
callback = functools.partial(callback, shutdowner=shutdowner)
future = asyncio.ensure_future(future)
future.add_done_callback(callback)
self._resource = future
return future
def _create_async_gen_init_future(self, initializer):
if inspect.isasyncgen(initializer):
return self._create_init_future(initializer.__anext__(), initializer.asend)
future = asyncio.Future()
create_initializer = asyncio.ensure_future(initializer)
create_initializer.add_done_callback(functools.partial(self._async_create_gen_callback, future))
self._resource = future
return future
def _async_init_callback(self, initializer, shutdowner=None):
try:
resource = initializer.result()
except Exception:
self._initialized = False
else:
self._resource = resource
self._shutdowner = shutdowner
def _async_create_gen_callback(self, future, initializer_future):
initializer = initializer_future.result()
init_future = self._create_init_future(initializer.__anext__(), initializer.asend)
init_future.add_done_callback(functools.partial(self._async_trigger_result, future))
def _async_trigger_result(self, future, future_result):
future.set_result(future_result.result())
def _create_shutdown_future(self, shutdown_future):
future = asyncio.Future()
shutdown_future = asyncio.ensure_future(shutdown_future)
shutdown_future.add_done_callback(functools.partial(self._async_shutdown_callback, future))
return future
def _async_shutdown_callback(self, future_result, shutdowner):
try:
shutdowner.result()
except StopAsyncIteration:
pass
self._resource = None
self._initialized = False
self._shutdowner = None
future_result.set_result(None)
@staticmethod
def _is_resource_subclass(instance):
if sys.version_info < (3, 5):
return False
if not isinstance(instance, CLASS_TYPES):
return
from . import resources
return issubclass(instance, resources.Resource)
@staticmethod
def _is_async_resource_subclass(instance):
if sys.version_info < (3, 5):
return False
if not isinstance(instance, CLASS_TYPES):
return
from . import resources
return issubclass(instance, resources.AsyncResource)
cdef class Container(Provider):
"""Container provider provides an instance of declarative container.
@ -4519,7 +4639,7 @@ cdef class MethodCaller(Provider):
future_result.set_result(result)
cdef class Injection:
cdef class Injection(object):
"""Abstract injection class."""
@ -4646,7 +4766,7 @@ cpdef tuple parse_named_injections(dict kwargs):
return tuple(injections)
cdef class OverridingContext:
cdef class OverridingContext(object):
"""Provider overriding context.
:py:class:`OverridingContext` is used by :py:meth:`Provider.override` for
@ -4682,7 +4802,7 @@ cdef class OverridingContext:
self._overridden.reset_last_overriding()
cdef class BaseSingletonResetContext:
cdef class BaseSingletonResetContext(object):
def __init__(self, Provider provider):
self._singleton = provider
@ -4718,7 +4838,7 @@ cpdef bint is_provider(object instance):
:rtype: bool
"""
return (not isinstance(instance, type) and
return (not isinstance(instance, CLASS_TYPES) and
getattr(instance, "__IS_PROVIDER__", False) is True)
@ -4746,7 +4866,7 @@ cpdef bint is_delegated(object instance):
:rtype: bool
"""
return (not isinstance(instance, type) and
return (not isinstance(instance, CLASS_TYPES) and
getattr(instance, "__IS_DELEGATED__", False) is True)
@ -4777,7 +4897,7 @@ cpdef bint is_container_instance(object instance):
:rtype: bool
"""
return (not isinstance(instance, type) and
return (not isinstance(instance, CLASS_TYPES) and
getattr(instance, "__IS_CONTAINER__", False) is True)
@ -4789,7 +4909,7 @@ cpdef bint is_container_class(object instance):
:rtype: bool
"""
return (isinstance(instance, type) and
return (isinstance(instance, CLASS_TYPES) and
getattr(instance, "__IS_CONTAINER__", False) is True)
@ -4916,6 +5036,14 @@ def iscoroutinefunction(obj):
return False
def isasyncgenfunction(obj):
"""Check if object is an asynchronous generator function."""
try:
return inspect.isasyncgenfunction(obj)
except AttributeError:
return False
def _resolve_string_import(provides):
if provides is None:
return provides

View File

@ -1,54 +1,23 @@
"""Resources module."""
from abc import ABCMeta, abstractmethod
from typing import Any, ClassVar, Generic, Optional, Tuple, TypeVar
import abc
from typing import TypeVar, Generic, Optional
T = TypeVar("T")
class Resource(Generic[T], metaclass=ABCMeta):
__slots__: ClassVar[Tuple[str, ...]] = ("args", "kwargs", "obj")
class Resource(Generic[T], metaclass=abc.ABCMeta):
obj: Optional[T]
def __init__(self, *args: Any, **kwargs: Any) -> None:
self.args = args
self.kwargs = kwargs
self.obj = None
@abstractmethod
def init(self, *args: Any, **kwargs: Any) -> Optional[T]: ...
@abc.abstractmethod
def init(self, *args, **kwargs) -> Optional[T]: ...
def shutdown(self, resource: Optional[T]) -> None: ...
def __enter__(self) -> Optional[T]:
self.obj = obj = self.init(*self.args, **self.kwargs)
return obj
def __exit__(self, *exc_info: Any) -> None:
self.shutdown(self.obj)
self.obj = None
class AsyncResource(Generic[T], metaclass=abc.ABCMeta):
class AsyncResource(Generic[T], metaclass=ABCMeta):
__slots__: ClassVar[Tuple[str, ...]] = ("args", "kwargs", "obj")
obj: Optional[T]
def __init__(self, *args: Any, **kwargs: Any) -> None:
self.args = args
self.kwargs = kwargs
self.obj = None
@abstractmethod
async def init(self, *args: Any, **kwargs: Any) -> Optional[T]: ...
@abc.abstractmethod
async def init(self, *args, **kwargs) -> Optional[T]: ...
async def shutdown(self, resource: Optional[T]) -> None: ...
async def __aenter__(self) -> Optional[T]:
self.obj = obj = await self.init(*self.args, **self.kwargs)
return obj
async def __aexit__(self, *exc_info: Any) -> None:
await self.shutdown(self.obj)
self.obj = None

View File

@ -6,20 +6,16 @@ import importlib.machinery
import inspect
import pkgutil
import sys
from contextlib import suppress
from inspect import isbuiltin, isclass
import warnings
from types import ModuleType
from typing import (
TYPE_CHECKING,
Any,
AsyncIterator,
Callable,
Dict,
Generic,
Iterable,
Iterator,
List,
Optional,
Protocol,
Set,
Tuple,
Type,
@ -27,19 +23,13 @@ from typing import (
Union,
cast,
)
from warnings import warn
try:
from typing import Self
except ImportError:
from typing_extensions import Self
if sys.version_info < (3, 7):
from typing import GenericMeta
else:
try:
from functools import cache
except ImportError:
from functools import lru_cache
class GenericMeta(type): ...
cache = lru_cache(maxsize=None)
# Hotfix, see: https://github.com/ets-labs/python-dependency-injector/issues/362
if sys.version_info >= (3, 9):
@ -63,48 +53,33 @@ else:
return None
MARKER_EXTRACTORS: List[Callable[[Any], Any]] = []
INSPECT_EXCLUSION_FILTERS: List[Callable[[Any], bool]] = [isbuiltin]
with suppress(ImportError):
from fastapi.params import Depends as FastAPIDepends
def extract_marker_from_fastapi(param: Any) -> Any:
if isinstance(param, FastAPIDepends):
return param.dependency
return None
MARKER_EXTRACTORS.append(extract_marker_from_fastapi)
with suppress(ImportError):
from fast_depends.dependencies import Depends as FastDepends
def extract_marker_from_fast_depends(param: Any) -> Any:
if isinstance(param, FastDepends):
return param.dependency
return None
MARKER_EXTRACTORS.append(extract_marker_from_fast_depends)
try:
import fastapi.params
except ImportError:
fastapi = None
with suppress(ImportError):
from starlette.requests import Request as StarletteRequest
def is_starlette_request_cls(obj: Any) -> bool:
return isclass(obj) and _safe_is_subclass(obj, StarletteRequest)
INSPECT_EXCLUSION_FILTERS.append(is_starlette_request_cls)
try:
import starlette.requests
except ImportError:
starlette = None
with suppress(ImportError):
from werkzeug.local import LocalProxy as WerkzeugLocalProxy
try:
import werkzeug.local
except ImportError:
werkzeug = None
def is_werkzeug_local_proxy(obj: Any) -> bool:
return isinstance(obj, WerkzeugLocalProxy)
INSPECT_EXCLUSION_FILTERS.append(is_werkzeug_local_proxy)
from . import providers
from . import providers # noqa: E402
if sys.version_info[:2] == (3, 5):
warnings.warn(
"Dependency Injector will drop support of Python 3.5 after Jan 1st of 2022. "
"This does not mean that there will be any immediate breaking changes, "
"but tests will no longer be executed on Python 3.5, and bugs will not be addressed.",
category=DeprecationWarning,
)
__all__ = (
"wire",
@ -128,15 +103,7 @@ __all__ = (
T = TypeVar("T")
F = TypeVar("F", bound=Callable[..., Any])
if TYPE_CHECKING:
from .containers import Container
else:
Container = Any
class DIWiringWarning(RuntimeWarning):
"""Base class for all warnings raised by the wiring module."""
Container = Any
class PatchedRegistry:
@ -361,7 +328,7 @@ class ProvidersMap:
original: providers.ConfigurationOption,
as_: Any = None,
) -> Optional[providers.Provider]:
original_root = original._get_root()
original_root = original.root
new = self._resolve_provider(original_root)
if new is None:
return None
@ -420,11 +387,30 @@ class ProvidersMap:
return providers_map
def is_excluded_from_inspect(obj: Any) -> bool:
for is_excluded in INSPECT_EXCLUSION_FILTERS:
if is_excluded(obj):
class InspectFilter:
def is_excluded(self, instance: object) -> bool:
if self._is_werkzeug_local_proxy(instance):
return True
return False
elif self._is_starlette_request_cls(instance):
return True
elif self._is_builtin(instance):
return True
else:
return False
def _is_werkzeug_local_proxy(self, instance: object) -> bool:
return werkzeug and isinstance(instance, werkzeug.local.LocalProxy)
def _is_starlette_request_cls(self, instance: object) -> bool:
return (
starlette
and isinstance(instance, type)
and _safe_is_subclass(instance, starlette.requests.Request)
)
def _is_builtin(self, instance: object) -> bool:
return inspect.isbuiltin(instance)
def wire( # noqa: C901
@ -432,7 +418,6 @@ def wire( # noqa: C901
*,
modules: Optional[Iterable[ModuleType]] = None,
packages: Optional[Iterable[ModuleType]] = None,
keep_cache: bool = False,
) -> None:
"""Wire container providers with provided packages and modules."""
modules = [*modules] if modules else []
@ -444,8 +429,8 @@ def wire( # noqa: C901
providers_map = ProvidersMap(container)
for module in modules:
for member_name, member in _get_members_and_annotated(module):
if is_excluded_from_inspect(member):
for member_name, member in inspect.getmembers(module):
if _inspect_filter.is_excluded(member):
continue
if _is_marker(member):
@ -455,7 +440,7 @@ def wire( # noqa: C901
elif inspect.isclass(member):
cls = member
try:
cls_members = _get_members_and_annotated(cls)
cls_members = inspect.getmembers(cls)
except Exception: # noqa
# Hotfix, see: https://github.com/ets-labs/python-dependency-injector/issues/441
continue
@ -473,9 +458,6 @@ def wire( # noqa: C901
for patched in _patched_registry.get_callables_from_module(module):
_bind_injections(patched, providers_map)
if not keep_cache:
clear_cache()
def unwire( # noqa: C901
*,
@ -510,11 +492,6 @@ def unwire( # noqa: C901
def inject(fn: F) -> F:
"""Decorate callable with injecting decorator."""
reference_injections, reference_closing = _fetch_reference_injections(fn)
if not reference_injections:
warn("@inject is not required here", DIWiringWarning, stacklevel=2)
return fn
patched = _get_patched(fn, reference_injections, reference_closing)
return cast(F, patched)
@ -560,10 +537,6 @@ 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)
@ -616,26 +589,22 @@ def _unpatch_attribute(patched: PatchedAttribute) -> None:
def _extract_marker(parameter: inspect.Parameter) -> Optional["_Marker"]:
if get_origin(parameter.annotation) is Annotated:
args = get_args(parameter.annotation)
if len(args) > 1:
marker = args[1]
else:
marker = None
marker = get_args(parameter.annotation)[1]
else:
marker = parameter.default
for marker_extractor in MARKER_EXTRACTORS:
if _marker := marker_extractor(marker):
marker = _marker
break
if not isinstance(marker, _Marker):
if not isinstance(marker, _Marker) and not _is_fastapi_depends(marker):
return None
if _is_fastapi_depends(marker):
marker = marker.dependency
if not isinstance(marker, _Marker):
return None
return marker
@cache
def _fetch_reference_injections( # noqa: C901
fn: Callable[..., Any],
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
@ -673,6 +642,21 @@ 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:
@ -694,9 +678,10 @@ def _bind_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> Non
if injection in patched_callable.reference_closing:
patched_callable.add_closing(injection, provider)
for resource in provider.traverse(types=[providers.Resource]):
patched_callable.add_closing(str(id(resource)), resource)
deps = {}
_locate_dependent_closing_args(provider, deps)
for key, dep in deps.items():
patched_callable.add_closing(key, dep)
def _unbind_injections(fn: Callable[..., Any]) -> None:
@ -740,8 +725,6 @@ def _get_patched(
if inspect.iscoroutinefunction(fn):
patched = _get_async_patched(fn, patched_object)
elif inspect.isasyncgenfunction(fn):
patched = _get_async_gen_patched(fn, patched_object)
else:
patched = _get_sync_patched(fn, patched_object)
@ -751,6 +734,10 @@ def _get_patched(
return patched
def _is_fastapi_depends(param: Any) -> bool:
return fastapi and isinstance(param, fastapi.params.Depends)
def _is_patched(fn) -> bool:
return _patched_registry.has_callable(fn)
@ -812,15 +799,15 @@ class RequiredModifier(Modifier):
def __init__(self) -> None:
self.type_modifier = None
def as_int(self) -> Self:
def as_int(self) -> "RequiredModifier":
self.type_modifier = TypeModifier(int)
return self
def as_float(self) -> Self:
def as_float(self) -> "RequiredModifier":
self.type_modifier = TypeModifier(float)
return self
def as_(self, type_: Type) -> Self:
def as_(self, type_: Type) -> "RequiredModifier":
self.type_modifier = TypeModifier(type_)
return self
@ -868,15 +855,15 @@ class ProvidedInstance(Modifier):
def __init__(self) -> None:
self.segments = []
def __getattr__(self, item: str) -> Self:
def __getattr__(self, item):
self.segments.append((self.TYPE_ATTRIBUTE, item))
return self
def __getitem__(self, item) -> Self:
def __getitem__(self, item):
self.segments.append((self.TYPE_ITEM, item))
return self
def call(self) -> Self:
def call(self):
self.segments.append((self.TYPE_CALL, None))
return self
@ -901,56 +888,44 @@ def provided() -> ProvidedInstance:
return ProvidedInstance()
MarkerItem = Union[
str,
providers.Provider[Any],
Tuple[str, TypeModifier],
Type[Container],
"_Marker",
]
class ClassGetItemMeta(GenericMeta):
def __getitem__(cls, item):
# Spike for Python 3.6
if isinstance(item, tuple):
return cls(*item)
return cls(item)
if TYPE_CHECKING:
class _Marker(Generic[T], metaclass=ClassGetItemMeta):
class _Marker(Protocol):
__IS_MARKER__: bool
__IS_MARKER__ = True
def __call__(self) -> Self: ...
def __getattr__(self, item: str) -> Self: ...
def __getitem__(self, item: Any) -> Any: ...
def __init__(
self,
provider: Union[providers.Provider, Container, str],
modifier: Optional[Modifier] = None,
) -> None:
if _is_declarative_container(provider):
provider = provider.__self__
self.provider = provider
self.modifier = modifier
Provide: _Marker
Provider: _Marker
Closing: _Marker
else:
def __class_getitem__(cls, item) -> T:
if isinstance(item, tuple):
return cls(*item)
return cls(item)
class _Marker:
def __call__(self) -> T:
return self
__IS_MARKER__ = True
def __init__(
self,
provider: Union[providers.Provider, Container, str],
modifier: Optional[Modifier] = None,
) -> None:
if _is_declarative_container(provider):
provider = provider.__self__
self.provider = provider
self.modifier = modifier
class Provide(_Marker): ...
def __class_getitem__(cls, item: MarkerItem) -> Self:
if isinstance(item, tuple):
return cls(*item)
return cls(item)
def __call__(self) -> Self:
return self
class Provider(_Marker): ...
class Provide(_Marker): ...
class Provider(_Marker): ...
class Closing(_Marker): ...
class Closing(_Marker): ...
class AutoLoader:
@ -1049,71 +1024,38 @@ def is_loader_installed() -> bool:
_patched_registry = PatchedRegistry()
_inspect_filter = InspectFilter()
_loader = AutoLoader()
# Optimizations
from ._cwiring import DependencyResolver # noqa: E402
from ._cwiring import _sync_inject # noqa
from ._cwiring import _async_inject # noqa
# Wiring uses the following Python wrapper because there is
# no possibility to compile a first-type citizen coroutine in Cython.
def _get_async_patched(fn: F, patched: PatchedCallable) -> F:
@functools.wraps(fn)
async def _patched(*args: Any, **raw_kwargs: Any) -> Any:
resolver = DependencyResolver(raw_kwargs, patched.injections, patched.closing)
async with resolver as kwargs:
return await fn(*args, **kwargs)
return cast(F, _patched)
def _get_async_gen_patched(fn: F, patched: PatchedCallable) -> F:
@functools.wraps(fn)
async def _patched(*args: Any, **raw_kwargs: Any) -> AsyncIterator[Any]:
resolver = DependencyResolver(raw_kwargs, patched.injections, patched.closing)
async with resolver as kwargs:
async for obj in fn(*args, **kwargs):
yield obj
async def _patched(*args, **kwargs):
return await _async_inject(
fn,
args,
kwargs,
patched.injections,
patched.closing,
)
return cast(F, _patched)
def _get_sync_patched(fn: F, patched: PatchedCallable) -> F:
@functools.wraps(fn)
def _patched(*args: Any, **raw_kwargs: Any) -> Any:
resolver = DependencyResolver(raw_kwargs, patched.injections, patched.closing)
with resolver as kwargs:
return fn(*args, **kwargs)
def _patched(*args, **kwargs):
return _sync_inject(
fn,
args,
kwargs,
patched.injections,
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
def clear_cache() -> None:
"""Clear all caches used by :func:`wire`."""
_fetch_reference_injections.cache_clear()

View File

@ -0,0 +1,10 @@
[pytest]
testpaths = tests/unit/
python_files = test_*_py2_py3.py
asyncio_mode = auto
filterwarnings =
ignore:Module \"dependency_injector.ext.aiohttp\" is deprecated since version 4\.0\.0:DeprecationWarning
ignore:Module \"dependency_injector.ext.flask\" is deprecated since version 4\.0\.0:DeprecationWarning
ignore:Please use \`.*?\` from the \`scipy.*?\`(.*?)namespace is deprecated\.:DeprecationWarning
ignore:The \`scipy(.*?)\` namespace is deprecated(.*):DeprecationWarning
ignore:ssl\.PROTOCOL_TLS is deprecated:DeprecationWarning:botocore.*

View File

@ -0,0 +1,10 @@
[pytest]
testpaths = tests/unit/
python_files = test_*_py3.py
asyncio_mode = auto
filterwarnings =
ignore:Module \"dependency_injector.ext.aiohttp\" is deprecated since version 4\.0\.0:DeprecationWarning
ignore:Module \"dependency_injector.ext.flask\" is deprecated since version 4\.0\.0:DeprecationWarning
ignore:Please use \`.*?\` from the \`scipy.*?\`(.*?)namespace is deprecated\.:DeprecationWarning
ignore:The \`scipy(.*?)\` namespace is deprecated(.*):DeprecationWarning
ignore:ssl\.PROTOCOL_TLS is deprecated:DeprecationWarning:botocore.*

13
tests/.configs/pytest.ini Normal file
View File

@ -0,0 +1,13 @@
[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.*

View File

@ -1,10 +1,12 @@
from dependency_injector import providers
class Animal: ...
class Animal:
...
class Cat(Animal): ...
class Cat(Animal):
...
# Test 1: to check Aggregate provider
@ -18,19 +20,13 @@ val1: str = provider1("a")
provider1_set_non_string_keys: providers.Aggregate[str] = providers.Aggregate()
provider1_set_non_string_keys.set_providers({Cat: providers.Object("str")})
provider_set_non_string_1: providers.Provider[str] = (
provider1_set_non_string_keys.providers[Cat]
)
provider_set_non_string_1: providers.Provider[str] = provider1_set_non_string_keys.providers[Cat]
provider1_new_non_string_keys: providers.Aggregate[str] = providers.Aggregate(
{Cat: providers.Object("str")},
)
factory_new_non_string_1: providers.Provider[str] = (
provider1_new_non_string_keys.providers[Cat]
)
factory_new_non_string_1: providers.Provider[str] = provider1_new_non_string_keys.providers[Cat]
provider1_no_explicit_typing = providers.Aggregate(a=providers.Object("str"))
provider1_no_explicit_typing_factory: providers.Provider[str] = (
provider1_no_explicit_typing.providers["a"]
)
provider1_no_explicit_typing_factory: providers.Provider[str] = provider1_no_explicit_typing.providers["a"]
provider1_no_explicit_typing_object: str = provider1_no_explicit_typing("a")

View File

@ -1,9 +1,10 @@
from typing import Any, Callable, Dict, Optional, Tuple, Type
from typing import Callable, Optional, Tuple, Any, Dict, Type
from dependency_injector import providers
class Animal: ...
class Animal:
...
class Cat(Animal):
@ -52,13 +53,10 @@ provider8 = providers.CallableDelegate(providers.Callable(lambda: None))
# Test 9: to check the return type with await
provider9 = providers.Callable(Cat)
async def _async9() -> None:
animal1: Animal = await provider9(1, 2, 3, b="1", c=2, e=0.0) # type: ignore
animal2: Animal = await provider9.async_(1, 2, 3, b="1", c=2, e=0.0)
# Test 10: to check the .provides
provider10 = providers.Callable(Cat)
provides10: Optional[Callable[..., Cat]] = provider10.provides
@ -70,5 +68,5 @@ provides11: Optional[Callable[..., Animal]] = provider11.provides
assert provides11 is Cat
# Test 12: to check string imports
provider12: providers.Callable[Dict[Any, Any]] = providers.Callable("builtins.dict")
provider12: providers.Callable[dict] = providers.Callable("builtins.dict")
provider12.set_provides("builtins.dict")

View File

@ -1,13 +1,13 @@
from pathlib import Path
from typing import Any, Dict
from pydantic_settings import BaseSettings as PydanticSettings
from typing import Any
from dependency_injector import providers
from pydantic_settings import BaseSettings as PydanticSettings
# Test 1: to check the getattr
config1: providers.Configuration = providers.Configuration()
provider1: providers.Provider[Dict[str, Any]] = providers.Factory(dict, a=config1.a)
provider1: providers.Provider[dict] = providers.Factory(dict, a=config1.a)
# Test 2: to check the from_*() method
config2 = providers.Configuration()
@ -68,9 +68,7 @@ config5_pydantic = providers.Configuration(
pydantic_settings=[PydanticSettings()],
)
config5_pydantic.set_pydantic_settings([PydanticSettings()])
config5_pydantic_settings: list[PydanticSettings] = (
config5_pydantic.get_pydantic_settings()
)
config5_pydantic_settings: list[PydanticSettings] = config5_pydantic.get_pydantic_settings()
# Test 6: to check init arguments
config6 = providers.Configuration(

View File

@ -1,9 +1,8 @@
from typing import Any
from dependency_injector import providers
class Container: ...
class Container:
...
# Test 1: to check the return type
@ -12,4 +11,4 @@ var1: Container = provider1()
# Test 2: to check the getattr
provider2 = providers.Container(Container)
attr: providers.Provider[Any] = provider2.attr
attr: providers.Provider = provider2.attr

View File

@ -1,14 +1,14 @@
from typing import Awaitable, Coroutine
from typing import Coroutine
from dependency_injector import providers
async def _coro() -> None: ...
async def _coro() -> None:
...
# Test 1: to check the return type
provider1 = providers.Coroutine(_coro)
var1: Awaitable[None] = provider1()
var1: Coroutine = provider1()
# Test 2: to check string imports
provider2: providers.Coroutine[None] = providers.Coroutine("_coro")

View File

@ -1,4 +1,4 @@
from typing import Any, Dict
from typing import Dict
from dependency_injector import containers, providers
@ -10,7 +10,7 @@ class Container1(containers.DeclarativeContainer):
container1 = Container1()
container1_type: containers.Container = Container1()
provider1: providers.Provider[int] = container1.provider
provider1: providers.Provider = container1.provider
val1: int = container1.provider(3)
@ -20,7 +20,8 @@ class Container21(containers.DeclarativeContainer):
@containers.override(Container21)
class Container22(containers.DeclarativeContainer): ...
class Container22(containers.DeclarativeContainer):
...
# Test 3: to check @copy decorator
@ -29,14 +30,14 @@ class Container31(containers.DeclarativeContainer):
@containers.copy(Container31)
class Container32(containers.DeclarativeContainer): ...
class Container32(containers.DeclarativeContainer):
...
# Test 4: to override()
class Container4(containers.DeclarativeContainer):
provider = providers.Factory(int)
container4 = Container4()
container4.override(Container4())
@ -46,7 +47,7 @@ class Container5(containers.DeclarativeContainer):
provider = providers.Factory(int)
dependencies: Dict[str, providers.Provider[Any]] = Container5.dependencies
dependencies: Dict[str, providers.Provider] = Container5.dependencies
# Test 6: to check base class
@ -61,7 +62,6 @@ container6: containers.Container = Container6()
class Container7(containers.DeclarativeContainer):
provider = providers.Factory(str)
container7 = Container7()
container7.override_providers(provider="new_value")
with container7.override_providers(a=providers.Provider()):

View File

@ -1,20 +1,17 @@
from typing import Any, Optional
from typing import Optional
from dependency_injector import providers
# Test 1: to check the return type
provider1 = providers.Delegate(providers.Provider())
var1: providers.Provider[Any] = provider1()
var1: providers.Provider = provider1()
# Test 2: to check the return type with await
provider2 = providers.Delegate(providers.Provider())
async def _async2() -> None:
var1: providers.Provider[Any] = await provider2() # type: ignore
var2: providers.Provider[Any] = await provider2.async_()
var1: providers.Provider = await provider2() # type: ignore
var2: providers.Provider = await provider2.async_()
# Test 3: to check class type from provider
provider3 = providers.Delegate(providers.Provider())
provided_provides: Optional[providers.Provider[Any]] = provider3.provides
provided_provides: Optional[providers.Provider] = provider3.provides

View File

@ -1,12 +1,11 @@
from typing import Any
from dependency_injector import providers
# Test 1: to check the getattr type
provider1 = providers.DependenciesContainer(
a=providers.Provider(),
b=providers.Provider(),
)
a1: providers.Provider[Any] = provider1.a
b1: providers.Provider[Any] = provider1.b
a1: providers.Provider = provider1.a
b1: providers.Provider = provider1.b
c1: providers.ProvidedInstance = provider1.c.provided

View File

@ -1,14 +1,15 @@
from typing import Any, Type
from typing import Type
from dependency_injector import providers
class Animal: ...
class Animal:
...
class Cat(Animal):
def __init__(self, *a: Any, **kw: Any) -> None: ...
def __init__(self, *_, **__): ...
# Test 1: to check the return type
@ -22,8 +23,6 @@ var2: Type[Animal] = provider2.instance_of
# Test 3: to check the return type with await
provider3 = providers.Dependency(instance_of=Animal)
async def _async3() -> None:
var1: Animal = await provider3() # type: ignore
var2: Animal = await provider3.async_()

View File

@ -2,6 +2,7 @@ from typing import Any, Dict
from dependency_injector import providers
# Test 1: to check the return type (class)
provider1 = providers.Dict(
a1=providers.Factory(object),
@ -16,9 +17,7 @@ var2: Dict[Any, Any] = provider2()
# Test 3: to check init with non-string keys
provider3 = providers.Dict(
{object(): providers.Factory(object)}, a2=providers.Factory(object)
)
provider3 = providers.Dict({object(): providers.Factory(object)}, a2=providers.Factory(object))
var3: Dict[Any, Any] = provider3()
@ -43,8 +42,6 @@ provider6 = providers.Dict(
a1=providers.Factory(object),
a2=providers.Factory(object),
)
async def _async3() -> None:
var1: Dict[Any, Any] = await provider6() # type: ignore
var2: Dict[Any, Any] = await provider6.async_()

View File

@ -1,7 +1,8 @@
from typing import Any, Dict
from typing import Dict
from dependency_injector import containers, providers
# Test 1: to check setattr
container1 = containers.DynamicContainer()
container1.abc = providers.Provider()
@ -22,7 +23,7 @@ container4.set_providers(a=providers.Provider())
# Test 5: to check .dependencies attribute
container5 = containers.DynamicContainer()
dependencies: Dict[str, providers.Provider[Any]] = container5.dependencies
dependencies: Dict[str, providers.Provider] = container5.dependencies
# Test 6: to check base class
container6: containers.Container = containers.DynamicContainer()

View File

@ -1,14 +1,15 @@
from typing import Any, Callable, Dict, Optional, Tuple, Type
from typing import Callable, Optional, Tuple, Any, Dict, Type
from dependency_injector import providers
class Animal: ...
class Animal:
...
class Cat(Animal):
def __init__(self, *a: Any, **kw: Any) -> None: ...
def __init__(self, *_, **__): ...
@classmethod
def create(cls) -> Animal:
@ -62,29 +63,17 @@ factory_a_9: providers.Factory[str] = provider9.a
factory_b_9: providers.Factory[str] = provider9.b
val9: str = provider9("a")
provider9_set_non_string_keys: providers.FactoryAggregate[str] = (
providers.FactoryAggregate()
)
provider9_set_non_string_keys: providers.FactoryAggregate[str] = providers.FactoryAggregate()
provider9_set_non_string_keys.set_factories({Cat: providers.Factory(str, "str")})
factory_set_non_string_9: providers.Factory[str] = (
provider9_set_non_string_keys.factories[Cat]
)
factory_set_non_string_9: providers.Factory[str] = provider9_set_non_string_keys.factories[Cat]
provider9_new_non_string_keys: providers.FactoryAggregate[str] = (
providers.FactoryAggregate(
{Cat: providers.Factory(str, "str")},
)
)
factory_new_non_string_9: providers.Factory[str] = (
provider9_new_non_string_keys.factories[Cat]
provider9_new_non_string_keys: providers.FactoryAggregate[str] = providers.FactoryAggregate(
{Cat: providers.Factory(str, "str")},
)
factory_new_non_string_9: providers.Factory[str] = provider9_new_non_string_keys.factories[Cat]
provider9_no_explicit_typing = providers.FactoryAggregate(
a=providers.Factory(str, "str")
)
provider9_no_explicit_typing_factory: providers.Factory[str] = (
provider9_no_explicit_typing.factories["a"]
)
provider9_no_explicit_typing = providers.FactoryAggregate(a=providers.Factory(str, "str"))
provider9_no_explicit_typing_factory: providers.Factory[str] = provider9_no_explicit_typing.factories["a"]
provider9_no_explicit_typing_object: str = provider9_no_explicit_typing("a")
# Test 10: to check the explicit typing
@ -93,13 +82,10 @@ animal10: Animal = factory10()
# Test 11: to check the return type with await
provider11 = providers.Factory(Cat)
async def _async11() -> None:
animal1: Animal = await provider11(1, 2, 3, b="1", c=2, e=0.0) # type: ignore
animal2: Animal = await provider11.async_(1, 2, 3, b="1", c=2, e=0.0)
# Test 12: to check class type from .provides
provider12 = providers.Factory(Cat)
provided_cls12: Type[Animal] = provider12.cls
@ -115,5 +101,5 @@ provided_provides13: Optional[Callable[..., Animal]] = provider13.provides
assert provided_provides13 is not None and provided_provides13() == Cat()
# Test 14: to check string imports
provider14: providers.Factory[Dict[Any, Any]] = providers.Factory("builtins.dict")
provider14: providers.Factory[dict] = providers.Factory("builtins.dict")
provider14.set_provides("builtins.dict")

View File

@ -1,7 +1,8 @@
from typing import Any, List, Tuple
from typing import Tuple, Any, List
from dependency_injector import providers
# Test 1: to check the return type (class)
provider1 = providers.List(
providers.Factory(object),
@ -32,8 +33,6 @@ provider4 = providers.List(
providers.Factory(object),
providers.Factory(object),
)
async def _async4() -> None:
var1: List[Any] = await provider4() # type: ignore
var2: List[Any] = await provider4.async_()

View File

@ -1,7 +1,8 @@
from typing import Optional, Type
from typing import Type, Optional
from dependency_injector import providers
# Test 1: to check the return type
provider1 = providers.Object(int(3))
var1: int = provider1()
@ -15,13 +16,10 @@ method_caller2: providers.MethodCaller = provider2.provided.method.call(123, arg
# Test 3: to check the return type with await
provider3 = providers.Object(int(3))
async def _async3() -> None:
var1: int = await provider3() # type: ignore
var2: int = await provider3.async_()
# Test 4: to check class type from provider
provider4 = providers.Object(int("1"))
provided_provides: Optional[int] = provider4.provides

View File

@ -1,14 +1,13 @@
from typing import Any
from dependency_injector import providers
# Test 1: to check .provided attribute
provider1: providers.Provider[int] = providers.Object(1)
provided: int = provider1.provided()
provider1_delegate: providers.Provider[int] = provider1.provider
# Test 2: to check async mode API
provider2: providers.Provider[Any] = providers.Provider()
provider2: providers.Provider = providers.Provider()
provider2.enable_async_mode()
provider2.disable_async_mode()
provider2.reset_async_mode()

View File

@ -1,13 +1,4 @@
from typing import (
Any,
AsyncGenerator,
AsyncIterator,
Dict,
Generator,
Iterator,
List,
Optional,
)
from typing import List, Iterator, Generator, AsyncIterator, AsyncGenerator, Optional
from dependency_injector import providers, resources
@ -41,10 +32,11 @@ var3: List[int] = provider3()
# Test 4: to check the return type with resource subclass
class MyResource4(resources.Resource[List[int]]):
def init(self, *args: Any, **kwargs: Any) -> List[int]:
def init(self, *args, **kwargs) -> List[int]:
return []
def shutdown(self, resource: Optional[List[int]]) -> None: ...
def shutdown(self, resource: Optional[List[int]]) -> None:
...
provider4 = providers.Resource(MyResource4)
@ -92,10 +84,11 @@ async def _provide7() -> None:
# Test 8: to check the return type with async resource subclass
class MyResource8(resources.AsyncResource[List[int]]):
async def init(self, *args: Any, **kwargs: Any) -> List[int]:
async def init(self, *args, **kwargs) -> List[int]:
return []
async def shutdown(self, resource: Optional[List[int]]) -> None: ...
async def shutdown(self, resource: Optional[List[int]]) -> None:
...
provider8 = providers.Resource(MyResource8)
@ -107,5 +100,5 @@ async def _provide8() -> None:
# Test 9: to check string imports
provider9: providers.Resource[Dict[Any, Any]] = providers.Resource("builtins.dict")
provider9: providers.Resource[dict] = providers.Resource("builtins.dict")
provider9.set_provides("builtins.dict")

View File

@ -2,6 +2,7 @@ from typing import Any
from dependency_injector import providers
# Test 1: to check the return type
provider1 = providers.Selector(
lambda: "a",
@ -27,7 +28,7 @@ provider3 = providers.Selector(
a=providers.Factory(object),
b=providers.Factory(object),
)
attr3: providers.Provider[Any] = provider3.a
attr3: providers.Provider = provider3.a
# Test 4: to check the return type with await
provider4 = providers.Selector(
@ -35,8 +36,6 @@ provider4 = providers.Selector(
a=providers.Factory(object),
b=providers.Factory(object),
)
async def _async4() -> None:
var1: Any = await provider4()
var1: Any = await provider4() # type: ignore
var2: Any = await provider4.async_()

View File

@ -1,14 +1,15 @@
from typing import Any, Callable, Dict, Optional, Tuple, Type
from typing import Callable, Optional, Tuple, Any, Dict, Type
from dependency_injector import providers
class Animal: ...
class Animal:
...
class Cat(Animal):
def __init__(self, *a: Any, **kw: Any) -> None: ...
def __init__(self, *_, **__): ...
@classmethod
def create(cls) -> Animal:
@ -71,13 +72,10 @@ provider12 = providers.SingletonDelegate(providers.Singleton(object))
# Test 13: to check the return type with await
provider13 = providers.Singleton(Cat)
async def _async13() -> None:
animal1: Animal = await provider13(1, 2, 3, b="1", c=2, e=0.0) # type: ignore
animal2: Animal = await provider13.async_(1, 2, 3, b="1", c=2, e=0.0)
# Test 14: to check class from .provides
provider14 = providers.Singleton(Cat)
provided_cls14: Type[Cat] = provider14.cls
@ -93,5 +91,5 @@ provided_provides15: Optional[Callable[..., Animal]] = provider15.provides
assert provided_provides15 is not None and provided_provides15() == Cat()
# Test 16: to check string imports
provider16: providers.Singleton[Dict[Any, Any]] = providers.Singleton("builtins.dict")
provider16: providers.Singleton[dict] = providers.Singleton("builtins.dict")
provider16.set_provides("builtins.dict")

View File

@ -1,36 +0,0 @@
from typing import Iterator
from typing_extensions import Annotated
from dependency_injector.containers import DeclarativeContainer
from dependency_injector.providers import Object, Resource
from dependency_injector.wiring import Closing, Provide, required
def _resource() -> Iterator[int]:
yield 1
class Container(DeclarativeContainer):
value = Object(1)
res = Resource(_resource)
def default_by_ref(value: int = Provide[Container.value]) -> None: ...
def default_by_string(value: int = Provide["value"]) -> None: ...
def default_by_string_with_modifier(
value: int = Provide["value", required().as_int()]
) -> None: ...
def default_container(container: Container = Provide[Container]) -> None: ...
def default_with_closing(value: int = Closing[Provide[Container.res]]) -> None: ...
def annotated_by_ref(value: Annotated[int, Provide[Container.value]]) -> None: ...
def annotated_by_string(value: Annotated[int, Provide["value"]]) -> None: ...
def annotated_by_string_with_modifier(
value: Annotated[int, Provide["value", required().as_int()]],
) -> None: ...
def annotated_container(
container: Annotated[Container, Provide[Container]],
) -> None: ...
def annotated_with_closing(
value: Annotated[int, Closing[Provide[Container.res]]],
) -> None: ...

View File

@ -145,121 +145,3 @@ async def test_shutdown_sync_and_async_ordering():
await container.shutdown_resources()
assert initialized_resources == ["r1", "r2", "r3", "r1", "r2", "r3"]
assert shutdown_resources == ["r3", "r2", "r1", "r3", "r2", "r1"]
@mark.asyncio
async def test_init_and_shutdown_scoped_resources():
initialized_resources = []
shutdown_resources = []
def _sync_resource(name, **_):
initialized_resources.append(name)
yield name
shutdown_resources.append(name)
async def _async_resource(name, **_):
initialized_resources.append(name)
yield name
shutdown_resources.append(name)
class ResourceA(providers.Resource):
pass
class ResourceB(providers.Resource):
pass
class Container(containers.DeclarativeContainer):
resource_a = ResourceA(
_sync_resource,
name="ra1",
)
resource_b1 = ResourceB(
_sync_resource,
name="rb1",
r1=resource_a,
)
resource_b2 = ResourceB(
_async_resource,
name="rb2",
r2=resource_b1,
)
container = Container()
container.init_resources(resource_type=ResourceA)
assert initialized_resources == ["ra1"]
assert shutdown_resources == []
container.shutdown_resources(resource_type=ResourceA)
assert initialized_resources == ["ra1"]
assert shutdown_resources == ["ra1"]
await container.init_resources(resource_type=ResourceB)
assert initialized_resources == ["ra1", "ra1", "rb1", "rb2"]
assert shutdown_resources == ["ra1"]
await container.shutdown_resources(resource_type=ResourceB)
assert initialized_resources == ["ra1", "ra1", "rb1", "rb2"]
assert shutdown_resources == ["ra1", "rb2", "rb1"]
@mark.asyncio
async def test_init_and_shutdown_all_scoped_resources_using_default_value():
initialized_resources = []
shutdown_resources = []
def _sync_resource(name, **_):
initialized_resources.append(name)
yield name
shutdown_resources.append(name)
async def _async_resource(name, **_):
initialized_resources.append(name)
yield name
shutdown_resources.append(name)
class ResourceA(providers.Resource):
pass
class ResourceB(providers.Resource):
pass
class Container(containers.DeclarativeContainer):
resource_a = ResourceA(
_sync_resource,
name="r1",
)
resource_b1 = ResourceB(
_sync_resource,
name="r2",
r1=resource_a,
)
resource_b2 = ResourceB(
_async_resource,
name="r3",
r2=resource_b1,
)
container = Container()
await container.init_resources()
assert initialized_resources == ["r1", "r2", "r3"]
assert shutdown_resources == []
await container.shutdown_resources()
assert initialized_resources == ["r1", "r2", "r3"]
assert shutdown_resources == ["r3", "r2", "r1"]
await container.init_resources()
assert initialized_resources == ["r1", "r2", "r3", "r1", "r2", "r3"]
assert shutdown_resources == ["r3", "r2", "r1"]
await container.shutdown_resources()
assert initialized_resources == ["r1", "r2", "r3", "r1", "r2", "r3"]
assert shutdown_resources == ["r3", "r2", "r1", "r3", "r2", "r1"]

View File

@ -325,19 +325,6 @@ def test_init_shutdown_nested_resources():
assert _init2.shutdown_counter == 2
def test_init_shutdown_resources_wrong_type() -> None:
class Container(containers.DeclarativeContainer):
pass
c = Container()
with raises(TypeError, match=r"resource_type must be a subclass of Resource provider"):
c.init_resources(int) # type: ignore[arg-type]
with raises(TypeError, match=r"resource_type must be a subclass of Resource provider"):
c.shutdown_resources(int) # type: ignore[arg-type]
def test_reset_singletons():
class SubSubContainer(containers.DeclarativeContainer):
singleton = providers.Singleton(object)

View File

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

View File

@ -1,4 +1,4 @@
from typing import AsyncIterator, Iterator, TypeVar
from typing import AsyncIterator, Iterator
from unittest.mock import ANY
from pytest import mark
@ -7,12 +7,6 @@ from dependency_injector.containers import DeclarativeContainer
from dependency_injector.ext.starlette import Lifespan
from dependency_injector.providers import Resource
T = TypeVar("T")
class XResource(Resource[T]):
"""A test provider"""
class TestLifespan:
@mark.parametrize("sync", [False, True])
@ -34,15 +28,11 @@ class TestLifespan:
yield
shutdown = True
def nope():
assert False, "should not be called"
class Container(DeclarativeContainer):
x = XResource(sync_resource if sync else async_resource)
y = Resource(nope)
x = Resource(sync_resource if sync else async_resource)
container = Container()
lifespan = Lifespan(container, resource_type=XResource)
lifespan = Lifespan(container)
async with lifespan(ANY) as scope:
assert scope is None

View File

@ -2,12 +2,11 @@
import asyncio
import inspect
from contextlib import asynccontextmanager
import sys
from typing import Any
from pytest import mark, raises
from dependency_injector import containers, providers, resources
from pytest import mark, raises
@mark.asyncio
@ -35,6 +34,7 @@ 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()
@ -71,46 +71,6 @@ async def test_init_async_generator():
assert _init.shutdown_counter == 2
@mark.asyncio
async def test_init_async_context_manager() -> None:
resource = object()
init_counter = 0
shutdown_counter = 0
@asynccontextmanager
async def _init():
nonlocal init_counter, shutdown_counter
await asyncio.sleep(0.001)
init_counter += 1
yield resource
await asyncio.sleep(0.001)
shutdown_counter += 1
provider = providers.Resource(_init)
result1 = await provider()
assert result1 is resource
assert init_counter == 1
assert shutdown_counter == 0
await provider.shutdown()
assert init_counter == 1
assert shutdown_counter == 1
result2 = await provider()
assert result2 is resource
assert init_counter == 2
assert shutdown_counter == 1
await provider.shutdown()
assert init_counter == 2
assert shutdown_counter == 2
@mark.asyncio
async def test_init_async_class():
resource = object()

View File

@ -2,12 +2,10 @@
import decimal
import sys
from contextlib import contextmanager
from typing import Any
from pytest import mark, raises
from dependency_injector import containers, errors, providers, resources
from dependency_injector import containers, providers, resources, errors
from pytest import raises, mark
def init_fn(*args, **kwargs):
@ -125,41 +123,6 @@ def test_init_generator():
assert _init.shutdown_counter == 2
def test_init_context_manager() -> None:
init_counter, shutdown_counter = 0, 0
@contextmanager
def _init():
nonlocal init_counter, shutdown_counter
init_counter += 1
yield
shutdown_counter += 1
init_counter = 0
shutdown_counter = 0
provider = providers.Resource(_init)
result1 = provider()
assert result1 is None
assert init_counter == 1
assert shutdown_counter == 0
provider.shutdown()
assert init_counter == 1
assert shutdown_counter == 1
result2 = provider()
assert result2 is None
assert init_counter == 2
assert shutdown_counter == 1
provider.shutdown()
assert init_counter == 2
assert shutdown_counter == 2
def test_init_class():
class TestResource(resources.Resource):
init_counter = 0
@ -227,7 +190,7 @@ def test_init_class_abc_shutdown_definition_is_not_required():
def test_init_not_callable():
provider = providers.Resource(1)
with raises(TypeError, match=r"object is not callable"):
with raises(errors.Error):
provider.init()

View File

@ -1,9 +1,7 @@
import asyncio
from typing_extensions import Annotated
from dependency_injector import containers, providers
from dependency_injector.wiring import Closing, Provide, inject
from dependency_injector.wiring import inject, Provide, Closing
class TestResource:
@ -44,15 +42,6 @@ async def async_injection(
return resource1, resource2
@inject
async def async_generator_injection(
resource1: object = Provide[Container.resource1],
resource2: object = Closing[Provide[Container.resource2]],
):
yield resource1
yield resource2
@inject
async def async_injection_with_closing(
resource1: object = Closing[Provide[Container.resource1]],

View File

@ -1,126 +0,0 @@
"""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()

View File

@ -1,39 +0,0 @@
import sys
from fast_depends import Depends
from typing_extensions import Annotated
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class CoefficientService:
@staticmethod
def get_coefficient() -> float:
return 1.2
class Container(containers.DeclarativeContainer):
service = providers.Factory(CoefficientService)
@inject
def apply_coefficient(
a: int,
coefficient_provider: CoefficientService = Depends(Provide[Container.service]),
) -> float:
return a * coefficient_provider.get_coefficient()
@inject
def apply_coefficient_annotated(
a: int,
coefficient_provider: Annotated[
CoefficientService, Depends(Provide[Container.service])
],
) -> float:
return a * coefficient_provider.get_coefficient()
container = Container()
container.wire(modules=[sys.modules[__name__]])

View File

@ -59,13 +59,12 @@ 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=config.a), providers.Callable(lambda b: b, 2)
providers.Callable(lambda a: a, a=1), providers.Callable(lambda b: b, 2)
)
_dict = providers.Dict(
a=providers.Callable(lambda a: a, a=3), b=providers.Callable(lambda b: b, config.b)
a=providers.Callable(lambda a: a, a=3), b=providers.Callable(lambda b: b, 4)
)
service = providers.Resource(init_service, counter, _list, _dict=_dict)
service2 = providers.Resource(init_service, counter, _list, _dict=_dict)

View File

@ -32,23 +32,6 @@ async def test_async_injections():
assert asyncinjections.resource2.shutdown_counter == 0
@mark.asyncio
async def test_async_generator_injections() -> None:
resources = []
async for resource in asyncinjections.async_generator_injection():
resources.append(resource)
assert len(resources) == 2
assert resources[0] is asyncinjections.resource1
assert asyncinjections.resource1.init_counter == 1
assert asyncinjections.resource1.shutdown_counter == 0
assert resources[1] is asyncinjections.resource2
assert asyncinjections.resource2.init_counter == 1
assert asyncinjections.resource2.shutdown_counter == 1
@mark.asyncio
async def test_async_injections_with_closing():
resource1, resource2 = await asyncinjections.async_injection_with_closing()

View File

@ -1,176 +0,0 @@
"""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)

View File

@ -1,46 +0,0 @@
"""Tests for string module and package names."""
from typing import Iterator, Optional
from pytest import fixture, mark
from samples.wiring.container import Container
from dependency_injector.wiring import _fetch_reference_injections
@fixture
def container() -> Iterator[Container]:
container = Container()
yield container
container.unwire()
@mark.parametrize(
["arg_value", "wc_value", "empty_cache"],
[
(None, False, True),
(False, True, True),
(True, False, False),
(None, True, False),
],
)
def test_fetch_reference_injections_cache(
container: Container,
arg_value: Optional[bool],
wc_value: bool,
empty_cache: bool,
) -> None:
container.wiring_config.keep_cache = wc_value
container.wire(
modules=["samples.wiring.module"],
packages=["samples.wiring.package"],
keep_cache=arg_value,
)
cache_info = _fetch_reference_injections.cache_info()
if empty_cache:
assert cache_info == (0, 0, None, 0)
else:
assert cache_info.hits > 0
assert cache_info.misses > 0
assert cache_info.currsize > 0

View File

@ -1,9 +0,0 @@
from wiringfastdepends import sample
def test_apply_coefficient() -> None:
assert sample.apply_coefficient(100) == 120.0
def test_apply_coefficient_annotated() -> None:
assert sample.apply_coefficient_annotated(100) == 120.0

View File

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

33
tox.ini
View File

@ -1,7 +1,7 @@
[tox]
parallel_show_output = true
envlist=
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
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=
@ -17,10 +17,10 @@ deps=
mypy_boto3_s3
pydantic-settings
werkzeug
fast-depends
extras=
yaml
commands = pytest
commands = pytest -c tests/.configs/pytest.ini
python_files = test_*_py3*.py
setenv =
COVERAGE_RCFILE = pyproject.toml
@ -45,8 +45,7 @@ deps =
boto3
mypy_boto3_s3
werkzeug
fast-depends
commands = pytest -m pydantic
commands = pytest -c tests/.configs/pytest.ini -m pydantic
[testenv:coveralls]
passenv = GITHUB_*, COVERALLS_*, DEPENDENCY_INJECTOR_*
@ -58,10 +57,26 @@ deps=
coveralls>=4
commands=
coverage erase
coverage run -m pytest
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
commands = pytest -c tests/.configs/pytest-py35.ini
[testenv:pylint]
deps=
pylint
@ -74,8 +89,8 @@ commands=
deps=
flake8
commands=
flake8 src/dependency_injector/
flake8 examples/
flake8 --max-complexity=10 src/dependency_injector/
flake8 --max-complexity=10 examples/
[testenv:pydocstyle]
deps=
@ -90,4 +105,4 @@ deps=
pydantic-settings
mypy
commands=
mypy --strict tests/typing
mypy tests/typing