mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-06-23 06:53:12 +03:00
Merge branch 'release/4.48.0'
This commit is contained in:
commit
9d6994391f
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{py,pyi,pxd,pyx}]
|
||||
ij_visual_guides = 80,88
|
9
docs/api/asgi-lifespan.rst
Normal file
9
docs/api/asgi-lifespan.rst
Normal file
|
@ -0,0 +1,9 @@
|
|||
dependency_injector.ext.starlette
|
||||
=================================
|
||||
|
||||
.. automodule:: dependency_injector.ext.starlette
|
||||
:members:
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
.. disqus::
|
|
@ -2,10 +2,11 @@ API Documentation
|
|||
=================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 2
|
||||
|
||||
top-level
|
||||
providers
|
||||
containers
|
||||
wiring
|
||||
errors
|
||||
asgi-lifespan
|
||||
|
|
|
@ -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 = None
|
||||
language = "en"
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
|
|
|
@ -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: 13
|
||||
:emphasize-lines: 12
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
|
48
docs/examples/fastdepends.rst
Normal file
48
docs/examples/fastdepends.rst
Normal file
|
@ -0,0 +1,48 @@
|
|||
.. _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
|
|
@ -22,5 +22,6 @@ Explore the examples to see the ``Dependency Injector`` in action.
|
|||
fastapi
|
||||
fastapi-redis
|
||||
fastapi-sqlalchemy
|
||||
fastdepends
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -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:: plain
|
||||
.. code-block:: text
|
||||
|
||||
Explicit is better than implicit
|
||||
|
||||
|
|
|
@ -7,15 +7,31 @@ that were made in every particular version.
|
|||
From version 0.7.6 *Dependency Injector* framework strictly
|
||||
follows `Semantic versioning`_
|
||||
|
||||
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`_)
|
||||
- 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.
|
||||
|
||||
|
@ -371,8 +387,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
|
||||
|
|
|
@ -61,11 +61,12 @@ 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 3 types of initializers:
|
||||
Resource provider supports 4 types of initializers:
|
||||
|
||||
- Function
|
||||
- Generator
|
||||
- Subclass of ``resources.Resource``
|
||||
- Context Manager
|
||||
- Generator (legacy)
|
||||
- Subclass of ``resources.Resource`` (legacy)
|
||||
|
||||
Function initializer
|
||||
--------------------
|
||||
|
@ -103,8 +104,44 @@ you configure global resource:
|
|||
|
||||
Function initializer does not provide a way to specify custom resource shutdown.
|
||||
|
||||
Generator initializer
|
||||
---------------------
|
||||
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)
|
||||
------------------------------
|
||||
|
||||
Resource provider can use 2-step generators:
|
||||
|
||||
|
@ -154,8 +191,13 @@ object is not mandatory. You can leave ``yield`` statement empty:
|
|||
argument2=...,
|
||||
)
|
||||
|
||||
Subclass initializer
|
||||
--------------------
|
||||
.. note::
|
||||
|
||||
Generator initializers are automatically wrapped with ``contextmanager`` or ``asynccontextmanager`` decorator when
|
||||
provided to a ``Resource`` provider.
|
||||
|
||||
Subclass initializer (legacy)
|
||||
-----------------------------
|
||||
|
||||
You can create resource initializer by implementing a subclass of the ``resources.Resource``:
|
||||
|
||||
|
@ -210,6 +252,72 @@ 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
|
||||
---------------------------------------------------
|
||||
|
||||
|
@ -263,10 +371,11 @@ Asynchronous function initializer:
|
|||
argument2=...,
|
||||
)
|
||||
|
||||
Asynchronous generator initializer:
|
||||
Asynchronous Context Manager initializer:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@asynccontextmanager
|
||||
async def init_async_resource(argument1=..., argument2=...):
|
||||
connection = await connect()
|
||||
yield connection
|
||||
|
@ -358,5 +467,54 @@ 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::
|
||||
|
|
|
@ -257,7 +257,7 @@ Let's check that it works. Open another terminal session and use ``httpie``:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: json
|
||||
.. code-block:: http
|
||||
|
||||
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:: json
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 492
|
||||
|
|
|
@ -84,7 +84,7 @@ Create next structure in the project root directory. All files are empty. That's
|
|||
|
||||
Initial project layout:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
|
||||
./
|
||||
├── 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:: bash
|
||||
.. code-block:: text
|
||||
|
||||
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:: bash
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 2-3
|
||||
|
||||
./
|
||||
|
@ -205,13 +205,13 @@ Now run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
|
||||
OK
|
||||
|
||||
Check that files ``movies.csv`` and ``movies.db`` have appeared in the ``data/`` folder:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
: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:: bash
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 10
|
||||
|
||||
./
|
||||
|
@ -356,7 +356,7 @@ Let's move on to the finders.
|
|||
|
||||
Create the ``finders.py`` in the ``movies`` package:
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: text
|
||||
: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:: bash
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 12
|
||||
|
||||
./
|
||||
|
@ -613,7 +613,7 @@ Run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block:: plain
|
||||
.. code-block:: text
|
||||
|
||||
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:: plain
|
||||
.. code-block:: text
|
||||
|
||||
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:: plain
|
||||
.. code-block:: text
|
||||
|
||||
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:: bash
|
||||
.. code-block:: text
|
||||
:emphasize-lines: 13
|
||||
|
||||
./
|
||||
|
@ -977,7 +977,7 @@ Run in the terminal:
|
|||
|
||||
You should see:
|
||||
|
||||
.. code-block::
|
||||
.. code-block:: text
|
||||
|
||||
platform darwin -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
|
||||
plugins: cov-3.0.0
|
||||
|
|
|
@ -280,7 +280,7 @@ Now let's fill in the layout.
|
|||
|
||||
Put next into the ``base.html``:
|
||||
|
||||
.. code-block:: html
|
||||
.. code-block:: jinja
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
@ -313,7 +313,7 @@ And put something to the index page.
|
|||
|
||||
Put next into the ``index.html``:
|
||||
|
||||
.. code-block:: html
|
||||
.. code-block:: jinja
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
|
|
|
@ -127,6 +127,7 @@ 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
|
||||
|
@ -631,6 +632,36 @@ 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
|
||||
---------------------------------
|
||||
|
||||
|
@ -662,5 +693,6 @@ Take a look at other application examples:
|
|||
- :ref:`fastapi-example`
|
||||
- :ref:`fastapi-redis-example`
|
||||
- :ref:`fastapi-sqlalchemy-example`
|
||||
- :ref:`fastdepends-example`
|
||||
|
||||
.. disqus::
|
||||
|
|
|
@ -18,10 +18,9 @@ SQLITE_FILE = DIR / "movies.db"
|
|||
|
||||
|
||||
def create_csv(movies_data, path):
|
||||
with open(path, "w") as opened_file:
|
||||
with open(path, "w", newline="") as opened_file:
|
||||
writer = csv.writer(opened_file)
|
||||
for row in movies_data:
|
||||
writer.writerow(row)
|
||||
writer.writerows(movies_data)
|
||||
|
||||
|
||||
def create_sqlite(movies_data, path):
|
||||
|
|
|
@ -29,7 +29,7 @@ class CsvMovieFinder(MovieFinder):
|
|||
super().__init__(movie_factory)
|
||||
|
||||
def find_all(self) -> List[Movie]:
|
||||
with open(self._csv_file_path) as csv_file:
|
||||
with open(self._csv_file_path, newline="") as csv_file:
|
||||
csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
|
||||
return [self._movie_factory(*row) for row in csv_reader]
|
||||
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
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
|
||||
|
|
|
@ -91,6 +91,7 @@ show_missing = true
|
|||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
combine_as_imports = true
|
||||
|
||||
[tool.pylint.main]
|
||||
ignore = ["tests"]
|
||||
|
|
|
@ -20,5 +20,6 @@ scipy
|
|||
boto3
|
||||
mypy_boto3_s3
|
||||
typing_extensions
|
||||
fast-depends
|
||||
|
||||
-r requirements-ext.txt
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Top-level package."""
|
||||
|
||||
__version__ = "4.47.1"
|
||||
__version__ = "4.48.0"
|
||||
"""Version number.
|
||||
|
||||
:type: str
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
from typing import Any, Awaitable, Callable, Dict, Tuple, TypeVar
|
||||
from typing import Any, Dict
|
||||
|
||||
from .providers import Provider
|
||||
|
||||
T = TypeVar("T")
|
||||
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 _sync_inject(
|
||||
fn: Callable[..., T],
|
||||
args: Tuple[Any, ...],
|
||||
kwargs: Dict[str, Any],
|
||||
injections: Dict[str, Provider[Any]],
|
||||
closings: Dict[str, Provider[Any]],
|
||||
/,
|
||||
) -> T: ...
|
||||
async def _async_inject(
|
||||
fn: Callable[..., Awaitable[T]],
|
||||
args: Tuple[Any, ...],
|
||||
kwargs: Dict[str, Any],
|
||||
injections: Dict[str, Provider[Any]],
|
||||
closings: Dict[str, Provider[Any]],
|
||||
/,
|
||||
) -> T: ...
|
||||
def _isawaitable(instance: Any) -> bool: ...
|
||||
|
|
|
@ -1,83 +1,110 @@
|
|||
"""Wiring optimizations module."""
|
||||
|
||||
import asyncio
|
||||
import collections.abc
|
||||
import inspect
|
||||
import types
|
||||
from asyncio import gather
|
||||
from collections.abc import Awaitable
|
||||
from inspect import CO_ITERABLE_COROUTINE
|
||||
from types import CoroutineType, GeneratorType
|
||||
|
||||
from .providers cimport Provider, Resource, NULL_AWAITABLE
|
||||
from .wiring import _Marker
|
||||
|
||||
from .providers cimport Provider, Resource
|
||||
cimport cython
|
||||
|
||||
|
||||
def _sync_inject(object fn, tuple args, dict kwargs, dict injections, dict closings, /):
|
||||
cdef object result
|
||||
@cython.internal
|
||||
@cython.no_gc
|
||||
cdef class KWPair:
|
||||
cdef str name
|
||||
cdef object value
|
||||
|
||||
def __cinit__(self, str name, object value, /):
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
|
||||
cdef inline bint _is_injectable(dict kwargs, str name):
|
||||
return name not in kwargs or isinstance(kwargs[name], _Marker)
|
||||
|
||||
|
||||
cdef class DependencyResolver:
|
||||
cdef dict kwargs
|
||||
cdef dict to_inject
|
||||
cdef object arg_key
|
||||
cdef Provider provider
|
||||
cdef dict injections
|
||||
cdef dict 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()
|
||||
def __init__(self, dict kwargs, dict injections, dict closings, /):
|
||||
self.kwargs = kwargs
|
||||
self.to_inject = kwargs.copy()
|
||||
self.injections = injections
|
||||
self.closings = closings
|
||||
|
||||
result = fn(*args, **to_inject)
|
||||
async def _await_injection(self, kw_pair: KWPair, /) -> None:
|
||||
self.to_inject[kw_pair.name] = await kw_pair.value
|
||||
|
||||
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()
|
||||
cdef object _await_injections(self, to_await: list):
|
||||
return gather(*map(self._await_injection, to_await))
|
||||
|
||||
return result
|
||||
cdef void _handle_injections_sync(self):
|
||||
cdef Provider provider
|
||||
|
||||
for name, provider in self.injections.items():
|
||||
if _is_injectable(self.kwargs, name):
|
||||
self.to_inject[name] = 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
|
||||
cdef list _handle_injections_async(self):
|
||||
cdef list to_await = []
|
||||
cdef Provider provider
|
||||
|
||||
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
|
||||
for name, provider in self.injections.items():
|
||||
if _is_injectable(self.kwargs, name):
|
||||
provide = provider()
|
||||
|
||||
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
|
||||
if provider.is_async_mode_enabled() or _isawaitable(provide):
|
||||
to_await.append(KWPair(name, provide))
|
||||
else:
|
||||
self.to_inject[name] = provide
|
||||
|
||||
result = await fn(*args, **to_inject)
|
||||
return to_await
|
||||
|
||||
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 void _handle_closings_sync(self):
|
||||
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):
|
||||
provider.shutdown()
|
||||
|
||||
return result
|
||||
cdef list _handle_closings_async(self):
|
||||
cdef list to_await = []
|
||||
cdef Provider provider
|
||||
|
||||
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 self._await_injections(to_await)
|
||||
return self.to_inject
|
||||
|
||||
def __aexit__(self, *_):
|
||||
if to_await := self._handle_closings_async():
|
||||
return gather(*to_await)
|
||||
return NULL_AWAITABLE
|
||||
|
||||
|
||||
cdef bint _isawaitable(object instance):
|
||||
"""Return true if object can be passed to an ``await`` expression."""
|
||||
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))
|
||||
return (isinstance(instance, CoroutineType) or
|
||||
isinstance(instance, GeneratorType) and
|
||||
bool(instance.gi_code.co_flags & CO_ITERABLE_COROUTINE) or
|
||||
isinstance(instance, Awaitable))
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
from pathlib import Path
|
||||
from typing import (
|
||||
Generic,
|
||||
Type,
|
||||
Dict,
|
||||
List,
|
||||
Tuple,
|
||||
Optional,
|
||||
Any,
|
||||
Union,
|
||||
ClassVar,
|
||||
Awaitable,
|
||||
Callable as _Callable,
|
||||
ClassVar,
|
||||
Dict,
|
||||
Generic,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Awaitable,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
from .providers import Provider, Self, ProviderParent
|
||||
try:
|
||||
from typing import Self as _Self
|
||||
except ImportError:
|
||||
from typing_extensions import Self as _Self
|
||||
|
||||
from .providers import Provider, Resource, Self, ProviderParent
|
||||
|
||||
C_Base = TypeVar("C_Base", bound="Container")
|
||||
C = TypeVar("C", bound="DeclarativeContainer")
|
||||
|
@ -30,32 +35,34 @@ 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] = Provider
|
||||
providers: Dict[str, Provider]
|
||||
provider_type: Type[Provider[Any]] = Provider
|
||||
providers: Dict[str, Provider[Any]]
|
||||
dependencies: Dict[str, Provider[Any]]
|
||||
overridden: Tuple[Provider]
|
||||
overridden: Tuple[Provider[Any], ...]
|
||||
wiring_config: WiringConfiguration
|
||||
auto_load_config: bool = True
|
||||
__self__: Self
|
||||
def __init__(self) -> None: ...
|
||||
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 __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 __delattr__(self, name: str) -> None: ...
|
||||
def set_providers(self, **providers: Provider): ...
|
||||
def set_provider(self, name: str, provider: Provider) -> None: ...
|
||||
def set_providers(self, **providers: Provider[Any]) -> None: ...
|
||||
def set_provider(self, name: str, provider: Provider[Any]) -> None: ...
|
||||
def override(self, overriding: Union[Container, Type[Container]]) -> None: ...
|
||||
def override_providers(
|
||||
self, **overriding_providers: Union[Provider, Any]
|
||||
self, **overriding_providers: Union[Provider[Any], Any]
|
||||
) -> ProvidersOverridingContext[C_Base]: ...
|
||||
def reset_last_overriding(self) -> None: ...
|
||||
def reset_override(self) -> None: ...
|
||||
|
@ -67,8 +74,8 @@ class Container:
|
|||
from_package: Optional[str] = None,
|
||||
) -> None: ...
|
||||
def unwire(self) -> None: ...
|
||||
def init_resources(self) -> Optional[Awaitable]: ...
|
||||
def shutdown_resources(self) -> Optional[Awaitable]: ...
|
||||
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 load_config(self) -> None: ...
|
||||
def apply_container_providers_overridings(self) -> None: ...
|
||||
def reset_singletons(self) -> SingletonResetContext[C_Base]: ...
|
||||
|
@ -79,10 +86,10 @@ class Container:
|
|||
) -> None: ...
|
||||
def from_json_schema(self, filepath: Union[Path, str]) -> None: ...
|
||||
@overload
|
||||
def resolve_provider_name(self, provider: Provider) -> str: ...
|
||||
def resolve_provider_name(self, provider: Provider[Any]) -> str: ...
|
||||
@classmethod
|
||||
@overload
|
||||
def resolve_provider_name(cls, provider: Provider) -> str: ...
|
||||
def resolve_provider_name(cls, provider: Provider[Any]) -> str: ...
|
||||
@property
|
||||
def parent(self) -> Optional[ProviderParent]: ...
|
||||
@property
|
||||
|
@ -97,14 +104,14 @@ class Container:
|
|||
class DynamicContainer(Container): ...
|
||||
|
||||
class DeclarativeContainer(Container):
|
||||
cls_providers: ClassVar[Dict[str, Provider]]
|
||||
inherited_providers: ClassVar[Dict[str, Provider]]
|
||||
def __init__(self, **overriding_providers: Union[Provider, Any]) -> None: ...
|
||||
cls_providers: ClassVar[Dict[str, Provider[Any]]]
|
||||
inherited_providers: ClassVar[Dict[str, Provider[Any]]]
|
||||
def __init__(self, **overriding_providers: Union[Provider[Any], Any]) -> None: ...
|
||||
@classmethod
|
||||
def override(cls, overriding: Union[Container, Type[Container]]) -> None: ...
|
||||
@classmethod
|
||||
def override_providers(
|
||||
cls, **overriding_providers: Union[Provider, Any]
|
||||
cls, **overriding_providers: Union[Provider[Any], Any]
|
||||
) -> ProvidersOverridingContext[C_Base]: ...
|
||||
@classmethod
|
||||
def reset_last_overriding(cls) -> None: ...
|
||||
|
@ -113,7 +120,7 @@ class DeclarativeContainer(Container):
|
|||
|
||||
class ProvidersOverridingContext(Generic[T]):
|
||||
def __init__(
|
||||
self, container: T, overridden_providers: Iterable[Union[Provider, Any]]
|
||||
self, container: T, overridden_providers: Iterable[Union[Provider[Any], Any]]
|
||||
) -> None: ...
|
||||
def __enter__(self) -> T: ...
|
||||
def __exit__(self, *_: Any) -> None: ...
|
||||
|
|
|
@ -20,14 +20,15 @@ from .wiring import wire, unwire
|
|||
class WiringConfiguration:
|
||||
"""Container wiring configuration."""
|
||||
|
||||
def __init__(self, modules=None, packages=None, from_package=None, auto_wire=True):
|
||||
def __init__(self, modules=None, packages=None, from_package=None, auto_wire=True, keep_cache=False):
|
||||
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)
|
||||
return self.__class__(self.modules, self.packages, self.from_package, self.auto_wire, self.keep_cache)
|
||||
|
||||
|
||||
class Container:
|
||||
|
@ -258,7 +259,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):
|
||||
def wire(self, modules=None, packages=None, from_package=None, keep_cache=None):
|
||||
"""Wire container providers with provided packages and modules.
|
||||
|
||||
:rtype: None
|
||||
|
@ -289,10 +290,14 @@ 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:
|
||||
|
@ -310,11 +315,15 @@ class DynamicContainer(Container):
|
|||
self.wired_to_modules.clear()
|
||||
self.wired_to_packages.clear()
|
||||
|
||||
def init_resources(self):
|
||||
def init_resources(self, resource_type=providers.Resource):
|
||||
"""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=[providers.Resource]):
|
||||
for provider in self.traverse(types=[resource_type]):
|
||||
resource = provider.init()
|
||||
|
||||
if __is_future_or_coroutine(resource):
|
||||
|
@ -323,8 +332,12 @@ class DynamicContainer(Container):
|
|||
if futures:
|
||||
return asyncio.gather(*futures)
|
||||
|
||||
def shutdown_resources(self):
|
||||
def shutdown_resources(self, resource_type=providers.Resource):
|
||||
"""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:
|
||||
|
@ -355,7 +368,7 @@ class DynamicContainer(Container):
|
|||
for resource in resources_to_shutdown:
|
||||
resource.shutdown()
|
||||
|
||||
resources = list(self.traverse(types=[providers.Resource]))
|
||||
resources = list(self.traverse(types=[resource_type]))
|
||||
if any(resource.is_async_mode_enabled() for resource in resources):
|
||||
return _async_ordered_shutdown(resources)
|
||||
else:
|
||||
|
|
|
@ -7,7 +7,6 @@ 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.',
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
from typing import Awaitable as _Awaitable
|
||||
from typing import Any, Awaitable as _Awaitable, TypeVar
|
||||
|
||||
from dependency_injector import providers
|
||||
|
||||
class Application(providers.Singleton): ...
|
||||
class Extension(providers.Singleton): ...
|
||||
class Middleware(providers.DelegatedCallable): ...
|
||||
class MiddlewareFactory(providers.Factory): ...
|
||||
T = TypeVar("T")
|
||||
|
||||
class View(providers.Callable):
|
||||
def as_view(self) -> _Awaitable: ...
|
||||
class Application(providers.Singleton[T]): ...
|
||||
class Extension(providers.Singleton[T]): ...
|
||||
class Middleware(providers.DelegatedCallable[T]): ...
|
||||
class MiddlewareFactory(providers.Factory[T]): ...
|
||||
|
||||
class ClassBasedView(providers.Factory):
|
||||
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]: ...
|
||||
|
|
|
@ -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 providers, errors
|
||||
|
||||
from dependency_injector import errors, providers
|
||||
|
||||
warnings.warn(
|
||||
'Module "dependency_injector.ext.flask" is deprecated since '
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
from typing import Union, Optional, Callable as _Callable, Any
|
||||
from typing import Any, Callable as _Callable, Optional, TypeVar, Union
|
||||
|
||||
from flask.wrappers import Request
|
||||
|
||||
from flask import request as flask_request
|
||||
from dependency_injector import providers
|
||||
|
||||
request: providers.Object[flask_request]
|
||||
request: providers.Object[Request]
|
||||
T = TypeVar("T")
|
||||
|
||||
class Application(providers.Singleton): ...
|
||||
class Extension(providers.Singleton): ...
|
||||
class Application(providers.Singleton[T]): ...
|
||||
class Extension(providers.Singleton[T]): ...
|
||||
|
||||
class View(providers.Callable):
|
||||
def as_view(self) -> _Callable[..., Any]: ...
|
||||
class View(providers.Callable[T]):
|
||||
def as_view(self) -> _Callable[..., T]: ...
|
||||
|
||||
class ClassBasedView(providers.Factory):
|
||||
def as_view(self, name: str) -> _Callable[..., Any]: ...
|
||||
class ClassBasedView(providers.Factory[T]):
|
||||
def as_view(self, name: str) -> _Callable[..., T]: ...
|
||||
|
||||
def as_view(
|
||||
provider: Union[View, ClassBasedView], name: Optional[str] = None
|
||||
) -> _Callable[..., Any]: ...
|
||||
provider: Union[View[T], ClassBasedView[T]], name: Optional[str] = None
|
||||
) -> _Callable[..., T]: ...
|
||||
|
|
|
@ -697,3 +697,10 @@ 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
|
||||
|
|
|
@ -15,8 +15,11 @@ import re
|
|||
import sys
|
||||
import threading
|
||||
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:
|
||||
from inspect import _is_coroutine_mark as _is_coroutine_marker
|
||||
|
@ -3598,6 +3601,17 @@ 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."""
|
||||
|
@ -3653,6 +3667,12 @@ 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
|
||||
|
||||
|
@ -3753,28 +3773,21 @@ cdef class Resource(Provider):
|
|||
"""Shutdown resource."""
|
||||
if not self._initialized:
|
||||
if self._async_mode == ASYNC_MODE_ENABLED:
|
||||
result = asyncio.Future()
|
||||
result.set_result(None)
|
||||
return result
|
||||
return NULL_AWAITABLE
|
||||
return
|
||||
|
||||
if self._shutdowner:
|
||||
try:
|
||||
shutdown = self._shutdowner(self._resource)
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
if inspect.isawaitable(shutdown):
|
||||
return self._create_shutdown_future(shutdown)
|
||||
future = self._shutdowner(None, None, None)
|
||||
|
||||
if __is_future_or_coroutine(future):
|
||||
return ensure_future(self._shutdown_async(future))
|
||||
|
||||
self._resource = None
|
||||
self._initialized = False
|
||||
self._shutdowner = None
|
||||
|
||||
if self._async_mode == ASYNC_MODE_ENABLED:
|
||||
result = asyncio.Future()
|
||||
result.set_result(None)
|
||||
return result
|
||||
return NULL_AWAITABLE
|
||||
|
||||
@property
|
||||
def related(self):
|
||||
|
@ -3784,165 +3797,75 @@ 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
|
||||
|
||||
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,
|
||||
)
|
||||
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):
|
||||
self._initialized = True
|
||||
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._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__'):
|
||||
self._initialized = True
|
||||
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,
|
||||
)
|
||||
self._resource = resource = ensure_future(self._handle_async_cm(obj))
|
||||
return resource
|
||||
else:
|
||||
raise Error("Unknown type of resource initializer")
|
||||
self._resource = obj
|
||||
self._shutdowner = None
|
||||
|
||||
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 not isinstance(instance, type):
|
||||
return
|
||||
from . import resources
|
||||
return issubclass(instance, resources.Resource)
|
||||
|
||||
@staticmethod
|
||||
def _is_async_resource_subclass(instance):
|
||||
if not isinstance(instance, type):
|
||||
return
|
||||
from . import resources
|
||||
return issubclass(instance, resources.AsyncResource)
|
||||
|
||||
|
||||
cdef class Container(Provider):
|
||||
"""Container provider provides an instance of declarative container.
|
||||
|
@ -4993,14 +4916,6 @@ 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
|
||||
|
|
|
@ -1,23 +1,54 @@
|
|||
"""Resources module."""
|
||||
|
||||
import abc
|
||||
from typing import TypeVar, Generic, Optional
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from typing import Any, ClassVar, Generic, Optional, Tuple, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Resource(Generic[T], metaclass=abc.ABCMeta):
|
||||
class Resource(Generic[T], metaclass=ABCMeta):
|
||||
__slots__: ClassVar[Tuple[str, ...]] = ("args", "kwargs", "obj")
|
||||
|
||||
@abc.abstractmethod
|
||||
def init(self, *args, **kwargs) -> Optional[T]: ...
|
||||
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]: ...
|
||||
|
||||
def shutdown(self, resource: Optional[T]) -> None: ...
|
||||
|
||||
def __enter__(self) -> Optional[T]:
|
||||
self.obj = obj = self.init(*self.args, **self.kwargs)
|
||||
return obj
|
||||
|
||||
class AsyncResource(Generic[T], metaclass=abc.ABCMeta):
|
||||
def __exit__(self, *exc_info: Any) -> None:
|
||||
self.shutdown(self.obj)
|
||||
self.obj = None
|
||||
|
||||
@abc.abstractmethod
|
||||
async def init(self, *args, **kwargs) -> Optional[T]: ...
|
||||
|
||||
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]: ...
|
||||
|
||||
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
|
||||
|
|
|
@ -10,6 +10,7 @@ from types import ModuleType
|
|||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AsyncIterator,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
|
@ -24,7 +25,17 @@ from typing import (
|
|||
cast,
|
||||
)
|
||||
|
||||
from typing_extensions import Self
|
||||
try:
|
||||
from typing import Self
|
||||
except ImportError:
|
||||
from typing_extensions import Self
|
||||
|
||||
try:
|
||||
from functools import cache
|
||||
except ImportError:
|
||||
from functools import lru_cache
|
||||
|
||||
cache = lru_cache(maxsize=None)
|
||||
|
||||
# Hotfix, see: https://github.com/ets-labs/python-dependency-injector/issues/362
|
||||
if sys.version_info >= (3, 9):
|
||||
|
@ -48,10 +59,33 @@ else:
|
|||
return None
|
||||
|
||||
|
||||
MARKER_EXTRACTORS = []
|
||||
|
||||
try:
|
||||
import fastapi.params
|
||||
from fastapi.params import Depends as FastAPIDepends
|
||||
except ImportError:
|
||||
fastapi = None
|
||||
pass
|
||||
else:
|
||||
|
||||
def extract_marker_from_fastapi(param: Any) -> Any:
|
||||
if isinstance(param, FastAPIDepends):
|
||||
return param.dependency
|
||||
return None
|
||||
|
||||
MARKER_EXTRACTORS.append(extract_marker_from_fastapi)
|
||||
|
||||
try:
|
||||
from fast_depends.dependencies import Depends as FastDepends
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
|
||||
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:
|
||||
|
@ -65,8 +99,7 @@ try:
|
|||
except ImportError:
|
||||
werkzeug = None
|
||||
|
||||
|
||||
from . import providers
|
||||
from . import providers # noqa: E402
|
||||
|
||||
__all__ = (
|
||||
"wire",
|
||||
|
@ -409,6 +442,7 @@ 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 []
|
||||
|
@ -449,6 +483,9 @@ 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
|
||||
*,
|
||||
|
@ -592,18 +629,18 @@ def _extract_marker(parameter: inspect.Parameter) -> Optional["_Marker"]:
|
|||
else:
|
||||
marker = parameter.default
|
||||
|
||||
if not isinstance(marker, _Marker) and not _is_fastapi_depends(marker):
|
||||
for marker_extractor in MARKER_EXTRACTORS:
|
||||
if _marker := marker_extractor(marker):
|
||||
marker = _marker
|
||||
break
|
||||
|
||||
if not isinstance(marker, _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]]:
|
||||
|
@ -708,6 +745,8 @@ 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)
|
||||
|
||||
|
@ -717,10 +756,6 @@ 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)
|
||||
|
||||
|
@ -1023,36 +1058,41 @@ _inspect_filter = InspectFilter()
|
|||
_loader = AutoLoader()
|
||||
|
||||
# Optimizations
|
||||
from ._cwiring import _async_inject # noqa
|
||||
from ._cwiring import _sync_inject # noqa
|
||||
from ._cwiring import DependencyResolver # noqa: E402
|
||||
|
||||
|
||||
# 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, **kwargs):
|
||||
return await _async_inject(
|
||||
fn,
|
||||
args,
|
||||
kwargs,
|
||||
patched.injections,
|
||||
patched.closing,
|
||||
)
|
||||
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
|
||||
|
||||
return cast(F, _patched)
|
||||
|
||||
|
||||
def _get_sync_patched(fn: F, patched: PatchedCallable) -> F:
|
||||
@functools.wraps(fn)
|
||||
def _patched(*args, **kwargs):
|
||||
return _sync_inject(
|
||||
fn,
|
||||
args,
|
||||
kwargs,
|
||||
patched.injections,
|
||||
patched.closing,
|
||||
)
|
||||
def _patched(*args: Any, **raw_kwargs: Any) -> Any:
|
||||
resolver = DependencyResolver(raw_kwargs, patched.injections, patched.closing)
|
||||
|
||||
with resolver as kwargs:
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
return cast(F, _patched)
|
||||
|
||||
|
@ -1078,3 +1118,8 @@ def _get_members_and_annotated(obj: Any) -> Iterable[Tuple[str, Any]]:
|
|||
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()
|
||||
|
|
|
@ -145,3 +145,121 @@ 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"]
|
||||
|
|
|
@ -325,6 +325,19 @@ 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)
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
import asyncio
|
||||
import inspect
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any
|
||||
|
||||
from dependency_injector import containers, providers, resources
|
||||
from pytest import mark, raises
|
||||
|
||||
from dependency_injector import containers, providers, resources
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
async def test_init_async_function():
|
||||
|
@ -70,6 +71,46 @@ 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()
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
import decimal
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from typing import Any
|
||||
|
||||
from dependency_injector import containers, providers, resources, errors
|
||||
from pytest import raises, mark
|
||||
from pytest import mark, raises
|
||||
|
||||
from dependency_injector import containers, errors, providers, resources
|
||||
|
||||
|
||||
def init_fn(*args, **kwargs):
|
||||
|
@ -123,6 +125,41 @@ 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
|
||||
|
@ -190,7 +227,7 @@ def test_init_class_abc_shutdown_definition_is_not_required():
|
|||
|
||||
def test_init_not_callable():
|
||||
provider = providers.Resource(1)
|
||||
with raises(errors.Error):
|
||||
with raises(TypeError, match=r"object is not callable"):
|
||||
provider.init()
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import asyncio
|
||||
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import inject, Provide, Closing
|
||||
from dependency_injector.wiring import Closing, Provide, inject
|
||||
|
||||
|
||||
class TestResource:
|
||||
|
@ -42,6 +44,15 @@ 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]],
|
||||
|
|
39
tests/unit/samples/wiringfastdepends/sample.py
Normal file
39
tests/unit/samples/wiringfastdepends/sample.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
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__]])
|
|
@ -32,6 +32,23 @@ 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()
|
||||
|
|
46
tests/unit/wiring/test_cache.py
Normal file
46
tests/unit/wiring/test_cache.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
"""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
|
9
tests/unit/wiring/test_fastdepends.py
Normal file
9
tests/unit/wiring/test_fastdepends.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
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
|
Loading…
Reference in New Issue
Block a user