mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 01:26:51 +03:00
Async resources and injections (#352)
* Add support of async injections into wiring * Add support of async functions and async generators for resources * Update resource provider typing stub for stutdown * Add resource base class for async resources * Fix tests * Add tests for async injections in wiring @inject * Refactor provider tests * Add tests for async resources * Rework async resources callbacks to .add_done_callback() style (fixes pypy3 issue) * Add awaits into async resource class test * Refactor FastAPI tests * Implement async resources initialization in container * Move container async resource tests to a separate module for Python 3.6+ * Fix init async resources in container on Python 2 * Add first dirty async injections implementation * Fix isawaitable error * Turm asyncio import to conditional for safer Py2 usage * Refactor kwargs injections * Implement positional injections, add tests and make refactoring * Implement attribute injections and add tests * Add singleton implementation + tests for all singleton types * Implement injections in thread-local and thread-safe singleton providers * Update .provided + fix resource concurent initialization issue * Implement async mode for Dependency provider * Add async mode for the provider * Add overload for Factory typing * Add typing stubs for async resource * Refactor abstract* providers __call__() * Add async mode API + tests * Add typing stubs & tests for async mode API * Add tests for async mode auto configuration * Refactor Provider.__call__() to use async mode api * Refactor Dependency provider to use async mode api * Add tests for Dependency provider async mode * Add support of async mode for FactoryAggregate provider + tests * Refactor Singleton provider to use async mode api * Refactor ThreadSafeSingleton provider to use async mode api * Refactor ThreadLocalSingleton provider to use async mode api * Finish Singleton refactoring to use async mode api * Refactor Resource provider to use async mode api * Add Provider.async_() method + tests * Add typing stubs for async_() method + tests * Refactor Singleton typing stubs to return singleton from argument methods * Refactor provider typing stubs * Improve resource typing stub * Add tests for async context kwargs injections * Fix typo in resource provider tests * Cover shutdown of not initialized resource * Add test to cover resource initialization with an error * Fix Singleton and ThreadLocalSingleton to handle initialization errors * Add FastAPI + Redis example * Make cosmetic fixes to FastAPI + Redis example * Add missing development requirements * Update module docblock in fastapi + redis example * Add FastAPI + Redis example docs * Add references to FastAPI + Redis example * Refactor resource docs * Add asynchronous resources docs * Refactor wiring docs * Add async injections docs for wiring * Add async injections page and update docs index, readme, and key features pages * Add providers async injections example * Add docs on provider async mode enabling * Reword async provider docs * Add provider async mode docs * Add cross links to async docs * Mute flake8 errors in async provider examples * Update changelog * Make cosmetic fix to containers.pyx
This commit is contained in:
parent
9f6d2bb522
commit
feed916f46
|
@ -70,6 +70,8 @@ Key features of the ``Dependency Injector``:
|
||||||
- **Wiring**. Injects dependencies into functions and methods. Helps integrating with
|
- **Wiring**. Injects dependencies into functions and methods. Helps integrating with
|
||||||
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc.
|
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc.
|
||||||
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
|
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.
|
||||||
|
- **Asynchronous**. Supports asynchronous injections.
|
||||||
|
See `Asynchronous injections <https://python-dependency-injector.ets-labs.org/providers/async.html>`_.
|
||||||
- **Typing**. Provides typing stubs, ``mypy``-friendly.
|
- **Typing**. Provides typing stubs, ``mypy``-friendly.
|
||||||
See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
|
See `Typing and mypy <https://python-dependency-injector.ets-labs.org/providers/typing_mypy.html>`_.
|
||||||
- **Performance**. Fast. Written in ``Cython``.
|
- **Performance**. Fast. Written in ``Cython``.
|
||||||
|
@ -157,6 +159,8 @@ Choose one of the following:
|
||||||
- `Flask example <https://python-dependency-injector.ets-labs.org/examples/flask.html>`_
|
- `Flask example <https://python-dependency-injector.ets-labs.org/examples/flask.html>`_
|
||||||
- `Aiohttp example <https://python-dependency-injector.ets-labs.org/examples/aiohttp.html>`_
|
- `Aiohttp example <https://python-dependency-injector.ets-labs.org/examples/aiohttp.html>`_
|
||||||
- `Sanic example <https://python-dependency-injector.ets-labs.org/examples/sanic.html>`_
|
- `Sanic example <https://python-dependency-injector.ets-labs.org/examples/sanic.html>`_
|
||||||
|
- `FastAPI example <https://python-dependency-injector.ets-labs.org/examples/fastapi.html>`_
|
||||||
|
- `FastAPI + Redis example <https://python-dependency-injector.ets-labs.org/examples/fastapi-redis.html>`_
|
||||||
|
|
||||||
Tutorials
|
Tutorials
|
||||||
---------
|
---------
|
||||||
|
@ -223,4 +227,3 @@ Want to contribute?
|
||||||
.. |tell| unicode:: U+1F4AC .. tell sign
|
.. |tell| unicode:: U+1F4AC .. tell sign
|
||||||
.. |fork| unicode:: U+1F500 .. fork sign
|
.. |fork| unicode:: U+1F500 .. fork sign
|
||||||
.. |pull| unicode:: U+2B05 U+FE0F .. pull sign
|
.. |pull| unicode:: U+2B05 U+FE0F .. pull sign
|
||||||
|
|
||||||
|
|
98
docs/examples/fastapi-redis.rst
Normal file
98
docs/examples/fastapi-redis.rst
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
.. _fastapi-redis-example:
|
||||||
|
|
||||||
|
FastAPI + Redis example
|
||||||
|
=======================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,Dependency Injection,FastAPI,Redis,Example
|
||||||
|
:description: This example demonstrates a usage of the FastAPI, Redis, and Dependency Injector.
|
||||||
|
|
||||||
|
This example shows how to use ``Dependency Injector`` with `FastAPI <https://fastapi.tiangolo.com/>`_ and
|
||||||
|
`Redis <https://redis.io/>`_.
|
||||||
|
|
||||||
|
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-redis>`_.
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
- Provider :ref:`async-injections`
|
||||||
|
- Resource provider :ref:`resource-async-initializers`
|
||||||
|
- Wiring :ref:`async-injections-wiring`
|
||||||
|
|
||||||
|
Application structure
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Application has next structure:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
./
|
||||||
|
├── fastapiredis/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── application.py
|
||||||
|
│ ├── containers.py
|
||||||
|
│ ├── redis.py
|
||||||
|
│ ├── services.py
|
||||||
|
│ └── tests.py
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── Dockerfile
|
||||||
|
└── requirements.txt
|
||||||
|
|
||||||
|
Redis
|
||||||
|
-----
|
||||||
|
|
||||||
|
Module ``redis`` defines Redis connection pool initialization and shutdown. See ``fastapiredis/redis.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/redis.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Service
|
||||||
|
-------
|
||||||
|
|
||||||
|
Module ``services`` contains example service. Service has a dependency on Redis connection pool.
|
||||||
|
It uses it for getting and setting a key asynchronously. Real life service will do something more meaningful.
|
||||||
|
See ``fastapiredis/services.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/services.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Container
|
||||||
|
---------
|
||||||
|
|
||||||
|
Declarative container wires example service with Redis connection pool. See ``fastapiredis/containers.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/containers.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Application
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Module ``application`` creates ``FastAPI`` app, setup endpoint, and init container.
|
||||||
|
|
||||||
|
Endpoint ``index`` has a dependency on example service. The dependency is injected using :ref:`wiring` feature.
|
||||||
|
|
||||||
|
Listing of ``fastapiredis/application.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/application.py
|
||||||
|
:language: python
|
||||||
|
|
||||||
|
Tests
|
||||||
|
-----
|
||||||
|
|
||||||
|
Tests use :ref:`provider-overriding` feature to replace example service with a mock. See ``fastapiredis/tests.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/miniapps/fastapi-redis/fastapiredis/tests.py
|
||||||
|
:language: python
|
||||||
|
:emphasize-lines: 24
|
||||||
|
|
||||||
|
Sources
|
||||||
|
-------
|
||||||
|
|
||||||
|
The source code is available on the `Github <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-redis>`_.
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
- Provider :ref:`async-injections`
|
||||||
|
- Resource provider :ref:`resource-async-initializers`
|
||||||
|
- Wiring :ref:`async-injections-wiring`
|
||||||
|
|
||||||
|
.. disqus::
|
|
@ -19,5 +19,6 @@ Explore the examples to see the ``Dependency Injector`` in action.
|
||||||
aiohttp
|
aiohttp
|
||||||
sanic
|
sanic
|
||||||
fastapi
|
fastapi
|
||||||
|
fastapi-redis
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -78,6 +78,7 @@ Key features of the ``Dependency Injector``:
|
||||||
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
|
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
|
||||||
- **Wiring**. Injects dependencies into functions and methods. Helps integrating with
|
- **Wiring**. Injects dependencies into functions and methods. Helps integrating with
|
||||||
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
|
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
|
||||||
|
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
|
||||||
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
|
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
|
||||||
- **Performance**. Fast. Written in ``Cython``.
|
- **Performance**. Fast. Written in ``Cython``.
|
||||||
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
|
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
|
||||||
|
|
|
@ -287,6 +287,7 @@ Choose one of the following as a next step:
|
||||||
- :ref:`aiohttp-example`
|
- :ref:`aiohttp-example`
|
||||||
- :ref:`sanic-example`
|
- :ref:`sanic-example`
|
||||||
- :ref:`fastapi-example`
|
- :ref:`fastapi-example`
|
||||||
|
- :ref:`fastapi-redis-example`
|
||||||
- Pass the tutorials:
|
- Pass the tutorials:
|
||||||
- :ref:`flask-tutorial`
|
- :ref:`flask-tutorial`
|
||||||
- :ref:`aiohttp-tutorial`
|
- :ref:`aiohttp-tutorial`
|
||||||
|
|
|
@ -24,6 +24,7 @@ Key features of the ``Dependency Injector``:
|
||||||
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
|
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
|
||||||
- **Wiring**. Injects dependencies into functions and methods. Helps integrating with
|
- **Wiring**. Injects dependencies into functions and methods. Helps integrating with
|
||||||
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
|
other frameworks: Django, Flask, Aiohttp, Sanic, FastAPI, etc. See :ref:`wiring`.
|
||||||
|
- **Asynchronous**. Supports asynchronous injections. See :ref:`async-injections`.
|
||||||
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
|
- **Typing**. Provides typing stubs, ``mypy``-friendly. See :ref:`provider-typing`.
|
||||||
- **Performance**. Fast. Written in ``Cython``.
|
- **Performance**. Fast. Written in ``Cython``.
|
||||||
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
|
- **Maturity**. Mature and production-ready. Well-tested, documented and supported.
|
||||||
|
|
|
@ -9,6 +9,10 @@ follows `Semantic versioning`_
|
||||||
|
|
||||||
Development version
|
Development version
|
||||||
-------------------
|
-------------------
|
||||||
|
- Add support of async injections for providers.
|
||||||
|
- Add support of async injections for wiring.
|
||||||
|
- Add support of async initializers for ``Resource`` provider.
|
||||||
|
- Add ``FastAPI`` + ``Redis`` example.
|
||||||
- Add ARM wheel builds.
|
- Add ARM wheel builds.
|
||||||
See issue `#342 <https://github.com/ets-labs/python-dependency-injector/issues/342>`_ for details.
|
See issue `#342 <https://github.com/ets-labs/python-dependency-injector/issues/342>`_ for details.
|
||||||
- Fix a typo in `ext.flask` deprecation warning.
|
- Fix a typo in `ext.flask` deprecation warning.
|
||||||
|
|
108
docs/providers/async.rst
Normal file
108
docs/providers/async.rst
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
.. _async-injections:
|
||||||
|
|
||||||
|
Asynchronous injections
|
||||||
|
=======================
|
||||||
|
|
||||||
|
.. meta::
|
||||||
|
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Providers,Async,Injections,Asynchronous,Await,
|
||||||
|
Asyncio
|
||||||
|
:description: Dependency Injector providers support asynchronous injections. This page
|
||||||
|
demonstrates how make asynchronous dependency injections in Python.
|
||||||
|
|
||||||
|
Providers support asynchronous injections.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/async.py
|
||||||
|
:language: python
|
||||||
|
:emphasize-lines: 26-29
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
If provider has any awaitable injections it switches into async mode. In async mode provider always returns awaitable.
|
||||||
|
This causes a cascade effect:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
provider1() <── Async mode enabled <──┐
|
||||||
|
│ │
|
||||||
|
├──> provider2() │
|
||||||
|
│ │
|
||||||
|
├──> provider3() <── Async mode enabled <──┤
|
||||||
|
│ │ │
|
||||||
|
│ └──> provider4() <── Async provider ───────┘
|
||||||
|
│
|
||||||
|
└──> provider5()
|
||||||
|
│
|
||||||
|
└──> provider6()
|
||||||
|
|
||||||
|
In async mode provider prepares injections asynchronously.
|
||||||
|
|
||||||
|
If provider has multiple awaitable dependencies, it will run them concurrently. Provider will wait until all
|
||||||
|
dependencies are ready and inject them afterwards.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
provider1()
|
||||||
|
│
|
||||||
|
├──> provider2() <── Async mode enabled
|
||||||
|
│
|
||||||
|
├──> provider3() <── Async mode enabled
|
||||||
|
│
|
||||||
|
└──> provider4() <── Async mode enabled
|
||||||
|
|
||||||
|
Here is what provider will do for the previous example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
injections = await asyncio.gather(
|
||||||
|
provider2(),
|
||||||
|
provider3(),
|
||||||
|
provider4(),
|
||||||
|
)
|
||||||
|
await provider1(*injections)
|
||||||
|
|
||||||
|
Overriding behaviour
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
In async mode provider always returns awaitable. It applies to the overriding too. If provider in async mode is
|
||||||
|
overridden by a provider that doesn't return awaitable result, the result will be wrapped into awaitable.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/async_overriding.py
|
||||||
|
:language: python
|
||||||
|
:emphasize-lines: 19-24
|
||||||
|
:lines: 3-
|
||||||
|
|
||||||
|
Async mode mechanics and API
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
By default provider's async mode is undefined.
|
||||||
|
|
||||||
|
When provider async mode is undefined, provider will automatically select the mode during the next call.
|
||||||
|
If the result is awaitable, provider will enable async mode, if not - disable it.
|
||||||
|
|
||||||
|
If provider async mode is enabled, provider always returns awaitable. If the result is not awaitable,
|
||||||
|
provider wraps it into awaitable explicitly. You can safely ``await`` provider in async mode.
|
||||||
|
|
||||||
|
If provider async mode is disabled, provider behaves the regular way. It doesn't do async injections
|
||||||
|
preparation or non-awaitables to awaitables conversion.
|
||||||
|
|
||||||
|
Once provider async mode is enabled or disabled, provider will stay in this state. No automatic switching
|
||||||
|
will be done.
|
||||||
|
|
||||||
|
.. image:: images/async_mode.png
|
||||||
|
|
||||||
|
You can also use following methods to change provider's async mode manually:
|
||||||
|
|
||||||
|
- ``Provider.enable_async_mode()``
|
||||||
|
- ``Provider.disable_async_mode()``
|
||||||
|
- ``Provider.reset_async_mode()``
|
||||||
|
|
||||||
|
To check the state of provider's async mode use:
|
||||||
|
|
||||||
|
- ``Provider.is_async_mode_enabled()``
|
||||||
|
- ``Provider.is_async_mode_disabled()``
|
||||||
|
- ``Provider.is_async_mode_undefined()``
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
- Wiring :ref:`async-injections-wiring`
|
||||||
|
- Resource provider :ref:`resource-async-initializers`
|
||||||
|
- :ref:`fastapi-redis-example`
|
BIN
docs/providers/images/async_mode.png
Normal file
BIN
docs/providers/images/async_mode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -51,4 +51,5 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
|
||||||
overriding
|
overriding
|
||||||
provided_instance
|
provided_instance
|
||||||
custom
|
custom
|
||||||
|
async
|
||||||
typing_mypy
|
typing_mypy
|
||||||
|
|
|
@ -21,7 +21,7 @@ Resource provider
|
||||||
Resource providers help to initialize and configure logging, event loop, thread or process pool, etc.
|
Resource providers help to initialize and configure logging, event loop, thread or process pool, etc.
|
||||||
|
|
||||||
Resource provider is similar to ``Singleton``. Resource initialization happens only once.
|
Resource provider is similar to ``Singleton``. Resource initialization happens only once.
|
||||||
You can do injections and use provided instance the same way like you do with any other provider.
|
You can make injections and use provided instance the same way like you do with any other provider.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
:emphasize-lines: 12
|
:emphasize-lines: 12
|
||||||
|
@ -40,7 +40,7 @@ You can do injections and use provided instance the same way like you do with an
|
||||||
executor=thread_pool,
|
executor=thread_pool,
|
||||||
)
|
)
|
||||||
|
|
||||||
Container has an interface to initialize and shutdown all resources:
|
Container has an interface to initialize and shutdown all resources at once:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ Container has an interface to initialize and shutdown all resources:
|
||||||
container.init_resources()
|
container.init_resources()
|
||||||
container.shutdown_resources()
|
container.shutdown_resources()
|
||||||
|
|
||||||
You also can initialize and shutdown resources one-by-one using ``init()`` and
|
You can also initialize and shutdown resources one-by-one using ``init()`` and
|
||||||
``shutdown()`` methods of the provider:
|
``shutdown()`` methods of the provider:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -57,6 +57,10 @@ You also can initialize and shutdown resources one-by-one using ``init()`` and
|
||||||
container.thread_pool.init()
|
container.thread_pool.init()
|
||||||
container.thread_pool.shutdown()
|
container.thread_pool.shutdown()
|
||||||
|
|
||||||
|
When you call ``.shutdown()`` method on a resource provider, it will remove the reference to the initialized resource,
|
||||||
|
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 3 types of initializers:
|
||||||
|
|
||||||
- Function
|
- Function
|
||||||
|
@ -97,7 +101,7 @@ you configure global resource:
|
||||||
fname='logging.ini',
|
fname='logging.ini',
|
||||||
)
|
)
|
||||||
|
|
||||||
Function initializer does not support shutdown.
|
Function initializer does not provide a way to specify custom resource shutdown.
|
||||||
|
|
||||||
Generator initializer
|
Generator initializer
|
||||||
---------------------
|
---------------------
|
||||||
|
@ -235,4 +239,124 @@ The example above produces next output:
|
||||||
Shutdown service
|
Shutdown service
|
||||||
127.0.0.1 - - [29/Oct/2020 22:39:41] "GET / HTTP/1.1" 200 -
|
127.0.0.1 - - [29/Oct/2020 22:39:41] "GET / HTTP/1.1" 200 -
|
||||||
|
|
||||||
|
.. _resource-async-initializers:
|
||||||
|
|
||||||
|
Asynchronous initializers
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
When you write an asynchronous application, you might need to initialize resources asynchronously. Resource
|
||||||
|
provider supports asynchronous initialization and shutdown.
|
||||||
|
|
||||||
|
Asynchronous function initializer:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
async def init_async_resource(argument1=..., argument2=...):
|
||||||
|
return await connect()
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
resource = providers.Resource(
|
||||||
|
init_resource,
|
||||||
|
argument1=...,
|
||||||
|
argument2=...,
|
||||||
|
)
|
||||||
|
|
||||||
|
Asynchronous generator initializer:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
async def init_async_resource(argument1=..., argument2=...):
|
||||||
|
connection = await connect()
|
||||||
|
yield connection
|
||||||
|
await connection.close()
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
resource = providers.Resource(
|
||||||
|
init_async_resource,
|
||||||
|
argument1=...,
|
||||||
|
argument2=...,
|
||||||
|
)
|
||||||
|
|
||||||
|
Asynchronous subclass initializer:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from dependency_injector import resources
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncConnection(resources.AsyncResource):
|
||||||
|
|
||||||
|
async def init(self, argument1=..., argument2=...):
|
||||||
|
yield await connect()
|
||||||
|
|
||||||
|
async def shutdown(self, connection):
|
||||||
|
await connection.close()
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
resource = providers.Resource(
|
||||||
|
AsyncConnection,
|
||||||
|
argument1=...,
|
||||||
|
argument2=...,
|
||||||
|
)
|
||||||
|
|
||||||
|
When you use resource provider with asynchronous initializer you need to call its ``__call__()``,
|
||||||
|
``init()``, and ``shutdown()`` methods asynchronously:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
connection = providers.Resource(init_async_connection)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
container = Container()
|
||||||
|
connection = await container.connection()
|
||||||
|
connection = await container.connection.init()
|
||||||
|
connection = await container.connection.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
Container ``init_resources()`` and ``shutdown_resources()`` methods should be used asynchronously if there is
|
||||||
|
at least one asynchronous resource provider:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
connection1 = providers.Resource(init_async_connection)
|
||||||
|
|
||||||
|
connection2 = providers.Resource(init_sync_connection)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
container = Container()
|
||||||
|
await container.init_resources()
|
||||||
|
await container.shutdown_resources()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
- Provider :ref:`async-injections`
|
||||||
|
- Wiring :ref:`async-injections-wiring`
|
||||||
|
- :ref:`fastapi-redis-example`
|
||||||
|
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -167,21 +167,105 @@ You can use that in testing to re-create and re-wire a container before each tes
|
||||||
avoid re-wiring between tests.
|
avoid re-wiring between tests.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Python has a limitation on patching already imported individual members. To protect from errors
|
Python has a limitation on patching individually imported functions. To protect from errors
|
||||||
prefer an import of modules instead of individual members or make sure that imports happen
|
prefer importing modules to importing individual functions or make sure imports happen
|
||||||
after the wiring:
|
after the wiring:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Potential error:
|
||||||
|
|
||||||
|
from .module import fn
|
||||||
|
|
||||||
|
fn()
|
||||||
|
|
||||||
|
Instead use next:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# Always works:
|
||||||
|
|
||||||
from . import module
|
from . import module
|
||||||
|
|
||||||
module.fn()
|
module.fn()
|
||||||
|
|
||||||
# instead of
|
.. _async-injections-wiring:
|
||||||
|
|
||||||
from .module import fn
|
Asynchronous injections
|
||||||
|
-----------------------
|
||||||
|
|
||||||
fn()
|
Wiring feature supports asynchronous injections:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
db = providers.Resource(init_async_db_client)
|
||||||
|
|
||||||
|
cache = providers.Resource(init_async_cache_client)
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def main(
|
||||||
|
db: Database = Provide[Container.db],
|
||||||
|
cache: Cache = Provide[Container.cache],
|
||||||
|
):
|
||||||
|
...
|
||||||
|
|
||||||
|
When you call asynchronous function wiring prepares injections asynchronously.
|
||||||
|
Here is what it does for previous example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
db, cache = await asyncio.gather(
|
||||||
|
container.db(),
|
||||||
|
container.cache(),
|
||||||
|
)
|
||||||
|
|
||||||
|
await main(db=db, cache=cache)
|
||||||
|
|
||||||
|
You can also use ``Closing`` marker with the asynchronous ``Resource`` providers:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def main(
|
||||||
|
db: Database = Closing[Provide[Container.db]],
|
||||||
|
cache: Cache = Closing[Provide[Container.cache]],
|
||||||
|
):
|
||||||
|
...
|
||||||
|
|
||||||
|
Wiring does closing asynchronously:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
db, cache = await asyncio.gather(
|
||||||
|
container.db(),
|
||||||
|
container.cache(),
|
||||||
|
)
|
||||||
|
|
||||||
|
await main(db=db, cache=cache)
|
||||||
|
|
||||||
|
await asyncio.gather(
|
||||||
|
container.db.shutdown(),
|
||||||
|
container.cache.shutdown(),
|
||||||
|
)
|
||||||
|
|
||||||
|
See :ref:`Resources, wiring and per-function execution scope <resource-provider-wiring-closing>` for
|
||||||
|
details on ``Closing`` marker.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Wiring does not not convert asynchronous injections to synchronous.
|
||||||
|
|
||||||
|
It handles asynchronous injections only for ``async def`` functions. Asynchronous injections into
|
||||||
|
synchronous ``def`` function still work, but you need to take care of awaitables by your own.
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
- Provider :ref:`async-injections`
|
||||||
|
- Resource provider :ref:`resource-async-initializers`
|
||||||
|
- :ref:`fastapi-redis-example`
|
||||||
|
|
||||||
Integration with other frameworks
|
Integration with other frameworks
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
@ -211,5 +295,6 @@ Take a look at other application examples:
|
||||||
- :ref:`aiohttp-example`
|
- :ref:`aiohttp-example`
|
||||||
- :ref:`sanic-example`
|
- :ref:`sanic-example`
|
||||||
- :ref:`fastapi-example`
|
- :ref:`fastapi-example`
|
||||||
|
- :ref:`fastapi-redis-example`
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
10
examples/miniapps/fastapi-redis/Dockerfile
Normal file
10
examples/miniapps/fastapi-redis/Dockerfile
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
FROM python:3.8-buster
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /code
|
||||||
|
COPY . /code/
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
CMD ["uvicorn", "fastapiredis.application:app", "--host", "0.0.0.0"]
|
89
examples/miniapps/fastapi-redis/README.rst
Normal file
89
examples/miniapps/fastapi-redis/README.rst
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
FastAPI + Redis + Dependency Injector Example
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
This is a `FastAPI <https://docs.python.org/3/library/asyncio.html>`_
|
||||||
|
+ `Redis <https://redis.io/>`_
|
||||||
|
+ `Dependency Injector <https://python-dependency-injector.ets-labs.org/>`_ example application.
|
||||||
|
|
||||||
|
Run
|
||||||
|
---
|
||||||
|
|
||||||
|
Build the Docker image:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
Run the docker-compose environment:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
docker-compose up
|
||||||
|
|
||||||
|
The output should be something like:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
redis_1 | 1:C 04 Jan 2021 02:42:14.115 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
|
||||||
|
redis_1 | 1:C 04 Jan 2021 02:42:14.115 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
|
||||||
|
redis_1 | 1:C 04 Jan 2021 02:42:14.115 # Configuration loaded
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:14.116 * Running mode=standalone, port=6379.
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:14.116 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:14.116 # Server initialized
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:14.117 * Loading RDB produced by version 6.0.9
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:14.117 * RDB age 1 seconds
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:14.117 * RDB memory usage when created 0.77 Mb
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:14.117 * DB loaded from disk: 0.000 seconds
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:14.117 * Ready to accept connections
|
||||||
|
redis_1 | 1:signal-handler (1609728137) Received SIGTERM scheduling shutdown...
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:17.984 # User requested shutdown...
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:17.984 # Redis is now ready to exit, bye bye...
|
||||||
|
redis_1 | 1:C 04 Jan 2021 02:42:22.035 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
|
||||||
|
redis_1 | 1:C 04 Jan 2021 02:42:22.035 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
|
||||||
|
redis_1 | 1:C 04 Jan 2021 02:42:22.035 # Configuration loaded
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * Running mode=standalone, port=6379.
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:22.037 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:22.037 # Server initialized
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * Loading RDB produced by version 6.0.9
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * RDB age 9 seconds
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * RDB memory usage when created 0.77 Mb
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * DB loaded from disk: 0.000 seconds
|
||||||
|
redis_1 | 1:M 04 Jan 2021 02:42:22.037 * Ready to accept connections
|
||||||
|
example_1 | INFO: Started server process [1]
|
||||||
|
example_1 | INFO: Waiting for application startup.
|
||||||
|
example_1 | INFO: Application startup complete.
|
||||||
|
example_1 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
|
||||||
|
|
||||||
|
Test
|
||||||
|
----
|
||||||
|
|
||||||
|
This application comes with the unit tests.
|
||||||
|
|
||||||
|
To run the tests do:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
docker-compose run --rm example py.test fastapiredis/tests.py --cov=fastapiredis
|
||||||
|
|
||||||
|
The output should be something like:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
platform linux -- Python 3.8.6, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
|
||||||
|
rootdir: /code
|
||||||
|
plugins: cov-2.10.1, asyncio-0.14.0
|
||||||
|
collected 1 item
|
||||||
|
|
||||||
|
fastapiredis/tests.py . [100%]
|
||||||
|
|
||||||
|
----------- coverage: platform linux, python 3.8.6-final-0 -----------
|
||||||
|
Name Stmts Miss Cover
|
||||||
|
-------------------------------------------------
|
||||||
|
fastapiredis/__init__.py 0 0 100%
|
||||||
|
fastapiredis/application.py 15 0 100%
|
||||||
|
fastapiredis/containers.py 6 0 100%
|
||||||
|
fastapiredis/redis.py 7 4 43%
|
||||||
|
fastapiredis/services.py 7 3 57%
|
||||||
|
fastapiredis/tests.py 18 0 100%
|
||||||
|
-------------------------------------------------
|
||||||
|
TOTAL 53 7 87%
|
21
examples/miniapps/fastapi-redis/docker-compose.yml
Normal file
21
examples/miniapps/fastapi-redis/docker-compose.yml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
version: "3.7"
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
example:
|
||||||
|
build: .
|
||||||
|
environment:
|
||||||
|
REDIS_HOST: "redis"
|
||||||
|
REDIS_PASSWORD: "password"
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- "./:/code"
|
||||||
|
depends_on:
|
||||||
|
- "redis"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
command: ["redis-server", "--requirepass", "password"]
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
1
examples/miniapps/fastapi-redis/fastapiredis/__init__.py
Normal file
1
examples/miniapps/fastapi-redis/fastapiredis/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Top-level package."""
|
25
examples/miniapps/fastapi-redis/fastapiredis/application.py
Normal file
25
examples/miniapps/fastapi-redis/fastapiredis/application.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
"""Application module."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Depends
|
||||||
|
from dependency_injector.wiring import inject, Provide
|
||||||
|
|
||||||
|
from .containers import Container
|
||||||
|
from .services import Service
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
@app.api_route('/')
|
||||||
|
@inject
|
||||||
|
async def index(service: Service = Depends(Provide[Container.service])):
|
||||||
|
value = await service.process()
|
||||||
|
return {'result': value}
|
||||||
|
|
||||||
|
|
||||||
|
container = Container()
|
||||||
|
container.config.redis_host.from_env('REDIS_HOST', 'localhost')
|
||||||
|
container.config.redis_password.from_env('REDIS_PASSWORD', 'password')
|
||||||
|
container.wire(modules=[sys.modules[__name__]])
|
21
examples/miniapps/fastapi-redis/fastapiredis/containers.py
Normal file
21
examples/miniapps/fastapi-redis/fastapiredis/containers.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
"""Containers module."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
from . import redis, services
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
config = providers.Configuration()
|
||||||
|
|
||||||
|
redis_pool = providers.Resource(
|
||||||
|
redis.init_redis_pool,
|
||||||
|
host=config.redis_host,
|
||||||
|
password=config.redis_password,
|
||||||
|
)
|
||||||
|
|
||||||
|
service = providers.Factory(
|
||||||
|
services.Service,
|
||||||
|
redis=redis_pool,
|
||||||
|
)
|
12
examples/miniapps/fastapi-redis/fastapiredis/redis.py
Normal file
12
examples/miniapps/fastapi-redis/fastapiredis/redis.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
"""Redis client module."""
|
||||||
|
|
||||||
|
from typing import AsyncIterator
|
||||||
|
|
||||||
|
from aioredis import create_redis_pool, Redis
|
||||||
|
|
||||||
|
|
||||||
|
async def init_redis_pool(host: str, password: str) -> AsyncIterator[Redis]:
|
||||||
|
pool = await create_redis_pool(f'redis://{host}', password=password)
|
||||||
|
yield pool
|
||||||
|
pool.close()
|
||||||
|
await pool.wait_closed()
|
12
examples/miniapps/fastapi-redis/fastapiredis/services.py
Normal file
12
examples/miniapps/fastapi-redis/fastapiredis/services.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
"""Services module."""
|
||||||
|
|
||||||
|
from aioredis import Redis
|
||||||
|
|
||||||
|
|
||||||
|
class Service:
|
||||||
|
def __init__(self, redis: Redis) -> None:
|
||||||
|
self._redis = redis
|
||||||
|
|
||||||
|
async def process(self) -> str:
|
||||||
|
await self._redis.set('my-key', 'value')
|
||||||
|
return await self._redis.get('my-key', encoding='utf-8')
|
28
examples/miniapps/fastapi-redis/fastapiredis/tests.py
Normal file
28
examples/miniapps/fastapi-redis/fastapiredis/tests.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
"""Tests module."""
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from httpx import AsyncClient
|
||||||
|
|
||||||
|
from .application import app, container
|
||||||
|
from .services import Service
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client(event_loop):
|
||||||
|
client = AsyncClient(app=app, base_url='http://test')
|
||||||
|
yield client
|
||||||
|
event_loop.run_until_complete(client.aclose())
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_index(client):
|
||||||
|
service_mock = mock.AsyncMock(spec=Service)
|
||||||
|
service_mock.process.return_value = 'Foo'
|
||||||
|
|
||||||
|
with container.service.override(service_mock):
|
||||||
|
response = await client.get('/')
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {'result': 'Foo'}
|
10
examples/miniapps/fastapi-redis/requirements.txt
Normal file
10
examples/miniapps/fastapi-redis/requirements.txt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
dependency-injector
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
aioredis
|
||||||
|
|
||||||
|
# For testing:
|
||||||
|
pytest
|
||||||
|
pytest-asyncio
|
||||||
|
pytest-cov
|
||||||
|
httpx
|
37
examples/providers/async.py
Normal file
37
examples/providers/async.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
"""Asynchronous injections example."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
|
||||||
|
async def init_async_resource():
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
yield 'Initialized'
|
||||||
|
|
||||||
|
|
||||||
|
class Service:
|
||||||
|
def __init__(self, resource):
|
||||||
|
self.resource = resource
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
resource = providers.Resource(init_async_resource)
|
||||||
|
|
||||||
|
service = providers.Factory(
|
||||||
|
Service,
|
||||||
|
resource=resource,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def main(container: Container):
|
||||||
|
resource = await container.resource()
|
||||||
|
service = await container.service()
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
asyncio.run(main(container))
|
32
examples/providers/async_overriding.py
Normal file
32
examples/providers/async_overriding.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
"""Provider overriding in async mode example."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
|
||||||
|
async def init_async_resource():
|
||||||
|
return ...
|
||||||
|
|
||||||
|
|
||||||
|
def init_resource_mock():
|
||||||
|
return ...
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
resource = providers.Resource(init_async_resource)
|
||||||
|
|
||||||
|
|
||||||
|
async def main(container: Container):
|
||||||
|
resource1 = await container.resource()
|
||||||
|
|
||||||
|
container.resource.override(providers.Callable(init_resource_mock))
|
||||||
|
resource2 = await container.resource()
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
asyncio.run(main(container))
|
|
@ -7,5 +7,8 @@ pydocstyle
|
||||||
sphinx_autobuild
|
sphinx_autobuild
|
||||||
pip
|
pip
|
||||||
mypy
|
mypy
|
||||||
|
pyyaml
|
||||||
|
httpx
|
||||||
|
fastapi
|
||||||
|
|
||||||
-r requirements-ext.txt
|
-r requirements-ext.txt
|
||||||
|
|
|
@ -4,6 +4,8 @@ max_complexity = 10
|
||||||
exclude = types.py
|
exclude = types.py
|
||||||
per-file-ignores =
|
per-file-ignores =
|
||||||
examples/demo/*: F841
|
examples/demo/*: F841
|
||||||
|
examples/providers/async.py: F841
|
||||||
|
examples/providers/async_overriding.py: F841
|
||||||
examples/wiring/*: F841
|
examples/wiring/*: F841
|
||||||
|
|
||||||
[pydocstyle]
|
[pydocstyle]
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -11,3 +11,6 @@ cpdef bint is_container(object instance)
|
||||||
|
|
||||||
|
|
||||||
cpdef object _check_provider_type(object container, object provider)
|
cpdef object _check_provider_type(object container, object provider)
|
||||||
|
|
||||||
|
|
||||||
|
cpdef bint _isawaitable(object instance)
|
||||||
|
|
|
@ -1,5 +1,17 @@
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Type, Dict, Tuple, Optional, Any, Union, ClassVar, Callable as _Callable, Iterable, TypeVar
|
from typing import (
|
||||||
|
Type,
|
||||||
|
Dict,
|
||||||
|
Tuple,
|
||||||
|
Optional,
|
||||||
|
Any,
|
||||||
|
Union,
|
||||||
|
ClassVar,
|
||||||
|
Callable as _Callable,
|
||||||
|
Iterable,
|
||||||
|
TypeVar,
|
||||||
|
Awaitable,
|
||||||
|
)
|
||||||
|
|
||||||
from .providers import Provider
|
from .providers import Provider
|
||||||
|
|
||||||
|
@ -25,8 +37,8 @@ class Container:
|
||||||
def resolve_provider_name(self, provider_to_resolve: Provider) -> Optional[str]: ...
|
def resolve_provider_name(self, provider_to_resolve: Provider) -> Optional[str]: ...
|
||||||
def wire(self, modules: Optional[Iterable[ModuleType]] = None, packages: Optional[Iterable[ModuleType]] = None) -> None: ...
|
def wire(self, modules: Optional[Iterable[ModuleType]] = None, packages: Optional[Iterable[ModuleType]] = None) -> None: ...
|
||||||
def unwire(self) -> None: ...
|
def unwire(self) -> None: ...
|
||||||
def init_resources(self) -> None: ...
|
def init_resources(self) -> Optional[Awaitable]: ...
|
||||||
def shutdown_resources(self) -> None: ...
|
def shutdown_resources(self) -> Optional[Awaitable]: ...
|
||||||
|
|
||||||
|
|
||||||
class DynamicContainer(Container): ...
|
class DynamicContainer(Container): ...
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
"""Containers module."""
|
"""Containers module."""
|
||||||
|
|
||||||
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
except ImportError:
|
||||||
|
asyncio = None
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from .errors import Error
|
from .errors import Error
|
||||||
|
@ -216,17 +222,33 @@ class DynamicContainer(object):
|
||||||
|
|
||||||
def init_resources(self):
|
def init_resources(self):
|
||||||
"""Initialize all container resources."""
|
"""Initialize all container resources."""
|
||||||
|
futures = []
|
||||||
for provider in self.providers.values():
|
for provider in self.providers.values():
|
||||||
if not isinstance(provider, Resource):
|
if not isinstance(provider, Resource):
|
||||||
continue
|
continue
|
||||||
provider.init()
|
|
||||||
|
resource = provider.init()
|
||||||
|
|
||||||
|
if _isawaitable(resource):
|
||||||
|
futures.append(resource)
|
||||||
|
|
||||||
|
if futures:
|
||||||
|
return asyncio.gather(*futures)
|
||||||
|
|
||||||
def shutdown_resources(self):
|
def shutdown_resources(self):
|
||||||
"""Shutdown all container resources."""
|
"""Shutdown all container resources."""
|
||||||
|
futures = []
|
||||||
for provider in self.providers.values():
|
for provider in self.providers.values():
|
||||||
if not isinstance(provider, Resource):
|
if not isinstance(provider, Resource):
|
||||||
continue
|
continue
|
||||||
provider.shutdown()
|
|
||||||
|
shutdown = provider.shutdown()
|
||||||
|
|
||||||
|
if _isawaitable(shutdown):
|
||||||
|
futures.append(shutdown)
|
||||||
|
|
||||||
|
if futures:
|
||||||
|
return asyncio.gather(*futures)
|
||||||
|
|
||||||
|
|
||||||
class DeclarativeContainerMetaClass(type):
|
class DeclarativeContainerMetaClass(type):
|
||||||
|
@ -494,3 +516,10 @@ cpdef object _check_provider_type(object container, object provider):
|
||||||
if not isinstance(provider, container.provider_type):
|
if not isinstance(provider, container.provider_type):
|
||||||
raise Error('{0} can contain only {1} '
|
raise Error('{0} can contain only {1} '
|
||||||
'instances'.format(container, container.provider_type))
|
'instances'.format(container, container.provider_type))
|
||||||
|
|
||||||
|
|
||||||
|
cpdef bint _isawaitable(object instance):
|
||||||
|
try:
|
||||||
|
return <bint> inspect.isawaitable(instance)
|
||||||
|
except AttributeError:
|
||||||
|
return <bint> False
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,12 @@
|
||||||
"""Providers module."""
|
"""Providers module."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
except ImportError:
|
||||||
|
asyncio = None
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
|
||||||
cimport cython
|
cimport cython
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +14,7 @@ cimport cython
|
||||||
cdef class Provider(object):
|
cdef class Provider(object):
|
||||||
cdef tuple __overridden
|
cdef tuple __overridden
|
||||||
cdef Provider __last_overriding
|
cdef Provider __last_overriding
|
||||||
|
cdef int __async_mode
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs)
|
cpdef object _provide(self, tuple args, dict kwargs)
|
||||||
cpdef void _copy_overridings(self, Provider copied, dict memo)
|
cpdef void _copy_overridings(self, Provider copied, dict memo)
|
||||||
|
@ -134,10 +142,10 @@ cdef class FactoryAggregate(Provider):
|
||||||
# Singleton providers
|
# Singleton providers
|
||||||
cdef class BaseSingleton(Provider):
|
cdef class BaseSingleton(Provider):
|
||||||
cdef Factory __instantiator
|
cdef Factory __instantiator
|
||||||
|
cdef object __storage
|
||||||
|
|
||||||
|
|
||||||
cdef class Singleton(BaseSingleton):
|
cdef class Singleton(BaseSingleton):
|
||||||
cdef object __storage
|
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs)
|
cpdef object _provide(self, tuple args, dict kwargs)
|
||||||
|
|
||||||
|
@ -147,7 +155,6 @@ cdef class DelegatedSingleton(Singleton):
|
||||||
|
|
||||||
|
|
||||||
cdef class ThreadSafeSingleton(BaseSingleton):
|
cdef class ThreadSafeSingleton(BaseSingleton):
|
||||||
cdef object __storage
|
|
||||||
cdef object __storage_lock
|
cdef object __storage_lock
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs)
|
cpdef object _provide(self, tuple args, dict kwargs)
|
||||||
|
@ -158,7 +165,6 @@ cdef class DelegatedThreadSafeSingleton(ThreadSafeSingleton):
|
||||||
|
|
||||||
|
|
||||||
cdef class ThreadLocalSingleton(BaseSingleton):
|
cdef class ThreadLocalSingleton(BaseSingleton):
|
||||||
cdef object __storage
|
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs)
|
cpdef object _provide(self, tuple args, dict kwargs)
|
||||||
|
|
||||||
|
@ -331,30 +337,38 @@ cdef inline tuple __separate_prefixed_kwargs(dict kwargs):
|
||||||
|
|
||||||
@cython.boundscheck(False)
|
@cython.boundscheck(False)
|
||||||
@cython.wraparound(False)
|
@cython.wraparound(False)
|
||||||
cdef inline tuple __provide_positional_args(
|
cdef inline object __provide_positional_args(
|
||||||
tuple args,
|
tuple args,
|
||||||
tuple inj_args,
|
tuple inj_args,
|
||||||
int inj_args_len,
|
int inj_args_len,
|
||||||
):
|
):
|
||||||
cdef int index
|
cdef int index
|
||||||
cdef list positional_args
|
cdef list positional_args = []
|
||||||
|
cdef list awaitables = []
|
||||||
cdef PositionalInjection injection
|
cdef PositionalInjection injection
|
||||||
|
|
||||||
if inj_args_len == 0:
|
if inj_args_len == 0:
|
||||||
return args
|
return args
|
||||||
|
|
||||||
positional_args = list()
|
|
||||||
for index in range(inj_args_len):
|
for index in range(inj_args_len):
|
||||||
injection = <PositionalInjection>inj_args[index]
|
injection = <PositionalInjection>inj_args[index]
|
||||||
positional_args.append(__get_value(injection))
|
value = __get_value(injection)
|
||||||
|
positional_args.append(value)
|
||||||
|
|
||||||
|
if __isawaitable(value):
|
||||||
|
awaitables.append((index, value))
|
||||||
|
|
||||||
positional_args.extend(args)
|
positional_args.extend(args)
|
||||||
|
|
||||||
return tuple(positional_args)
|
if awaitables:
|
||||||
|
return __awaitable_args_kwargs_future(positional_args, awaitables)
|
||||||
|
|
||||||
|
return positional_args
|
||||||
|
|
||||||
|
|
||||||
@cython.boundscheck(False)
|
@cython.boundscheck(False)
|
||||||
@cython.wraparound(False)
|
@cython.wraparound(False)
|
||||||
cdef inline dict __provide_keyword_args(
|
cdef inline object __provide_keyword_args(
|
||||||
dict kwargs,
|
dict kwargs,
|
||||||
tuple inj_kwargs,
|
tuple inj_kwargs,
|
||||||
int inj_kwargs_len,
|
int inj_kwargs_len,
|
||||||
|
@ -362,14 +376,18 @@ cdef inline dict __provide_keyword_args(
|
||||||
cdef int index
|
cdef int index
|
||||||
cdef object name
|
cdef object name
|
||||||
cdef object value
|
cdef object value
|
||||||
cdef dict prefixed
|
cdef dict prefixed = {}
|
||||||
|
cdef list awaitables = []
|
||||||
cdef NamedInjection kw_injection
|
cdef NamedInjection kw_injection
|
||||||
|
|
||||||
if len(kwargs) == 0:
|
if len(kwargs) == 0:
|
||||||
for index in range(inj_kwargs_len):
|
for index in range(inj_kwargs_len):
|
||||||
kw_injection = <NamedInjection>inj_kwargs[index]
|
kw_injection = <NamedInjection>inj_kwargs[index]
|
||||||
name = __get_name(kw_injection)
|
name = __get_name(kw_injection)
|
||||||
kwargs[name] = __get_value(kw_injection)
|
value = __get_value(kw_injection)
|
||||||
|
kwargs[name] = value
|
||||||
|
if __isawaitable(value):
|
||||||
|
awaitables.append((name, value))
|
||||||
else:
|
else:
|
||||||
kwargs, prefixed = __separate_prefixed_kwargs(kwargs)
|
kwargs, prefixed = __separate_prefixed_kwargs(kwargs)
|
||||||
|
|
||||||
|
@ -387,23 +405,77 @@ cdef inline dict __provide_keyword_args(
|
||||||
value = __get_value(kw_injection)
|
value = __get_value(kw_injection)
|
||||||
|
|
||||||
kwargs[name] = value
|
kwargs[name] = value
|
||||||
|
if __isawaitable(value):
|
||||||
|
awaitables.append((name, value))
|
||||||
|
|
||||||
|
if awaitables:
|
||||||
|
return __awaitable_args_kwargs_future(kwargs, awaitables)
|
||||||
|
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
cdef inline object __awaitable_args_kwargs_future(object args, list awaitables):
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
|
||||||
|
args_future = asyncio.Future()
|
||||||
|
args_future.set_result((future_result, args, awaitables))
|
||||||
|
|
||||||
|
args_ready = asyncio.gather(args_future, *[value for _, value in awaitables])
|
||||||
|
args_ready.add_done_callback(__async_prepare_args_kwargs_callback)
|
||||||
|
asyncio.ensure_future(args_ready)
|
||||||
|
|
||||||
|
return future_result
|
||||||
|
|
||||||
|
|
||||||
|
cdef inline void __async_prepare_args_kwargs_callback(object future):
|
||||||
|
(future_result, args, awaitables), *awaited = future.result()
|
||||||
|
for value, (key, _) in zip(awaited, awaitables):
|
||||||
|
args[key] = value
|
||||||
|
future_result.set_result(args)
|
||||||
|
|
||||||
|
|
||||||
@cython.boundscheck(False)
|
@cython.boundscheck(False)
|
||||||
@cython.wraparound(False)
|
@cython.wraparound(False)
|
||||||
cdef inline object __inject_attributes(
|
cdef inline object __provide_attributes(tuple attributes, int attributes_len):
|
||||||
object instance,
|
|
||||||
tuple attributes,
|
|
||||||
int attributes_len,
|
|
||||||
):
|
|
||||||
cdef NamedInjection attr_injection
|
cdef NamedInjection attr_injection
|
||||||
|
cdef dict attribute_injections = {}
|
||||||
|
cdef list awaitables = []
|
||||||
|
|
||||||
for index in range(attributes_len):
|
for index in range(attributes_len):
|
||||||
attr_injection = <NamedInjection>attributes[index]
|
attr_injection = <NamedInjection>attributes[index]
|
||||||
setattr(instance,
|
name = __get_name(attr_injection)
|
||||||
__get_name(attr_injection),
|
value = __get_value(attr_injection)
|
||||||
__get_value(attr_injection))
|
attribute_injections[name] = value
|
||||||
|
if __isawaitable(value):
|
||||||
|
awaitables.append((name, value))
|
||||||
|
|
||||||
|
if awaitables:
|
||||||
|
return __awaitable_args_kwargs_future(attribute_injections, awaitables)
|
||||||
|
|
||||||
|
return attribute_injections
|
||||||
|
|
||||||
|
|
||||||
|
cdef inline object __async_inject_attributes(future_instance, future_attributes):
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
|
||||||
|
future = asyncio.Future()
|
||||||
|
future.set_result(future_result)
|
||||||
|
|
||||||
|
attributes_ready = asyncio.gather(future, future_instance, future_attributes)
|
||||||
|
attributes_ready.add_done_callback(__async_inject_attributes_callback)
|
||||||
|
asyncio.ensure_future(attributes_ready)
|
||||||
|
|
||||||
|
return future_result
|
||||||
|
|
||||||
|
cdef inline void __async_inject_attributes_callback(future):
|
||||||
|
future_result, instance, attributes = future.result()
|
||||||
|
__inject_attributes(instance, attributes)
|
||||||
|
future_result.set_result(instance)
|
||||||
|
|
||||||
|
|
||||||
|
cdef inline void __inject_attributes(object instance, dict attributes):
|
||||||
|
for name, value in attributes.items():
|
||||||
|
setattr(instance, name, value)
|
||||||
|
|
||||||
|
|
||||||
cdef inline object __call(
|
cdef inline object __call(
|
||||||
|
@ -411,25 +483,53 @@ cdef inline object __call(
|
||||||
tuple context_args,
|
tuple context_args,
|
||||||
tuple injection_args,
|
tuple injection_args,
|
||||||
int injection_args_len,
|
int injection_args_len,
|
||||||
dict kwargs,
|
dict context_kwargs,
|
||||||
tuple injection_kwargs,
|
tuple injection_kwargs,
|
||||||
int injection_kwargs_len,
|
int injection_kwargs_len,
|
||||||
):
|
):
|
||||||
cdef tuple positional_args
|
args = __provide_positional_args(
|
||||||
cdef dict keyword_args
|
|
||||||
|
|
||||||
positional_args = __provide_positional_args(
|
|
||||||
context_args,
|
context_args,
|
||||||
injection_args,
|
injection_args,
|
||||||
injection_args_len,
|
injection_args_len,
|
||||||
)
|
)
|
||||||
keyword_args = __provide_keyword_args(
|
kwargs = __provide_keyword_args(
|
||||||
kwargs,
|
context_kwargs,
|
||||||
injection_kwargs,
|
injection_kwargs,
|
||||||
injection_kwargs_len,
|
injection_kwargs_len,
|
||||||
)
|
)
|
||||||
|
|
||||||
return call(*positional_args, **keyword_args)
|
args_awaitable = __isawaitable(args)
|
||||||
|
kwargs_awaitable = __isawaitable(kwargs)
|
||||||
|
|
||||||
|
if args_awaitable or kwargs_awaitable:
|
||||||
|
if not args_awaitable:
|
||||||
|
future = asyncio.Future()
|
||||||
|
future.set_result(args)
|
||||||
|
args = future
|
||||||
|
|
||||||
|
if not kwargs_awaitable:
|
||||||
|
future = asyncio.Future()
|
||||||
|
future.set_result(kwargs)
|
||||||
|
kwargs = future
|
||||||
|
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
|
||||||
|
future = asyncio.Future()
|
||||||
|
future.set_result((future_result, call))
|
||||||
|
|
||||||
|
args_kwargs_ready = asyncio.gather(future, args, kwargs)
|
||||||
|
args_kwargs_ready.add_done_callback(__async_call_callback)
|
||||||
|
asyncio.ensure_future(args_kwargs_ready)
|
||||||
|
|
||||||
|
return future_result
|
||||||
|
|
||||||
|
return call(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
cdef inline void __async_call_callback(object future):
|
||||||
|
(future_result, call), args, kwargs = future.result()
|
||||||
|
result = call(*args, **kwargs)
|
||||||
|
future_result.set_result(result)
|
||||||
|
|
||||||
|
|
||||||
cdef inline object __callable_call(Callable self, tuple args, dict kwargs):
|
cdef inline object __callable_call(Callable self, tuple args, dict kwargs):
|
||||||
|
@ -450,8 +550,40 @@ cdef inline object __factory_call(Factory self, tuple args, dict kwargs):
|
||||||
instance = __callable_call(self.__instantiator, args, kwargs)
|
instance = __callable_call(self.__instantiator, args, kwargs)
|
||||||
|
|
||||||
if self.__attributes_len > 0:
|
if self.__attributes_len > 0:
|
||||||
__inject_attributes(instance,
|
attributes = __provide_attributes(self.__attributes, self.__attributes_len)
|
||||||
self.__attributes,
|
|
||||||
self.__attributes_len)
|
instance_awaitable = __isawaitable(instance)
|
||||||
|
attributes_awaitable = __isawaitable(attributes)
|
||||||
|
|
||||||
|
if instance_awaitable or attributes_awaitable:
|
||||||
|
if not instance_awaitable:
|
||||||
|
future = asyncio.Future()
|
||||||
|
future.set_result(instance)
|
||||||
|
instance = future
|
||||||
|
|
||||||
|
if not attributes_awaitable:
|
||||||
|
future = asyncio.Future()
|
||||||
|
future.set_result(attributes)
|
||||||
|
attributes = future
|
||||||
|
|
||||||
|
return __async_inject_attributes(instance, attributes)
|
||||||
|
|
||||||
|
__inject_attributes(instance, attributes)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
cdef bint __has_isawaitable = False
|
||||||
|
|
||||||
|
|
||||||
|
cdef inline bint __isawaitable(object instance):
|
||||||
|
global __has_isawaitable
|
||||||
|
|
||||||
|
if __has_isawaitable is True:
|
||||||
|
return inspect.isawaitable(instance)
|
||||||
|
|
||||||
|
if hasattr(inspect, 'isawaitable'):
|
||||||
|
__has_isawaitable = True
|
||||||
|
return inspect.isawaitable(instance)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import (
|
from typing import (
|
||||||
|
Awaitable,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Generic,
|
Generic,
|
||||||
Type,
|
Type,
|
||||||
|
@ -14,6 +15,7 @@ from typing import (
|
||||||
Union,
|
Union,
|
||||||
Coroutine as _Coroutine,
|
Coroutine as _Coroutine,
|
||||||
Iterator as _Iterator,
|
Iterator as _Iterator,
|
||||||
|
AsyncIterator as _AsyncIterator,
|
||||||
Generator as _Generator,
|
Generator as _Generator,
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
@ -33,7 +35,13 @@ class OverridingContext:
|
||||||
|
|
||||||
class Provider(Generic[T]):
|
class Provider(Generic[T]):
|
||||||
def __init__(self) -> None: ...
|
def __init__(self) -> None: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
||||||
|
@overload
|
||||||
|
def __call__(self, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
|
||||||
|
def async_(self, *args: Injection, **kwargs: Injection) -> Awaitable[T]: ...
|
||||||
|
|
||||||
def __deepcopy__(self, memo: Optional[_Dict[Any, Any]]) -> Provider: ...
|
def __deepcopy__(self, memo: Optional[_Dict[Any, Any]]) -> Provider: ...
|
||||||
def __str__(self) -> str: ...
|
def __str__(self) -> str: ...
|
||||||
def __repr__(self) -> str: ...
|
def __repr__(self) -> str: ...
|
||||||
|
@ -49,30 +57,33 @@ class Provider(Generic[T]):
|
||||||
def provider(self) -> Provider: ...
|
def provider(self) -> Provider: ...
|
||||||
@property
|
@property
|
||||||
def provided(self) -> ProvidedInstance: ...
|
def provided(self) -> ProvidedInstance: ...
|
||||||
|
def enable_async_mode(self) -> None: ...
|
||||||
|
def disable_async_mode(self) -> None: ...
|
||||||
|
def reset_async_mode(self) -> None: ...
|
||||||
|
def is_async_mode_enabled(self) -> bool: ...
|
||||||
|
def is_async_mode_disabled(self) -> bool: ...
|
||||||
|
def is_async_mode_undefined(self) -> bool: ...
|
||||||
def _copy_overridings(self, copied: Provider, memo: Optional[_Dict[Any, Any]]) -> None: ...
|
def _copy_overridings(self, copied: Provider, memo: Optional[_Dict[Any, Any]]) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
class Object(Provider, Generic[T]):
|
class Object(Provider[T]):
|
||||||
def __init__(self, provides: T) -> None: ...
|
def __init__(self, provides: T) -> None: ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
|
||||||
|
|
||||||
|
|
||||||
class Delegate(Provider):
|
class Delegate(Provider[Provider]):
|
||||||
def __init__(self, provides: Provider) -> None: ...
|
def __init__(self, provides: Provider) -> None: ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> Provider: ...
|
|
||||||
@property
|
@property
|
||||||
def provides(self) -> Provider: ...
|
def provides(self) -> Provider: ...
|
||||||
|
|
||||||
|
|
||||||
class Dependency(Provider, Generic[T]):
|
class Dependency(Provider[T]):
|
||||||
def __init__(self, instance_of: Type[T] = object) -> None: ...
|
def __init__(self, instance_of: Type[T] = object) -> None: ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
|
||||||
@property
|
@property
|
||||||
def instance_of(self) -> Type[T]: ...
|
def instance_of(self) -> Type[T]: ...
|
||||||
def provided_by(self, provider: Provider) -> OverridingContext: ...
|
def provided_by(self, provider: Provider) -> OverridingContext: ...
|
||||||
|
|
||||||
|
|
||||||
class ExternalDependency(Dependency): ...
|
class ExternalDependency(Dependency[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class DependenciesContainer(Object):
|
class DependenciesContainer(Object):
|
||||||
|
@ -82,9 +93,8 @@ class DependenciesContainer(Object):
|
||||||
def providers(self) -> _Dict[str, Provider]: ...
|
def providers(self) -> _Dict[str, Provider]: ...
|
||||||
|
|
||||||
|
|
||||||
class Callable(Provider, Generic[T]):
|
class Callable(Provider[T]):
|
||||||
def __init__(self, provides: _Callable[..., T], *args: Injection, **kwargs: Injection) -> None: ...
|
def __init__(self, provides: _Callable[..., T], *args: Injection, **kwargs: Injection) -> None: ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
|
||||||
@property
|
@property
|
||||||
def provides(self) -> T: ...
|
def provides(self) -> T: ...
|
||||||
@property
|
@property
|
||||||
|
@ -93,16 +103,16 @@ class Callable(Provider, Generic[T]):
|
||||||
def set_args(self, *args: Injection) -> Callable[T]: ...
|
def set_args(self, *args: Injection) -> Callable[T]: ...
|
||||||
def clear_args(self) -> Callable[T]: ...
|
def clear_args(self) -> Callable[T]: ...
|
||||||
@property
|
@property
|
||||||
def kwargs(self) -> _Dict[str, Injection]: ...
|
def kwargs(self) -> _Dict[Any, Injection]: ...
|
||||||
def add_kwargs(self, **kwargs: Injection) -> Callable[T]: ...
|
def add_kwargs(self, **kwargs: Injection) -> Callable[T]: ...
|
||||||
def set_kwargs(self, **kwargs: Injection) -> Callable[T]: ...
|
def set_kwargs(self, **kwargs: Injection) -> Callable[T]: ...
|
||||||
def clear_kwargs(self) -> Callable[T]: ...
|
def clear_kwargs(self) -> Callable[T]: ...
|
||||||
|
|
||||||
|
|
||||||
class DelegatedCallable(Callable): ...
|
class DelegatedCallable(Callable[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class AbstractCallable(Callable):
|
class AbstractCallable(Callable[T]):
|
||||||
def override(self, provider: Callable) -> OverridingContext: ...
|
def override(self, provider: Callable) -> OverridingContext: ...
|
||||||
|
|
||||||
|
|
||||||
|
@ -110,13 +120,13 @@ class CallableDelegate(Delegate):
|
||||||
def __init__(self, callable: Callable) -> None: ...
|
def __init__(self, callable: Callable) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
class Coroutine(Callable): ...
|
class Coroutine(Callable[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class DelegatedCoroutine(Coroutine): ...
|
class DelegatedCoroutine(Coroutine[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class AbstractCoroutine(Coroutine):
|
class AbstractCoroutine(Coroutine[T]):
|
||||||
def override(self, provider: Coroutine) -> OverridingContext: ...
|
def override(self, provider: Coroutine) -> OverridingContext: ...
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,10 +134,9 @@ class CoroutineDelegate(Delegate):
|
||||||
def __init__(self, coroutine: Coroutine) -> None: ...
|
def __init__(self, coroutine: Coroutine) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationOption(Provider):
|
class ConfigurationOption(Provider[Any]):
|
||||||
UNDEFINED: object
|
UNDEFINED: object
|
||||||
def __init__(self, name: Tuple[str], root: Configuration) -> None: ...
|
def __init__(self, name: Tuple[str], root: Configuration) -> None: ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> Any: ...
|
|
||||||
def __getattr__(self, item: str) -> ConfigurationOption: ...
|
def __getattr__(self, item: str) -> ConfigurationOption: ...
|
||||||
def __getitem__(self, item: Union[str, Provider]) -> ConfigurationOption: ...
|
def __getitem__(self, item: Union[str, Provider]) -> ConfigurationOption: ...
|
||||||
@property
|
@property
|
||||||
|
@ -149,7 +158,7 @@ class TypedConfigurationOption(Callable[T]):
|
||||||
def option(self) -> ConfigurationOption: ...
|
def option(self) -> ConfigurationOption: ...
|
||||||
|
|
||||||
|
|
||||||
class Configuration(Object):
|
class Configuration(Object[Any]):
|
||||||
DEFAULT_NAME: str = 'config'
|
DEFAULT_NAME: str = 'config'
|
||||||
def __init__(self, name: str = DEFAULT_NAME, default: Optional[Any] = None) -> None: ...
|
def __init__(self, name: str = DEFAULT_NAME, default: Optional[Any] = None) -> None: ...
|
||||||
def __getattr__(self, item: str) -> ConfigurationOption: ...
|
def __getattr__(self, item: str) -> ConfigurationOption: ...
|
||||||
|
@ -165,10 +174,9 @@ class Configuration(Object):
|
||||||
def from_env(self, name: str, default: Optional[Any] = None) -> None: ...
|
def from_env(self, name: str, default: Optional[Any] = None) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
class Factory(Provider, Generic[T]):
|
class Factory(Provider[T]):
|
||||||
provided_type: Optional[Type]
|
provided_type: Optional[Type]
|
||||||
def __init__(self, provides: _Callable[..., T], *args: Injection, **kwargs: Injection) -> None: ...
|
def __init__(self, provides: _Callable[..., T], *args: Injection, **kwargs: Injection) -> None: ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
|
||||||
@property
|
@property
|
||||||
def cls(self) -> T: ...
|
def cls(self) -> T: ...
|
||||||
@property
|
@property
|
||||||
|
@ -179,21 +187,21 @@ class Factory(Provider, Generic[T]):
|
||||||
def set_args(self, *args: Injection) -> Factory[T]: ...
|
def set_args(self, *args: Injection) -> Factory[T]: ...
|
||||||
def clear_args(self) -> Factory[T]: ...
|
def clear_args(self) -> Factory[T]: ...
|
||||||
@property
|
@property
|
||||||
def kwargs(self) -> _Dict[str, Injection]: ...
|
def kwargs(self) -> _Dict[Any, Injection]: ...
|
||||||
def add_kwargs(self, **kwargs: Injection) -> Factory[T]: ...
|
def add_kwargs(self, **kwargs: Injection) -> Factory[T]: ...
|
||||||
def set_kwargs(self, **kwargs: Injection) -> Factory[T]: ...
|
def set_kwargs(self, **kwargs: Injection) -> Factory[T]: ...
|
||||||
def clear_kwargs(self) -> Factory[T]: ...
|
def clear_kwargs(self) -> Factory[T]: ...
|
||||||
@property
|
@property
|
||||||
def attributes(self) -> _Dict[str, Injection]: ...
|
def attributes(self) -> _Dict[Any, Injection]: ...
|
||||||
def add_attributes(self, **kwargs: Injection) -> Factory[T]: ...
|
def add_attributes(self, **kwargs: Injection) -> Factory[T]: ...
|
||||||
def set_attributes(self, **kwargs: Injection) -> Factory[T]: ...
|
def set_attributes(self, **kwargs: Injection) -> Factory[T]: ...
|
||||||
def clear_attributes(self) -> Factory[T]: ...
|
def clear_attributes(self) -> Factory[T]: ...
|
||||||
|
|
||||||
|
|
||||||
class DelegatedFactory(Factory): ...
|
class DelegatedFactory(Factory[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class AbstractFactory(Factory):
|
class AbstractFactory(Factory[T]):
|
||||||
def override(self, provider: Factory) -> OverridingContext: ...
|
def override(self, provider: Factory) -> OverridingContext: ...
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,55 +211,60 @@ class FactoryDelegate(Delegate):
|
||||||
|
|
||||||
class FactoryAggregate(Provider):
|
class FactoryAggregate(Provider):
|
||||||
def __init__(self, **factories: Factory): ...
|
def __init__(self, **factories: Factory): ...
|
||||||
def __call__(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Any: ...
|
|
||||||
def __getattr__(self, factory_name: str) -> Factory: ...
|
def __getattr__(self, factory_name: str) -> Factory: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __call__(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Any: ...
|
||||||
|
@overload
|
||||||
|
def __call__(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Awaitable[Any]: ...
|
||||||
|
def async_(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Awaitable[Any]: ...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def factories(self) -> _Dict[str, Factory]: ...
|
def factories(self) -> _Dict[str, Factory]: ...
|
||||||
|
|
||||||
|
|
||||||
class BaseSingleton(Provider, Generic[T]):
|
class BaseSingleton(Provider[T]):
|
||||||
provided_type = Optional[Type]
|
provided_type = Optional[Type]
|
||||||
def __init__(self, provides: _Callable[..., T], *args: Injection, **kwargs: Injection) -> None: ...
|
def __init__(self, provides: _Callable[..., T], *args: Injection, **kwargs: Injection) -> None: ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
|
||||||
@property
|
@property
|
||||||
def cls(self) -> T: ...
|
def cls(self) -> T: ...
|
||||||
@property
|
@property
|
||||||
def args(self) -> Tuple[Injection]: ...
|
def args(self) -> Tuple[Injection]: ...
|
||||||
def add_args(self, *args: Injection) -> Factory[T]: ...
|
def add_args(self, *args: Injection) -> BaseSingleton[T]: ...
|
||||||
def set_args(self, *args: Injection) -> Factory[T]: ...
|
def set_args(self, *args: Injection) -> BaseSingleton[T]: ...
|
||||||
def clear_args(self) -> Factory[T]: ...
|
def clear_args(self) -> BaseSingleton[T]: ...
|
||||||
@property
|
@property
|
||||||
def kwargs(self) -> _Dict[str, Injection]: ...
|
def kwargs(self) -> _Dict[Any, Injection]: ...
|
||||||
def add_kwargs(self, **kwargs: Injection) -> Factory[T]: ...
|
def add_kwargs(self, **kwargs: Injection) -> BaseSingleton[T]: ...
|
||||||
def set_kwargs(self, **kwargs: Injection) -> Factory[T]: ...
|
def set_kwargs(self, **kwargs: Injection) -> BaseSingleton[T]: ...
|
||||||
def clear_kwargs(self) -> Factory[T]: ...
|
def clear_kwargs(self) -> BaseSingleton[T]: ...
|
||||||
@property
|
@property
|
||||||
def attributes(self) -> _Dict[str, Injection]: ...
|
def attributes(self) -> _Dict[Any, Injection]: ...
|
||||||
def add_attributes(self, **kwargs: Injection) -> Factory[T]: ...
|
def add_attributes(self, **kwargs: Injection) -> BaseSingleton[T]: ...
|
||||||
def set_attributes(self, **kwargs: Injection) -> Factory[T]: ...
|
def set_attributes(self, **kwargs: Injection) -> BaseSingleton[T]: ...
|
||||||
def clear_attributes(self) -> Factory[T]: ...
|
def clear_attributes(self) -> BaseSingleton[T]: ...
|
||||||
def reset(self) -> None: ...
|
def reset(self) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
class Singleton(BaseSingleton): ...
|
class Singleton(BaseSingleton[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class DelegatedSingleton(Singleton): ...
|
class DelegatedSingleton(Singleton[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class ThreadSafeSingleton(Singleton): ...
|
class ThreadSafeSingleton(Singleton[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class DelegatedThreadSafeSingleton(ThreadSafeSingleton): ...
|
class DelegatedThreadSafeSingleton(ThreadSafeSingleton[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class ThreadLocalSingleton(BaseSingleton): ...
|
class ThreadLocalSingleton(BaseSingleton[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class DelegatedThreadLocalSingleton(ThreadLocalSingleton): ...
|
class DelegatedThreadLocalSingleton(ThreadLocalSingleton[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class AbstractSingleton(BaseSingleton):
|
class AbstractSingleton(BaseSingleton[T]):
|
||||||
def override(self, provider: BaseSingleton) -> OverridingContext: ...
|
def override(self, provider: BaseSingleton) -> OverridingContext: ...
|
||||||
|
|
||||||
|
|
||||||
|
@ -259,19 +272,17 @@ class SingletonDelegate(Delegate):
|
||||||
def __init__(self, factory: BaseSingleton): ...
|
def __init__(self, factory: BaseSingleton): ...
|
||||||
|
|
||||||
|
|
||||||
class List(Provider):
|
class List(Provider[_List]):
|
||||||
def __init__(self, *args: Injection): ...
|
def __init__(self, *args: Injection): ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> _List[Any]: ...
|
|
||||||
@property
|
@property
|
||||||
def args(self) -> Tuple[Injection]: ...
|
def args(self) -> Tuple[Injection]: ...
|
||||||
def add_args(self, *args: Injection) -> List: ...
|
def add_args(self, *args: Injection) -> List[T]: ...
|
||||||
def set_args(self, *args: Injection) -> List: ...
|
def set_args(self, *args: Injection) -> List[T]: ...
|
||||||
def clear_args(self) -> List: ...
|
def clear_args(self) -> List[T]: ...
|
||||||
|
|
||||||
|
|
||||||
class Dict(Provider):
|
class Dict(Provider[_Dict]):
|
||||||
def __init__(self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection): ...
|
def __init__(self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection): ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> _Dict[Any, Any]: ...
|
|
||||||
@property
|
@property
|
||||||
def kwargs(self) -> _Dict[Any, Injection]: ...
|
def kwargs(self) -> _Dict[Any, Injection]: ...
|
||||||
def add_kwargs(self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection) -> Dict: ...
|
def add_kwargs(self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection) -> Dict: ...
|
||||||
|
@ -279,42 +290,44 @@ class Dict(Provider):
|
||||||
def clear_kwargs(self) -> Dict: ...
|
def clear_kwargs(self) -> Dict: ...
|
||||||
|
|
||||||
|
|
||||||
class Resource(Provider, Generic[T]):
|
class Resource(Provider[T]):
|
||||||
@overload
|
@overload
|
||||||
def __init__(self, initializer: _Callable[..., resources.Resource[T]], *args: Injection, **kwargs: Injection) -> None: ...
|
def __init__(self, initializer: Type[resources.Resource[T]], *args: Injection, **kwargs: Injection) -> None: ...
|
||||||
|
@overload
|
||||||
|
def __init__(self, initializer: Type[resources.AsyncResource[T]], *args: Injection, **kwargs: Injection) -> None: ...
|
||||||
@overload
|
@overload
|
||||||
def __init__(self, initializer: _Callable[..., _Iterator[T]], *args: Injection, **kwargs: Injection) -> None: ...
|
def __init__(self, initializer: _Callable[..., _Iterator[T]], *args: Injection, **kwargs: Injection) -> None: ...
|
||||||
@overload
|
@overload
|
||||||
|
def __init__(self, initializer: _Callable[..., _AsyncIterator[T]], *args: Injection, **kwargs: Injection) -> None: ...
|
||||||
|
@overload
|
||||||
|
def __init__(self, initializer: _Callable[..., _Coroutine[Injection, Injection, T]], *args: Injection, **kwargs: Injection) -> None: ...
|
||||||
|
@overload
|
||||||
def __init__(self, initializer: _Callable[..., T], *args: Injection, **kwargs: Injection) -> None: ...
|
def __init__(self, initializer: _Callable[..., T], *args: Injection, **kwargs: Injection) -> None: ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
|
||||||
@property
|
@property
|
||||||
def args(self) -> Tuple[Injection]: ...
|
def args(self) -> Tuple[Injection]: ...
|
||||||
def add_args(self, *args: Injection) -> Resource: ...
|
def add_args(self, *args: Injection) -> Resource[T]: ...
|
||||||
def set_args(self, *args: Injection) -> Resource: ...
|
def set_args(self, *args: Injection) -> Resource[T]: ...
|
||||||
def clear_args(self) -> Resource: ...
|
def clear_args(self) -> Resource[T]: ...
|
||||||
@property
|
@property
|
||||||
def kwargs(self) -> _Dict[Any, Injection]: ...
|
def kwargs(self) -> _Dict[Any, Injection]: ...
|
||||||
def add_kwargs(self, **kwargs: Injection) -> Resource: ...
|
def add_kwargs(self, **kwargs: Injection) -> Resource[T]: ...
|
||||||
def set_kwargs(self, **kwargs: Injection) -> Resource: ...
|
def set_kwargs(self, **kwargs: Injection) -> Resource[T]: ...
|
||||||
def clear_kwargs(self) -> Resource: ...
|
def clear_kwargs(self) -> Resource[T]: ...
|
||||||
@property
|
@property
|
||||||
def initialized(self) -> bool: ...
|
def initialized(self) -> bool: ...
|
||||||
def init(self) -> T: ...
|
def init(self) -> Optional[Awaitable[T]]: ...
|
||||||
def shutdown(self) -> None: ...
|
def shutdown(self) -> Optional[Awaitable]: ...
|
||||||
|
|
||||||
|
|
||||||
class Container(Provider):
|
class Container(Provider[T]):
|
||||||
|
|
||||||
def __init__(self, container_cls: Type[T], container: Optional[T] = None, **overriding_providers: Provider) -> None: ...
|
def __init__(self, container_cls: Type[T], container: Optional[T] = None, **overriding_providers: Provider) -> None: ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
|
|
||||||
def __getattr__(self, name: str) -> Provider: ...
|
def __getattr__(self, name: str) -> Provider: ...
|
||||||
@property
|
@property
|
||||||
def container(self) -> T: ...
|
def container(self) -> T: ...
|
||||||
|
|
||||||
|
|
||||||
class Selector(Provider):
|
class Selector(Provider[Any]):
|
||||||
def __init__(self, selector: _Callable[..., Any], **providers: Provider): ...
|
def __init__(self, selector: _Callable[..., Any], **providers: Provider): ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> Any: ...
|
|
||||||
def __getattr__(self, name: str) -> Provider: ...
|
def __getattr__(self, name: str) -> Provider: ...
|
||||||
@property
|
@property
|
||||||
def providers(self) -> _Dict[str, Provider]: ...
|
def providers(self) -> _Dict[str, Provider]: ...
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -88,6 +89,11 @@ else:
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
cdef int ASYNC_MODE_UNDEFINED = 0
|
||||||
|
cdef int ASYNC_MODE_ENABLED = 1
|
||||||
|
cdef int ASYNC_MODE_DISABLED = 2
|
||||||
|
|
||||||
|
|
||||||
cdef class Provider(object):
|
cdef class Provider(object):
|
||||||
"""Base provider class.
|
"""Base provider class.
|
||||||
|
|
||||||
|
@ -148,6 +154,7 @@ cdef class Provider(object):
|
||||||
"""Initializer."""
|
"""Initializer."""
|
||||||
self.__overridden = tuple()
|
self.__overridden = tuple()
|
||||||
self.__last_overriding = None
|
self.__last_overriding = None
|
||||||
|
self.__async_mode = ASYNC_MODE_UNDEFINED
|
||||||
super(Provider, self).__init__()
|
super(Provider, self).__init__()
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
|
@ -156,8 +163,24 @@ cdef class Provider(object):
|
||||||
Callable interface implementation.
|
Callable interface implementation.
|
||||||
"""
|
"""
|
||||||
if self.__last_overriding is not None:
|
if self.__last_overriding is not None:
|
||||||
return self.__last_overriding(*args, **kwargs)
|
result = self.__last_overriding(*args, **kwargs)
|
||||||
return self._provide(args, kwargs)
|
else:
|
||||||
|
result = self._provide(args, kwargs)
|
||||||
|
|
||||||
|
if self.is_async_mode_disabled():
|
||||||
|
return result
|
||||||
|
elif self.is_async_mode_enabled():
|
||||||
|
if not __isawaitable(result):
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
future_result.set_result(result)
|
||||||
|
return future_result
|
||||||
|
return result
|
||||||
|
elif self.is_async_mode_undefined():
|
||||||
|
if __isawaitable(result):
|
||||||
|
self.enable_async_mode()
|
||||||
|
else:
|
||||||
|
self.disable_async_mode()
|
||||||
|
return result
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
"""Create and return full copy of provider."""
|
"""Create and return full copy of provider."""
|
||||||
|
@ -254,6 +277,23 @@ cdef class Provider(object):
|
||||||
self.__overridden = tuple()
|
self.__overridden = tuple()
|
||||||
self.__last_overriding = None
|
self.__last_overriding = None
|
||||||
|
|
||||||
|
def async_(self, *args, **kwargs):
|
||||||
|
"""Return provided object asynchronously.
|
||||||
|
|
||||||
|
This method is a synonym of __call__().
|
||||||
|
It provides typing stubs for correct type checking with
|
||||||
|
`await` expression:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
database_provider: Provider[DatabaseConnection] = Resource(init_db_async)
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
db: DatabaseConnection = await database_provider.async_()
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
return self.__call__(*args, **kwargs)
|
||||||
|
|
||||||
def delegate(self):
|
def delegate(self):
|
||||||
"""Return provider's delegate.
|
"""Return provider's delegate.
|
||||||
|
|
||||||
|
@ -279,6 +319,33 @@ cdef class Provider(object):
|
||||||
"""Return :py:class:`ProvidedInstance` provider."""
|
"""Return :py:class:`ProvidedInstance` provider."""
|
||||||
return ProvidedInstance(self)
|
return ProvidedInstance(self)
|
||||||
|
|
||||||
|
def enable_async_mode(self):
|
||||||
|
"""Enable async mode."""
|
||||||
|
self.__async_mode = ASYNC_MODE_ENABLED
|
||||||
|
|
||||||
|
def disable_async_mode(self):
|
||||||
|
"""Disable async mode."""
|
||||||
|
self.__async_mode = ASYNC_MODE_DISABLED
|
||||||
|
|
||||||
|
def reset_async_mode(self):
|
||||||
|
"""Reset async mode.
|
||||||
|
|
||||||
|
Provider will automatically set the mode on the next call.
|
||||||
|
"""
|
||||||
|
self.__async_mode = ASYNC_MODE_UNDEFINED
|
||||||
|
|
||||||
|
def is_async_mode_enabled(self):
|
||||||
|
"""Check if async mode is enabled."""
|
||||||
|
return self.__async_mode == ASYNC_MODE_ENABLED
|
||||||
|
|
||||||
|
def is_async_mode_disabled(self):
|
||||||
|
"""Check if async mode is disabled."""
|
||||||
|
return self.__async_mode == ASYNC_MODE_DISABLED
|
||||||
|
|
||||||
|
def is_async_mode_undefined(self):
|
||||||
|
"""Check if async mode is undefined."""
|
||||||
|
return self.__async_mode == ASYNC_MODE_UNDEFINED
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
"""Providing strategy implementation.
|
"""Providing strategy implementation.
|
||||||
|
|
||||||
|
@ -472,18 +539,38 @@ cdef class Dependency(Provider):
|
||||||
|
|
||||||
:rtype: object
|
:rtype: object
|
||||||
"""
|
"""
|
||||||
cdef object instance
|
|
||||||
|
|
||||||
if self.__last_overriding is None:
|
if self.__last_overriding is None:
|
||||||
raise Error('Dependency is not defined')
|
raise Error('Dependency is not defined')
|
||||||
|
|
||||||
instance = self.__last_overriding(*args, **kwargs)
|
result = self.__last_overriding(*args, **kwargs)
|
||||||
|
|
||||||
if not isinstance(instance, self.instance_of):
|
|
||||||
raise Error('{0} is not an '.format(instance) +
|
|
||||||
'instance of {0}'.format(self.instance_of))
|
|
||||||
|
|
||||||
return instance
|
if self.is_async_mode_disabled():
|
||||||
|
self._check_instance_type(result)
|
||||||
|
return result
|
||||||
|
elif self.is_async_mode_enabled():
|
||||||
|
if __isawaitable(result):
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
result = asyncio.ensure_future(result)
|
||||||
|
result.add_done_callback(functools.partial(self._async_provide, future_result))
|
||||||
|
return future_result
|
||||||
|
else:
|
||||||
|
self._check_instance_type(result)
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
future_result.set_result(result)
|
||||||
|
return future_result
|
||||||
|
elif self.is_async_mode_undefined():
|
||||||
|
if __isawaitable(result):
|
||||||
|
self.enable_async_mode()
|
||||||
|
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
result = asyncio.ensure_future(result)
|
||||||
|
result.add_done_callback(functools.partial(self._async_provide, future_result))
|
||||||
|
return future_result
|
||||||
|
else:
|
||||||
|
self.disable_async_mode()
|
||||||
|
self._check_instance_type(result)
|
||||||
|
return result
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return string representation of provider.
|
"""Return string representation of provider.
|
||||||
|
@ -514,6 +601,19 @@ cdef class Dependency(Provider):
|
||||||
"""
|
"""
|
||||||
return self.override(provider)
|
return self.override(provider)
|
||||||
|
|
||||||
|
def _async_provide(self, future_result, future):
|
||||||
|
instance = future.result()
|
||||||
|
try:
|
||||||
|
self._check_instance_type(instance)
|
||||||
|
except Error as exception:
|
||||||
|
future_result.set_exception(exception)
|
||||||
|
else:
|
||||||
|
future_result.set_result(instance)
|
||||||
|
|
||||||
|
def _check_instance_type(self, instance):
|
||||||
|
if not isinstance(instance, self.instance_of):
|
||||||
|
raise Error('{0} is not an instance of {1}'.format(instance, self.instance_of))
|
||||||
|
|
||||||
|
|
||||||
cdef class ExternalDependency(Dependency):
|
cdef class ExternalDependency(Dependency):
|
||||||
""":py:class:`ExternalDependency` provider describes dependency interface.
|
""":py:class:`ExternalDependency` provider describes dependency interface.
|
||||||
|
@ -904,7 +1004,7 @@ cdef class AbstractCallable(Callable):
|
||||||
"""
|
"""
|
||||||
if self.__last_overriding is None:
|
if self.__last_overriding is None:
|
||||||
raise Error('{0} must be overridden before calling'.format(self))
|
raise Error('{0} must be overridden before calling'.format(self))
|
||||||
return self.__last_overriding(*args, **kwargs)
|
return super().__call__(*args, **kwargs)
|
||||||
|
|
||||||
def override(self, provider):
|
def override(self, provider):
|
||||||
"""Override provider with another provider.
|
"""Override provider with another provider.
|
||||||
|
@ -1020,7 +1120,7 @@ cdef class AbstractCoroutine(Coroutine):
|
||||||
"""
|
"""
|
||||||
if self.__last_overriding is None:
|
if self.__last_overriding is None:
|
||||||
raise Error('{0} must be overridden before calling'.format(self))
|
raise Error('{0} must be overridden before calling'.format(self))
|
||||||
return self.__last_overriding(*args, **kwargs)
|
return super().__call__(*args, **kwargs)
|
||||||
|
|
||||||
def override(self, provider):
|
def override(self, provider):
|
||||||
"""Override provider with another provider.
|
"""Override provider with another provider.
|
||||||
|
@ -1790,7 +1890,7 @@ cdef class AbstractFactory(Factory):
|
||||||
"""
|
"""
|
||||||
if self.__last_overriding is None:
|
if self.__last_overriding is None:
|
||||||
raise Error('{0} must be overridden before calling'.format(self))
|
raise Error('{0} must be overridden before calling'.format(self))
|
||||||
return self.__last_overriding(*args, **kwargs)
|
return super().__call__(*args, **kwargs)
|
||||||
|
|
||||||
def override(self, provider):
|
def override(self, provider):
|
||||||
"""Override provider with another provider.
|
"""Override provider with another provider.
|
||||||
|
@ -1881,13 +1981,6 @@ cdef class FactoryAggregate(Provider):
|
||||||
|
|
||||||
return copied
|
return copied
|
||||||
|
|
||||||
def __call__(self, factory_name, *args, **kwargs):
|
|
||||||
"""Create new object using factory with provided name.
|
|
||||||
|
|
||||||
Callable interface implementation.
|
|
||||||
"""
|
|
||||||
return self.__get_factory(factory_name)(*args, **kwargs)
|
|
||||||
|
|
||||||
def __getattr__(self, factory_name):
|
def __getattr__(self, factory_name):
|
||||||
"""Return aggregated factory."""
|
"""Return aggregated factory."""
|
||||||
return self.__get_factory(factory_name)
|
return self.__get_factory(factory_name)
|
||||||
|
@ -1915,6 +2008,19 @@ cdef class FactoryAggregate(Provider):
|
||||||
raise Error(
|
raise Error(
|
||||||
'{0} providers could not be overridden'.format(self.__class__))
|
'{0} providers could not be overridden'.format(self.__class__))
|
||||||
|
|
||||||
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
|
try:
|
||||||
|
factory_name = args[0]
|
||||||
|
except IndexError:
|
||||||
|
try:
|
||||||
|
factory_name = kwargs.pop('factory_name')
|
||||||
|
except KeyError:
|
||||||
|
raise TypeError('Factory missing 1 required positional argument: \'factory_name\'')
|
||||||
|
else:
|
||||||
|
args = args[1:]
|
||||||
|
|
||||||
|
return self.__get_factory(factory_name)(*args, **kwargs)
|
||||||
|
|
||||||
cdef Factory __get_factory(self, str factory_name):
|
cdef Factory __get_factory(self, str factory_name):
|
||||||
if factory_name not in self.__factories:
|
if factory_name not in self.__factories:
|
||||||
raise NoSuchProviderError(
|
raise NoSuchProviderError(
|
||||||
|
@ -2075,6 +2181,16 @@ cdef class BaseSingleton(Provider):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def _async_init_instance(self, future_result, result):
|
||||||
|
try:
|
||||||
|
instance = result.result()
|
||||||
|
except Exception as exception:
|
||||||
|
self.__storage = None
|
||||||
|
future_result.set_exception(exception)
|
||||||
|
else:
|
||||||
|
self.__storage = instance
|
||||||
|
future_result.set_result(instance)
|
||||||
|
|
||||||
|
|
||||||
cdef class Singleton(BaseSingleton):
|
cdef class Singleton(BaseSingleton):
|
||||||
"""Singleton provider returns same instance on every call.
|
"""Singleton provider returns same instance on every call.
|
||||||
|
@ -2122,13 +2238,24 @@ cdef class Singleton(BaseSingleton):
|
||||||
|
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
if __isawaitable(self.__storage):
|
||||||
|
asyncio.ensure_future(self.__storage).cancel()
|
||||||
self.__storage = None
|
self.__storage = None
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
"""Return single instance."""
|
"""Return single instance."""
|
||||||
if self.__storage is None:
|
if self.__storage is None:
|
||||||
self.__storage = __factory_call(self.__instantiator,
|
instance = __factory_call(self.__instantiator, args, kwargs)
|
||||||
args, kwargs)
|
|
||||||
|
if __isawaitable(instance):
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
instance = asyncio.ensure_future(instance)
|
||||||
|
instance.add_done_callback(functools.partial(self._async_init_instance, future_result))
|
||||||
|
self.__storage = future_result
|
||||||
|
return future_result
|
||||||
|
|
||||||
|
self.__storage = instance
|
||||||
|
|
||||||
return self.__storage
|
return self.__storage
|
||||||
|
|
||||||
|
|
||||||
|
@ -2179,18 +2306,30 @@ cdef class ThreadSafeSingleton(BaseSingleton):
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
with self.__storage_lock:
|
with self.__storage_lock:
|
||||||
|
if __isawaitable(self.__storage):
|
||||||
|
asyncio.ensure_future(self.__storage).cancel()
|
||||||
self.__storage = None
|
self.__storage = None
|
||||||
|
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
"""Return single instance."""
|
"""Return single instance."""
|
||||||
storage = self.__storage
|
instance = self.__storage
|
||||||
if storage is None:
|
|
||||||
|
if instance is None:
|
||||||
with self.__storage_lock:
|
with self.__storage_lock:
|
||||||
if self.__storage is None:
|
if self.__storage is None:
|
||||||
self.__storage = __factory_call(self.__instantiator,
|
instance = __factory_call(self.__instantiator, args, kwargs)
|
||||||
args, kwargs)
|
|
||||||
storage = self.__storage
|
if __isawaitable(instance):
|
||||||
return storage
|
future_result = asyncio.Future()
|
||||||
|
instance = asyncio.ensure_future(instance)
|
||||||
|
instance.add_done_callback(functools.partial(self._async_init_instance, future_result))
|
||||||
|
self.__storage = future_result
|
||||||
|
return future_result
|
||||||
|
|
||||||
|
self.__storage = instance
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
cdef class DelegatedThreadSafeSingleton(ThreadSafeSingleton):
|
cdef class DelegatedThreadSafeSingleton(ThreadSafeSingleton):
|
||||||
|
@ -2248,6 +2387,8 @@ cdef class ThreadLocalSingleton(BaseSingleton):
|
||||||
|
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
if __isawaitable(self.__storage.instance):
|
||||||
|
asyncio.ensure_future(self.__storage.instance).cancel()
|
||||||
del self.__storage.instance
|
del self.__storage.instance
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
|
@ -2258,10 +2399,28 @@ cdef class ThreadLocalSingleton(BaseSingleton):
|
||||||
instance = self.__storage.instance
|
instance = self.__storage.instance
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
instance = __factory_call(self.__instantiator, args, kwargs)
|
instance = __factory_call(self.__instantiator, args, kwargs)
|
||||||
|
|
||||||
|
if __isawaitable(instance):
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
instance = asyncio.ensure_future(instance)
|
||||||
|
instance.add_done_callback(functools.partial(self._async_init_instance, future_result))
|
||||||
|
self.__storage.instance = future_result
|
||||||
|
return future_result
|
||||||
|
|
||||||
self.__storage.instance = instance
|
self.__storage.instance = instance
|
||||||
finally:
|
finally:
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
def _async_init_instance(self, future_result, result):
|
||||||
|
try:
|
||||||
|
instance = result.result()
|
||||||
|
except Exception as exception:
|
||||||
|
del self.__storage.instance
|
||||||
|
future_result.set_exception(exception)
|
||||||
|
else:
|
||||||
|
self.__storage.instance = instance
|
||||||
|
future_result.set_result(instance)
|
||||||
|
|
||||||
|
|
||||||
cdef class DelegatedThreadLocalSingleton(ThreadLocalSingleton):
|
cdef class DelegatedThreadLocalSingleton(ThreadLocalSingleton):
|
||||||
"""Delegated thread-local singleton is injected "as is".
|
"""Delegated thread-local singleton is injected "as is".
|
||||||
|
@ -2302,7 +2461,7 @@ cdef class AbstractSingleton(BaseSingleton):
|
||||||
"""
|
"""
|
||||||
if self.__last_overriding is None:
|
if self.__last_overriding is None:
|
||||||
raise Error('{0} must be overridden before calling'.format(self))
|
raise Error('{0} must be overridden before calling'.format(self))
|
||||||
return self.__last_overriding(*args, **kwargs)
|
return super().__call__(*args, **kwargs)
|
||||||
|
|
||||||
def override(self, provider):
|
def override(self, provider):
|
||||||
"""Override provider with another provider.
|
"""Override provider with another provider.
|
||||||
|
@ -2705,18 +2864,30 @@ cdef class Resource(Provider):
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
"""Shutdown resource."""
|
"""Shutdown resource."""
|
||||||
if not self.__initialized:
|
if not self.__initialized:
|
||||||
|
if self.is_async_mode_enabled():
|
||||||
|
result = asyncio.Future()
|
||||||
|
result.set_result(None)
|
||||||
|
return result
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.__shutdowner:
|
if self.__shutdowner:
|
||||||
try:
|
try:
|
||||||
self.__shutdowner(self.__resource)
|
shutdown = self.__shutdowner(self.__resource)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
if inspect.isawaitable(shutdown):
|
||||||
|
return self._create_shutdown_future(shutdown)
|
||||||
|
|
||||||
self.__resource = None
|
self.__resource = None
|
||||||
self.__initialized = False
|
self.__initialized = False
|
||||||
self.__shutdowner = None
|
self.__shutdowner = None
|
||||||
|
|
||||||
|
if self.is_async_mode_enabled():
|
||||||
|
result = asyncio.Future()
|
||||||
|
result.set_result(None)
|
||||||
|
return result
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
if self.__initialized:
|
if self.__initialized:
|
||||||
return self.__resource
|
return self.__resource
|
||||||
|
@ -2733,6 +2904,19 @@ cdef class Resource(Provider):
|
||||||
self.__kwargs_len,
|
self.__kwargs_len,
|
||||||
)
|
)
|
||||||
self.__shutdowner = initializer.shutdown
|
self.__shutdowner = initializer.shutdown
|
||||||
|
elif self._is_async_resource_subclass(self.__initializer):
|
||||||
|
initializer = self.__initializer()
|
||||||
|
async_init = __call(
|
||||||
|
initializer.init,
|
||||||
|
args,
|
||||||
|
self.__args,
|
||||||
|
self.__args_len,
|
||||||
|
kwargs,
|
||||||
|
self.__kwargs,
|
||||||
|
self.__kwargs_len,
|
||||||
|
)
|
||||||
|
self.__initialized = True
|
||||||
|
return self._create_init_future(async_init, initializer.shutdown)
|
||||||
elif inspect.isgeneratorfunction(self.__initializer):
|
elif inspect.isgeneratorfunction(self.__initializer):
|
||||||
initializer = __call(
|
initializer = __call(
|
||||||
self.__initializer,
|
self.__initializer,
|
||||||
|
@ -2745,6 +2929,30 @@ cdef class Resource(Provider):
|
||||||
)
|
)
|
||||||
self.__resource = next(initializer)
|
self.__resource = next(initializer)
|
||||||
self.__shutdowner = initializer.send
|
self.__shutdowner = initializer.send
|
||||||
|
elif iscoroutinefunction(self.__initializer):
|
||||||
|
initializer = __call(
|
||||||
|
self.__initializer,
|
||||||
|
args,
|
||||||
|
self.__args,
|
||||||
|
self.__args_len,
|
||||||
|
kwargs,
|
||||||
|
self.__kwargs,
|
||||||
|
self.__kwargs_len,
|
||||||
|
)
|
||||||
|
self.__initialized = True
|
||||||
|
return self._create_init_future(initializer)
|
||||||
|
elif isasyncgenfunction(self.__initializer):
|
||||||
|
initializer = __call(
|
||||||
|
self.__initializer,
|
||||||
|
args,
|
||||||
|
self.__args,
|
||||||
|
self.__args_len,
|
||||||
|
kwargs,
|
||||||
|
self.__kwargs,
|
||||||
|
self.__kwargs_len,
|
||||||
|
)
|
||||||
|
self.__initialized = True
|
||||||
|
return self._create_init_future(initializer.__anext__(), initializer.asend)
|
||||||
elif callable(self.__initializer):
|
elif callable(self.__initializer):
|
||||||
self.__resource = __call(
|
self.__resource = __call(
|
||||||
self.__initializer,
|
self.__initializer,
|
||||||
|
@ -2761,6 +2969,45 @@ cdef class Resource(Provider):
|
||||||
self.__initialized = True
|
self.__initialized = True
|
||||||
return self.__resource
|
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 _async_init_callback(self, initializer, shutdowner=None):
|
||||||
|
try:
|
||||||
|
resource = initializer.result()
|
||||||
|
except Exception:
|
||||||
|
self.__initialized = False
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
self.__resource = resource
|
||||||
|
self.__shutdowner = shutdowner
|
||||||
|
|
||||||
|
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
|
@staticmethod
|
||||||
def _is_resource_subclass(instance):
|
def _is_resource_subclass(instance):
|
||||||
if sys.version_info < (3, 5):
|
if sys.version_info < (3, 5):
|
||||||
|
@ -2770,6 +3017,15 @@ cdef class Resource(Provider):
|
||||||
from . import resources
|
from . import resources
|
||||||
return issubclass(instance, resources.Resource)
|
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):
|
cdef class Container(Provider):
|
||||||
"""Container provider provides an instance of declarative container.
|
"""Container provider provides an instance of declarative container.
|
||||||
|
@ -3037,8 +3293,18 @@ cdef class AttributeGetter(Provider):
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
provided = self.__provider(*args, **kwargs)
|
provided = self.__provider(*args, **kwargs)
|
||||||
|
if __isawaitable(provided):
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
provided = asyncio.ensure_future(provided)
|
||||||
|
provided.add_done_callback(functools.partial(self._async_provide, future_result))
|
||||||
|
return future_result
|
||||||
return getattr(provided, self.__attribute)
|
return getattr(provided, self.__attribute)
|
||||||
|
|
||||||
|
def _async_provide(self, future_result, future):
|
||||||
|
provided = future.result()
|
||||||
|
result = getattr(provided, self.__attribute)
|
||||||
|
future_result.set_result(result)
|
||||||
|
|
||||||
|
|
||||||
cdef class ItemGetter(Provider):
|
cdef class ItemGetter(Provider):
|
||||||
"""Provider that returns the item of the injected instance.
|
"""Provider that returns the item of the injected instance.
|
||||||
|
@ -3087,8 +3353,18 @@ cdef class ItemGetter(Provider):
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
provided = self.__provider(*args, **kwargs)
|
provided = self.__provider(*args, **kwargs)
|
||||||
|
if __isawaitable(provided):
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
provided = asyncio.ensure_future(provided)
|
||||||
|
provided.add_done_callback(functools.partial(self._async_provide, future_result))
|
||||||
|
return future_result
|
||||||
return provided[self.__item]
|
return provided[self.__item]
|
||||||
|
|
||||||
|
def _async_provide(self, future_result, future):
|
||||||
|
provided = future.result()
|
||||||
|
result = provided[self.__item]
|
||||||
|
future_result.set_result(result)
|
||||||
|
|
||||||
|
|
||||||
cdef class MethodCaller(Provider):
|
cdef class MethodCaller(Provider):
|
||||||
"""Provider that calls the method of the injected instance.
|
"""Provider that calls the method of the injected instance.
|
||||||
|
@ -3169,6 +3445,11 @@ cdef class MethodCaller(Provider):
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
call = self.__provider()
|
call = self.__provider()
|
||||||
|
if __isawaitable(call):
|
||||||
|
future_result = asyncio.Future()
|
||||||
|
call = asyncio.ensure_future(call)
|
||||||
|
call.add_done_callback(functools.partial(self._async_provide, future_result, args, kwargs))
|
||||||
|
return future_result
|
||||||
return __call(
|
return __call(
|
||||||
call,
|
call,
|
||||||
args,
|
args,
|
||||||
|
@ -3179,6 +3460,19 @@ cdef class MethodCaller(Provider):
|
||||||
self.__kwargs_len,
|
self.__kwargs_len,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _async_provide(self, future_result, args, kwargs, future):
|
||||||
|
call = future.result()
|
||||||
|
result = __call(
|
||||||
|
call,
|
||||||
|
args,
|
||||||
|
self.__args,
|
||||||
|
self.__args_len,
|
||||||
|
kwargs,
|
||||||
|
self.__kwargs,
|
||||||
|
self.__kwargs_len,
|
||||||
|
)
|
||||||
|
future_result.set_result(result)
|
||||||
|
|
||||||
|
|
||||||
cdef class Injection(object):
|
cdef class Injection(object):
|
||||||
"""Abstract injection class."""
|
"""Abstract injection class."""
|
||||||
|
@ -3381,3 +3675,36 @@ def merge_dicts(dict1, dict2):
|
||||||
result = dict1.copy()
|
result = dict1.copy()
|
||||||
result.update(dict2)
|
result.update(dict2)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def isawaitable(obj):
|
||||||
|
"""Check if object is a coroutine function.
|
||||||
|
|
||||||
|
Return False for any object in Python 3.4 or below.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return inspect.isawaitable(obj)
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def iscoroutinefunction(obj):
|
||||||
|
"""Check if object is a coroutine function.
|
||||||
|
|
||||||
|
Return False for any object in Python 3.4 or below.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return inspect.iscoroutinefunction(obj)
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def isasyncgenfunction(obj):
|
||||||
|
"""Check if object is an asynchronous generator function.
|
||||||
|
|
||||||
|
Return False for any object in Python 3.4 or below.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return inspect.isasyncgenfunction(obj)
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
|
@ -29,3 +29,14 @@ class Resource(Generic[T], metaclass=ResourceMeta):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def shutdown(self, resource: T) -> None:
|
def shutdown(self, resource: T) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncResource(Generic[T], metaclass=ResourceMeta):
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
async def init(self, *args, **kwargs) -> T:
|
||||||
|
...
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
async def shutdown(self, resource: T) -> None:
|
||||||
|
...
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Wiring module."""
|
"""Wiring module."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import importlib
|
import importlib
|
||||||
|
@ -426,10 +427,20 @@ def _get_async_patched(fn):
|
||||||
@functools.wraps(fn)
|
@functools.wraps(fn)
|
||||||
async def _patched(*args, **kwargs):
|
async def _patched(*args, **kwargs):
|
||||||
to_inject = kwargs.copy()
|
to_inject = kwargs.copy()
|
||||||
|
to_inject_await = []
|
||||||
|
to_close_await = []
|
||||||
for injection, provider in _patched.__injections__.items():
|
for injection, provider in _patched.__injections__.items():
|
||||||
if injection not in kwargs \
|
if injection not in kwargs \
|
||||||
or _is_fastapi_default_arg_injection(injection, kwargs):
|
or _is_fastapi_default_arg_injection(injection, kwargs):
|
||||||
to_inject[injection] = provider()
|
provide = provider()
|
||||||
|
if inspect.isawaitable(provide):
|
||||||
|
to_inject_await.append((injection, provide))
|
||||||
|
else:
|
||||||
|
to_inject[injection] = provide
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
result = await fn(*args, **to_inject)
|
result = await fn(*args, **to_inject)
|
||||||
|
|
||||||
|
@ -439,7 +450,11 @@ def _get_async_patched(fn):
|
||||||
continue
|
continue
|
||||||
if not isinstance(provider, providers.Resource):
|
if not isinstance(provider, providers.Resource):
|
||||||
continue
|
continue
|
||||||
provider.shutdown()
|
shutdown = provider.shutdown()
|
||||||
|
if inspect.isawaitable(shutdown):
|
||||||
|
to_close_await.append(shutdown)
|
||||||
|
|
||||||
|
await asyncio.gather(*to_close_await)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
return _patched
|
return _patched
|
||||||
|
|
|
@ -50,3 +50,9 @@ animal7: Animal = provider7(1, 2, 3, b='1', c=2, e=0.0)
|
||||||
|
|
||||||
# Test 8: to check the CallableDelegate __init__
|
# Test 8: to check the CallableDelegate __init__
|
||||||
provider8 = providers.CallableDelegate(providers.Callable(lambda: None))
|
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)
|
||||||
|
|
|
@ -4,3 +4,9 @@ from dependency_injector import providers
|
||||||
# Test 1: to check the return type
|
# Test 1: to check the return type
|
||||||
provider1 = providers.Delegate(providers.Provider())
|
provider1 = providers.Delegate(providers.Provider())
|
||||||
var1: providers.Provider = 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 = await provider2() # type: ignore
|
||||||
|
var2: providers.Provider = await provider2.async_()
|
||||||
|
|
|
@ -20,3 +20,9 @@ var1: Animal = provider1()
|
||||||
# Test 2: to check the return type
|
# Test 2: to check the return type
|
||||||
provider2 = providers.Dependency(instance_of=Animal)
|
provider2 = providers.Dependency(instance_of=Animal)
|
||||||
var2: Type[Animal] = provider2.instance_of
|
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_()
|
||||||
|
|
|
@ -35,3 +35,13 @@ provider5 = providers.Dict(
|
||||||
a2=providers.Factory(object),
|
a2=providers.Factory(object),
|
||||||
)
|
)
|
||||||
provided5: providers.ProvidedInstance = provider5.provided
|
provided5: providers.ProvidedInstance = provider5.provided
|
||||||
|
|
||||||
|
|
||||||
|
# Test 6: to check the return type with await
|
||||||
|
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_()
|
||||||
|
|
|
@ -66,3 +66,9 @@ val9: Any = provider9('a')
|
||||||
# Test 10: to check the explicit typing
|
# Test 10: to check the explicit typing
|
||||||
factory10: providers.Provider[Animal] = providers.Factory(Cat)
|
factory10: providers.Provider[Animal] = providers.Factory(Cat)
|
||||||
animal10: Animal = factory10()
|
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)
|
||||||
|
|
|
@ -27,3 +27,12 @@ provided3: providers.ProvidedInstance = provider3.provided
|
||||||
attr_getter3: providers.AttributeGetter = provider3.provided.attr
|
attr_getter3: providers.AttributeGetter = provider3.provided.attr
|
||||||
item_getter3: providers.ItemGetter = provider3.provided['item']
|
item_getter3: providers.ItemGetter = provider3.provided['item']
|
||||||
method_caller3: providers.MethodCaller = provider3.provided.method.call(123, arg=324)
|
method_caller3: providers.MethodCaller = provider3.provided.method.call(123, arg=324)
|
||||||
|
|
||||||
|
# Test 4: to check the return type with await
|
||||||
|
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_()
|
||||||
|
|
|
@ -11,3 +11,9 @@ provided2: providers.ProvidedInstance = provider2.provided
|
||||||
attr_getter2: providers.AttributeGetter = provider2.provided.attr
|
attr_getter2: providers.AttributeGetter = provider2.provided.attr
|
||||||
item_getter2: providers.ItemGetter = provider2.provided['item']
|
item_getter2: providers.ItemGetter = provider2.provided['item']
|
||||||
method_caller2: providers.MethodCaller = provider2.provided.method.call(123, arg=324)
|
method_caller2: providers.MethodCaller = provider2.provided.method.call(123, arg=324)
|
||||||
|
|
||||||
|
# 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_()
|
||||||
|
|
|
@ -4,3 +4,12 @@ from dependency_injector import providers
|
||||||
# Test 1: to check .provided attribute
|
# Test 1: to check .provided attribute
|
||||||
provider1: providers.Provider[int] = providers.Object(1)
|
provider1: providers.Provider[int] = providers.Object(1)
|
||||||
provided: providers.ProvidedInstance = provider1.provided
|
provided: providers.ProvidedInstance = provider1.provided
|
||||||
|
|
||||||
|
# Test 2: to check async mode API
|
||||||
|
provider2: providers.Provider = providers.Provider()
|
||||||
|
provider2.enable_async_mode()
|
||||||
|
provider2.disable_async_mode()
|
||||||
|
provider2.reset_async_mode()
|
||||||
|
r1: bool = provider2.is_async_mode_enabled()
|
||||||
|
r2: bool = provider2.is_async_mode_disabled()
|
||||||
|
r3: bool = provider2.is_async_mode_undefined()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import List, Iterator, Generator
|
from typing import List, Iterator, Generator, AsyncIterator, AsyncGenerator
|
||||||
|
|
||||||
from dependency_injector import providers, resources
|
from dependency_injector import providers, resources
|
||||||
|
|
||||||
|
@ -41,3 +41,59 @@ class MyResource4(resources.Resource[List[int]]):
|
||||||
|
|
||||||
provider4 = providers.Resource(MyResource4)
|
provider4 = providers.Resource(MyResource4)
|
||||||
var4: List[int] = provider4()
|
var4: List[int] = provider4()
|
||||||
|
|
||||||
|
|
||||||
|
# Test 5: to check the return type with async function
|
||||||
|
async def init5() -> List[int]:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
provider5 = providers.Resource(init5)
|
||||||
|
|
||||||
|
|
||||||
|
async def _provide5() -> None:
|
||||||
|
var1: List[int] = await provider5() # type: ignore
|
||||||
|
var2: List[int] = await provider5.async_()
|
||||||
|
|
||||||
|
|
||||||
|
# Test 6: to check the return type with async iterator
|
||||||
|
async def init6() -> AsyncIterator[List[int]]:
|
||||||
|
yield []
|
||||||
|
|
||||||
|
|
||||||
|
provider6 = providers.Resource(init6)
|
||||||
|
|
||||||
|
|
||||||
|
async def _provide6() -> None:
|
||||||
|
var1: List[int] = await provider6() # type: ignore
|
||||||
|
var2: List[int] = await provider6.async_()
|
||||||
|
|
||||||
|
|
||||||
|
# Test 7: to check the return type with async generator
|
||||||
|
async def init7() -> AsyncGenerator[List[int], None]:
|
||||||
|
yield []
|
||||||
|
|
||||||
|
|
||||||
|
provider7 = providers.Resource(init7)
|
||||||
|
|
||||||
|
|
||||||
|
async def _provide7() -> None:
|
||||||
|
var1: List[int] = await provider7() # type: ignore
|
||||||
|
var2: List[int] = await provider7.async_()
|
||||||
|
|
||||||
|
|
||||||
|
# Test 8: to check the return type with async resource subclass
|
||||||
|
class MyResource8(resources.AsyncResource[List[int]]):
|
||||||
|
async def init(self, *args, **kwargs) -> List[int]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def shutdown(self, resource: List[int]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
provider8 = providers.Resource(MyResource8)
|
||||||
|
|
||||||
|
|
||||||
|
async def _provide8() -> None:
|
||||||
|
var1: List[int] = await provider8() # type: ignore
|
||||||
|
var2: List[int] = await provider8.async_()
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from dependency_injector import providers
|
from dependency_injector import providers
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +9,7 @@ provider1 = providers.Selector(
|
||||||
a=providers.Factory(object),
|
a=providers.Factory(object),
|
||||||
b=providers.Factory(object),
|
b=providers.Factory(object),
|
||||||
)
|
)
|
||||||
var1: int = provider1()
|
var1: Any = provider1()
|
||||||
|
|
||||||
# Test 2: to check the provided instance interface
|
# Test 2: to check the provided instance interface
|
||||||
provider2 = providers.Selector(
|
provider2 = providers.Selector(
|
||||||
|
@ -27,3 +29,13 @@ provider3 = providers.Selector(
|
||||||
b=providers.Factory(object),
|
b=providers.Factory(object),
|
||||||
)
|
)
|
||||||
attr3: providers.Provider = provider3.a
|
attr3: providers.Provider = provider3.a
|
||||||
|
|
||||||
|
# Test 4: to check the return type with await
|
||||||
|
provider4 = providers.Selector(
|
||||||
|
lambda: 'a',
|
||||||
|
a=providers.Factory(object),
|
||||||
|
b=providers.Factory(object),
|
||||||
|
)
|
||||||
|
async def _async4() -> None:
|
||||||
|
var1: Any = await provider4() # type: ignore
|
||||||
|
var2: Any = await provider4.async_()
|
||||||
|
|
|
@ -69,3 +69,9 @@ animal11: Animal = provider11(1, 2, 3, b='1', c=2, e=0.0)
|
||||||
|
|
||||||
# Test 12: to check the SingletonDelegate __init__
|
# Test 12: to check the SingletonDelegate __init__
|
||||||
provider12 = providers.SingletonDelegate(providers.Singleton(object))
|
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)
|
||||||
|
|
57
tests/unit/asyncutils.py
Normal file
57
tests/unit/asyncutils.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
"""Test utils."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import contextlib
|
||||||
|
import sys
|
||||||
|
import gc
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
def run(main):
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
return loop.run_until_complete(main)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_test_loop(
|
||||||
|
loop_factory=asyncio.new_event_loop
|
||||||
|
) -> asyncio.AbstractEventLoop:
|
||||||
|
loop = loop_factory()
|
||||||
|
try:
|
||||||
|
module = loop.__class__.__module__
|
||||||
|
skip_watcher = 'uvloop' in module
|
||||||
|
except AttributeError: # pragma: no cover
|
||||||
|
# Just in case
|
||||||
|
skip_watcher = True
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
if sys.platform != 'win32' and not skip_watcher:
|
||||||
|
policy = asyncio.get_event_loop_policy()
|
||||||
|
watcher = asyncio.SafeChildWatcher() # type: ignore
|
||||||
|
watcher.attach_loop(loop)
|
||||||
|
with contextlib.suppress(NotImplementedError):
|
||||||
|
policy.set_child_watcher(watcher)
|
||||||
|
return loop
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_test_loop(loop: asyncio.AbstractEventLoop, fast: bool = False) -> None:
|
||||||
|
closed = loop.is_closed()
|
||||||
|
if not closed:
|
||||||
|
loop.call_soon(loop.stop)
|
||||||
|
loop.run_forever()
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
if not fast:
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
asyncio.set_event_loop(None)
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.loop = setup_test_loop()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
teardown_test_loop(self.loop)
|
||||||
|
|
||||||
|
def _run(self, f):
|
||||||
|
return self.loop.run_until_complete(f)
|
71
tests/unit/containers/test_dynamic_async_resources_py36.py
Normal file
71
tests/unit/containers/test_dynamic_async_resources_py36.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
"""Dependency injector dynamic container unit tests for async resources."""
|
||||||
|
|
||||||
|
import unittest2 as unittest
|
||||||
|
|
||||||
|
# Runtime import to get asyncutils module
|
||||||
|
import os
|
||||||
|
_TOP_DIR = os.path.abspath(
|
||||||
|
os.path.sep.join((
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'../',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
import sys
|
||||||
|
sys.path.append(_TOP_DIR)
|
||||||
|
|
||||||
|
from asyncutils import AsyncTestCase
|
||||||
|
|
||||||
|
from dependency_injector import (
|
||||||
|
containers,
|
||||||
|
providers,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncResourcesTest(AsyncTestCase):
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.version_info[:2] <= (3, 5), 'Async test')
|
||||||
|
def test_async_init_resources(self):
|
||||||
|
async def _init1():
|
||||||
|
_init1.init_counter += 1
|
||||||
|
yield
|
||||||
|
_init1.shutdown_counter += 1
|
||||||
|
|
||||||
|
_init1.init_counter = 0
|
||||||
|
_init1.shutdown_counter = 0
|
||||||
|
|
||||||
|
async def _init2():
|
||||||
|
_init2.init_counter += 1
|
||||||
|
yield
|
||||||
|
_init2.shutdown_counter += 1
|
||||||
|
|
||||||
|
_init2.init_counter = 0
|
||||||
|
_init2.shutdown_counter = 0
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
resource1 = providers.Resource(_init1)
|
||||||
|
resource2 = providers.Resource(_init2)
|
||||||
|
|
||||||
|
container = Container()
|
||||||
|
self.assertEqual(_init1.init_counter, 0)
|
||||||
|
self.assertEqual(_init1.shutdown_counter, 0)
|
||||||
|
self.assertEqual(_init2.init_counter, 0)
|
||||||
|
self.assertEqual(_init2.shutdown_counter, 0)
|
||||||
|
|
||||||
|
self._run(container.init_resources())
|
||||||
|
self.assertEqual(_init1.init_counter, 1)
|
||||||
|
self.assertEqual(_init1.shutdown_counter, 0)
|
||||||
|
self.assertEqual(_init2.init_counter, 1)
|
||||||
|
self.assertEqual(_init2.shutdown_counter, 0)
|
||||||
|
|
||||||
|
self._run(container.shutdown_resources())
|
||||||
|
self.assertEqual(_init1.init_counter, 1)
|
||||||
|
self.assertEqual(_init1.shutdown_counter, 1)
|
||||||
|
self.assertEqual(_init2.init_counter, 1)
|
||||||
|
self.assertEqual(_init2.shutdown_counter, 1)
|
||||||
|
|
||||||
|
self._run(container.init_resources())
|
||||||
|
self._run(container.shutdown_resources())
|
||||||
|
self.assertEqual(_init1.init_counter, 2)
|
||||||
|
self.assertEqual(_init1.shutdown_counter, 2)
|
||||||
|
self.assertEqual(_init2.init_counter, 2)
|
||||||
|
self.assertEqual(_init2.shutdown_counter, 2)
|
|
@ -231,7 +231,3 @@ class DeclarativeContainerInstanceTests(unittest.TestCase):
|
||||||
self.assertEqual(_init1.shutdown_counter, 2)
|
self.assertEqual(_init1.shutdown_counter, 2)
|
||||||
self.assertEqual(_init2.init_counter, 2)
|
self.assertEqual(_init2.init_counter, 2)
|
||||||
self.assertEqual(_init2.shutdown_counter, 2)
|
self.assertEqual(_init2.shutdown_counter, 2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
|
|
818
tests/unit/providers/test_async_py36.py
Normal file
818
tests/unit/providers/test_async_py36.py
Normal file
|
@ -0,0 +1,818 @@
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers, errors
|
||||||
|
|
||||||
|
# Runtime import to get asyncutils module
|
||||||
|
import os
|
||||||
|
_TOP_DIR = os.path.abspath(
|
||||||
|
os.path.sep.join((
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'../',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
import sys
|
||||||
|
sys.path.append(_TOP_DIR)
|
||||||
|
|
||||||
|
from asyncutils import AsyncTestCase
|
||||||
|
|
||||||
|
|
||||||
|
RESOURCE1 = object()
|
||||||
|
RESOURCE2 = object()
|
||||||
|
|
||||||
|
|
||||||
|
async def init_resource(resource):
|
||||||
|
await asyncio.sleep(random.randint(1, 10) / 1000)
|
||||||
|
yield resource
|
||||||
|
await asyncio.sleep(random.randint(1, 10) / 1000)
|
||||||
|
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
def __init__(self, resource1: object, resource2: object) -> None:
|
||||||
|
self.resource1 = resource1
|
||||||
|
self.resource2 = resource2
|
||||||
|
|
||||||
|
|
||||||
|
class Service:
|
||||||
|
def __init__(self, client: Client) -> None:
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))
|
||||||
|
resource2 = providers.Resource(init_resource, providers.Object(RESOURCE2))
|
||||||
|
|
||||||
|
client = providers.Factory(
|
||||||
|
Client,
|
||||||
|
resource1=resource1,
|
||||||
|
resource2=resource2,
|
||||||
|
)
|
||||||
|
|
||||||
|
service = providers.Factory(
|
||||||
|
Service,
|
||||||
|
client=client,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FactoryTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_args_injection(self):
|
||||||
|
class ContainerWithArgs(containers.DeclarativeContainer):
|
||||||
|
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))
|
||||||
|
resource2 = providers.Resource(init_resource, providers.Object(RESOURCE2))
|
||||||
|
|
||||||
|
client = providers.Factory(
|
||||||
|
Client,
|
||||||
|
resource1,
|
||||||
|
resource2,
|
||||||
|
)
|
||||||
|
|
||||||
|
service = providers.Factory(
|
||||||
|
Service,
|
||||||
|
client,
|
||||||
|
)
|
||||||
|
|
||||||
|
container = ContainerWithArgs()
|
||||||
|
|
||||||
|
client1 = self._run(container.client())
|
||||||
|
client2 = self._run(container.client())
|
||||||
|
|
||||||
|
self.assertIsInstance(client1, Client)
|
||||||
|
self.assertIs(client1.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client1.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(client2, Client)
|
||||||
|
self.assertIs(client2.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client2.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
service1 = self._run(container.service())
|
||||||
|
service2 = self._run(container.service())
|
||||||
|
|
||||||
|
self.assertIsInstance(service1, Service)
|
||||||
|
self.assertIsInstance(service1.client, Client)
|
||||||
|
self.assertIs(service1.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service1.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(service2, Service)
|
||||||
|
self.assertIsInstance(service2.client, Client)
|
||||||
|
self.assertIs(service2.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service2.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsNot(service1.client, service2.client)
|
||||||
|
|
||||||
|
def test_kwargs_injection(self):
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
client1 = self._run(container.client())
|
||||||
|
client2 = self._run(container.client())
|
||||||
|
|
||||||
|
self.assertIsInstance(client1, Client)
|
||||||
|
self.assertIs(client1.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client1.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(client2, Client)
|
||||||
|
self.assertIs(client2.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client2.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
service1 = self._run(container.service())
|
||||||
|
service2 = self._run(container.service())
|
||||||
|
|
||||||
|
self.assertIsInstance(service1, Service)
|
||||||
|
self.assertIsInstance(service1.client, Client)
|
||||||
|
self.assertIs(service1.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service1.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(service2, Service)
|
||||||
|
self.assertIsInstance(service2.client, Client)
|
||||||
|
self.assertIs(service2.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service2.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsNot(service1.client, service2.client)
|
||||||
|
|
||||||
|
def test_context_kwargs_injection(self):
|
||||||
|
resource2_extra = object()
|
||||||
|
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
client1 = self._run(container.client(resource2=resource2_extra))
|
||||||
|
client2 = self._run(container.client(resource2=resource2_extra))
|
||||||
|
|
||||||
|
self.assertIsInstance(client1, Client)
|
||||||
|
self.assertIs(client1.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client1.resource2, resource2_extra)
|
||||||
|
|
||||||
|
self.assertIsInstance(client2, Client)
|
||||||
|
self.assertIs(client2.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client2.resource2, resource2_extra)
|
||||||
|
|
||||||
|
def test_args_kwargs_injection(self):
|
||||||
|
class ContainerWithArgsAndKwArgs(containers.DeclarativeContainer):
|
||||||
|
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))
|
||||||
|
resource2 = providers.Resource(init_resource, providers.Object(RESOURCE2))
|
||||||
|
|
||||||
|
client = providers.Factory(
|
||||||
|
Client,
|
||||||
|
resource1,
|
||||||
|
resource2=resource2,
|
||||||
|
)
|
||||||
|
|
||||||
|
service = providers.Factory(
|
||||||
|
Service,
|
||||||
|
client=client,
|
||||||
|
)
|
||||||
|
|
||||||
|
container = ContainerWithArgsAndKwArgs()
|
||||||
|
|
||||||
|
client1 = self._run(container.client())
|
||||||
|
client2 = self._run(container.client())
|
||||||
|
|
||||||
|
self.assertIsInstance(client1, Client)
|
||||||
|
self.assertIs(client1.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client1.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(client2, Client)
|
||||||
|
self.assertIs(client2.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client2.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
service1 = self._run(container.service())
|
||||||
|
service2 = self._run(container.service())
|
||||||
|
|
||||||
|
self.assertIsInstance(service1, Service)
|
||||||
|
self.assertIsInstance(service1.client, Client)
|
||||||
|
self.assertIs(service1.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service1.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(service2, Service)
|
||||||
|
self.assertIsInstance(service2.client, Client)
|
||||||
|
self.assertIs(service2.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service2.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsNot(service1.client, service2.client)
|
||||||
|
|
||||||
|
def test_attributes_injection(self):
|
||||||
|
class ContainerWithAttributes(containers.DeclarativeContainer):
|
||||||
|
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))
|
||||||
|
resource2 = providers.Resource(init_resource, providers.Object(RESOURCE2))
|
||||||
|
|
||||||
|
client = providers.Factory(
|
||||||
|
Client,
|
||||||
|
resource1,
|
||||||
|
resource2=None,
|
||||||
|
)
|
||||||
|
client.add_attributes(resource2=resource2)
|
||||||
|
|
||||||
|
service = providers.Factory(
|
||||||
|
Service,
|
||||||
|
client=None,
|
||||||
|
)
|
||||||
|
service.add_attributes(client=client)
|
||||||
|
|
||||||
|
container = ContainerWithAttributes()
|
||||||
|
|
||||||
|
client1 = self._run(container.client())
|
||||||
|
client2 = self._run(container.client())
|
||||||
|
|
||||||
|
self.assertIsInstance(client1, Client)
|
||||||
|
self.assertIs(client1.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client1.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(client2, Client)
|
||||||
|
self.assertIs(client2.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client2.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
service1 = self._run(container.service())
|
||||||
|
service2 = self._run(container.service())
|
||||||
|
|
||||||
|
self.assertIsInstance(service1, Service)
|
||||||
|
self.assertIsInstance(service1.client, Client)
|
||||||
|
self.assertIs(service1.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service1.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(service2, Service)
|
||||||
|
self.assertIsInstance(service2.client, Client)
|
||||||
|
self.assertIs(service2.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service2.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsNot(service1.client, service2.client)
|
||||||
|
|
||||||
|
|
||||||
|
class FactoryAggregateTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_async_mode(self):
|
||||||
|
object1 = object()
|
||||||
|
object2 = object()
|
||||||
|
|
||||||
|
async def _get_object1():
|
||||||
|
return object1
|
||||||
|
|
||||||
|
def _get_object2():
|
||||||
|
return object2
|
||||||
|
|
||||||
|
provider = providers.FactoryAggregate(
|
||||||
|
object1=providers.Factory(_get_object1),
|
||||||
|
object2=providers.Factory(_get_object2),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
created_object1 = self._run(provider('object1'))
|
||||||
|
self.assertIs(created_object1, object1)
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
created_object2 = self._run(provider('object2'))
|
||||||
|
self.assertIs(created_object2, object2)
|
||||||
|
|
||||||
|
|
||||||
|
class SingletonTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_injections(self):
|
||||||
|
class ContainerWithSingletons(containers.DeclarativeContainer):
|
||||||
|
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))
|
||||||
|
resource2 = providers.Resource(init_resource, providers.Object(RESOURCE2))
|
||||||
|
|
||||||
|
client = providers.Singleton(
|
||||||
|
Client,
|
||||||
|
resource1=resource1,
|
||||||
|
resource2=resource2,
|
||||||
|
)
|
||||||
|
|
||||||
|
service = providers.Singleton(
|
||||||
|
Service,
|
||||||
|
client=client,
|
||||||
|
)
|
||||||
|
|
||||||
|
container = ContainerWithSingletons()
|
||||||
|
|
||||||
|
client1 = self._run(container.client())
|
||||||
|
client2 = self._run(container.client())
|
||||||
|
|
||||||
|
self.assertIsInstance(client1, Client)
|
||||||
|
self.assertIs(client1.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client1.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(client2, Client)
|
||||||
|
self.assertIs(client2.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client2.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
service1 = self._run(container.service())
|
||||||
|
service2 = self._run(container.service())
|
||||||
|
|
||||||
|
self.assertIsInstance(service1, Service)
|
||||||
|
self.assertIsInstance(service1.client, Client)
|
||||||
|
self.assertIs(service1.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service1.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(service2, Service)
|
||||||
|
self.assertIsInstance(service2.client, Client)
|
||||||
|
self.assertIs(service2.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service2.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIs(service1, service2)
|
||||||
|
self.assertIs(service1.client, service2.client)
|
||||||
|
self.assertIs(service1.client, client1)
|
||||||
|
|
||||||
|
self.assertIs(service2.client, client2)
|
||||||
|
self.assertIs(client1, client2)
|
||||||
|
|
||||||
|
def test_async_mode(self):
|
||||||
|
instance = object()
|
||||||
|
|
||||||
|
async def create_instance():
|
||||||
|
return instance
|
||||||
|
|
||||||
|
provider = providers.Singleton(create_instance)
|
||||||
|
|
||||||
|
instance1 = self._run(provider())
|
||||||
|
instance2 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertIs(instance1, instance2)
|
||||||
|
self.assertIs(instance, instance)
|
||||||
|
|
||||||
|
def test_async_init_with_error(self):
|
||||||
|
# Disable default exception handling to prevent output
|
||||||
|
asyncio.get_event_loop().set_exception_handler(lambda loop, context: ...)
|
||||||
|
|
||||||
|
async def create_instance():
|
||||||
|
create_instance.counter += 1
|
||||||
|
raise RuntimeError()
|
||||||
|
|
||||||
|
create_instance.counter = 0
|
||||||
|
|
||||||
|
provider = providers.Singleton(create_instance)
|
||||||
|
|
||||||
|
|
||||||
|
future = provider()
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
self._run(future)
|
||||||
|
|
||||||
|
self.assertEqual(create_instance.counter, 1)
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
self._run(provider())
|
||||||
|
|
||||||
|
self.assertEqual(create_instance.counter, 2)
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
# Restore default exception handling
|
||||||
|
asyncio.get_event_loop().set_exception_handler(None)
|
||||||
|
|
||||||
|
|
||||||
|
class DelegatedSingletonTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_async_mode(self):
|
||||||
|
instance = object()
|
||||||
|
|
||||||
|
async def create_instance():
|
||||||
|
return instance
|
||||||
|
|
||||||
|
provider = providers.DelegatedSingleton(create_instance)
|
||||||
|
|
||||||
|
instance1 = self._run(provider())
|
||||||
|
instance2 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertIs(instance1, instance2)
|
||||||
|
self.assertIs(instance, instance)
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadSafeSingletonTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_async_mode(self):
|
||||||
|
instance = object()
|
||||||
|
|
||||||
|
async def create_instance():
|
||||||
|
return instance
|
||||||
|
|
||||||
|
provider = providers.ThreadSafeSingleton(create_instance)
|
||||||
|
|
||||||
|
instance1 = self._run(provider())
|
||||||
|
instance2 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertIs(instance1, instance2)
|
||||||
|
self.assertIs(instance, instance)
|
||||||
|
|
||||||
|
|
||||||
|
class DelegatedThreadSafeSingletonTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_async_mode(self):
|
||||||
|
instance = object()
|
||||||
|
|
||||||
|
async def create_instance():
|
||||||
|
return instance
|
||||||
|
|
||||||
|
provider = providers.DelegatedThreadSafeSingleton(create_instance)
|
||||||
|
|
||||||
|
instance1 = self._run(provider())
|
||||||
|
instance2 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertIs(instance1, instance2)
|
||||||
|
self.assertIs(instance, instance)
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadLocalSingletonTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_async_mode(self):
|
||||||
|
instance = object()
|
||||||
|
|
||||||
|
async def create_instance():
|
||||||
|
return instance
|
||||||
|
|
||||||
|
provider = providers.ThreadLocalSingleton(create_instance)
|
||||||
|
|
||||||
|
instance1 = self._run(provider())
|
||||||
|
instance2 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertIs(instance1, instance2)
|
||||||
|
self.assertIs(instance, instance)
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_init_with_error(self):
|
||||||
|
# Disable default exception handling to prevent output
|
||||||
|
asyncio.get_event_loop().set_exception_handler(lambda loop, context: ...)
|
||||||
|
|
||||||
|
async def create_instance():
|
||||||
|
create_instance.counter += 1
|
||||||
|
raise RuntimeError()
|
||||||
|
create_instance.counter = 0
|
||||||
|
|
||||||
|
provider = providers.ThreadLocalSingleton(create_instance)
|
||||||
|
|
||||||
|
future = provider()
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
self._run(future)
|
||||||
|
|
||||||
|
self.assertEqual(create_instance.counter, 1)
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
self._run(provider())
|
||||||
|
|
||||||
|
self.assertEqual(create_instance.counter, 2)
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
# Restore default exception handling
|
||||||
|
asyncio.get_event_loop().set_exception_handler(None)
|
||||||
|
|
||||||
|
|
||||||
|
class DelegatedThreadLocalSingletonTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_async_mode(self):
|
||||||
|
instance = object()
|
||||||
|
|
||||||
|
async def create_instance():
|
||||||
|
return instance
|
||||||
|
|
||||||
|
provider = providers.DelegatedThreadLocalSingleton(create_instance)
|
||||||
|
|
||||||
|
instance1 = self._run(provider())
|
||||||
|
instance2 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertIs(instance1, instance2)
|
||||||
|
self.assertIs(instance, instance)
|
||||||
|
|
||||||
|
|
||||||
|
class ProvidedInstanceTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_provided_attribute(self):
|
||||||
|
class TestClient:
|
||||||
|
def __init__(self, resource):
|
||||||
|
self.resource = resource
|
||||||
|
|
||||||
|
class TestService:
|
||||||
|
def __init__(self, resource):
|
||||||
|
self.resource = resource
|
||||||
|
|
||||||
|
class TestContainer(containers.DeclarativeContainer):
|
||||||
|
resource = providers.Resource(init_resource, providers.Object(RESOURCE1))
|
||||||
|
client = providers.Factory(TestClient, resource=resource)
|
||||||
|
service = providers.Factory(TestService, resource=client.provided.resource)
|
||||||
|
|
||||||
|
container = TestContainer()
|
||||||
|
|
||||||
|
instance1, instance2 = self._run(
|
||||||
|
asyncio.gather(
|
||||||
|
container.service(),
|
||||||
|
container.service(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIs(instance1.resource, RESOURCE1)
|
||||||
|
self.assertIs(instance2.resource, RESOURCE1)
|
||||||
|
self.assertIs(instance1.resource, instance2.resource)
|
||||||
|
|
||||||
|
def test_provided_item(self):
|
||||||
|
class TestClient:
|
||||||
|
def __init__(self, resource):
|
||||||
|
self.resource = resource
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return getattr(self, item)
|
||||||
|
|
||||||
|
class TestService:
|
||||||
|
def __init__(self, resource):
|
||||||
|
self.resource = resource
|
||||||
|
|
||||||
|
class TestContainer(containers.DeclarativeContainer):
|
||||||
|
resource = providers.Resource(init_resource, providers.Object(RESOURCE1))
|
||||||
|
client = providers.Factory(TestClient, resource=resource)
|
||||||
|
service = providers.Factory(TestService, resource=client.provided['resource'])
|
||||||
|
|
||||||
|
container = TestContainer()
|
||||||
|
|
||||||
|
instance1, instance2 = self._run(
|
||||||
|
asyncio.gather(
|
||||||
|
container.service(),
|
||||||
|
container.service(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIs(instance1.resource, RESOURCE1)
|
||||||
|
self.assertIs(instance2.resource, RESOURCE1)
|
||||||
|
self.assertIs(instance1.resource, instance2.resource)
|
||||||
|
|
||||||
|
def test_provided_method_call(self):
|
||||||
|
class TestClient:
|
||||||
|
def __init__(self, resource):
|
||||||
|
self.resource = resource
|
||||||
|
|
||||||
|
def get_resource(self):
|
||||||
|
return self.resource
|
||||||
|
|
||||||
|
class TestService:
|
||||||
|
def __init__(self, resource):
|
||||||
|
self.resource = resource
|
||||||
|
|
||||||
|
class TestContainer(containers.DeclarativeContainer):
|
||||||
|
resource = providers.Resource(init_resource, providers.Object(RESOURCE1))
|
||||||
|
client = providers.Factory(TestClient, resource=resource)
|
||||||
|
service = providers.Factory(TestService, resource=client.provided.get_resource.call())
|
||||||
|
|
||||||
|
container = TestContainer()
|
||||||
|
|
||||||
|
instance1, instance2 = self._run(
|
||||||
|
asyncio.gather(
|
||||||
|
container.service(),
|
||||||
|
container.service(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIs(instance1.resource, RESOURCE1)
|
||||||
|
self.assertIs(instance2.resource, RESOURCE1)
|
||||||
|
self.assertIs(instance1.resource, instance2.resource)
|
||||||
|
|
||||||
|
|
||||||
|
class DependencyTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_isinstance(self):
|
||||||
|
dependency = 1.0
|
||||||
|
|
||||||
|
async def get_async():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
provider = providers.Dependency(instance_of=float)
|
||||||
|
provider.override(providers.Callable(get_async))
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
dependency1 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
dependency2 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertEqual(dependency1, dependency)
|
||||||
|
self.assertEqual(dependency2, dependency)
|
||||||
|
|
||||||
|
def test_isinstance_invalid(self):
|
||||||
|
async def get_async():
|
||||||
|
return {}
|
||||||
|
|
||||||
|
provider = providers.Dependency(instance_of=float)
|
||||||
|
provider.override(providers.Callable(get_async))
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
with self.assertRaises(errors.Error):
|
||||||
|
self._run(provider())
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
def test_async_mode(self):
|
||||||
|
dependency = 123
|
||||||
|
|
||||||
|
async def get_async():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
def get_sync():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
provider = providers.Dependency(instance_of=int)
|
||||||
|
provider.override(providers.Factory(get_async))
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
dependency1 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
dependency2 = self._run(provider())
|
||||||
|
self.assertEqual(dependency1, dependency)
|
||||||
|
self.assertEqual(dependency2, dependency)
|
||||||
|
|
||||||
|
provider.override(providers.Factory(get_sync))
|
||||||
|
|
||||||
|
dependency3 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
dependency4 = self._run(provider())
|
||||||
|
self.assertEqual(dependency3, dependency)
|
||||||
|
self.assertEqual(dependency4, dependency)
|
||||||
|
|
||||||
|
|
||||||
|
class OverrideTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_provider(self):
|
||||||
|
dependency = object()
|
||||||
|
|
||||||
|
async def _get_dependency_async():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
def _get_dependency_sync():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
provider = providers.Provider()
|
||||||
|
|
||||||
|
provider.override(providers.Callable(_get_dependency_async))
|
||||||
|
dependency1 = self._run(provider())
|
||||||
|
|
||||||
|
provider.override(providers.Callable(_get_dependency_sync))
|
||||||
|
dependency2 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertIs(dependency1, dependency)
|
||||||
|
self.assertIs(dependency2, dependency)
|
||||||
|
|
||||||
|
def test_callable(self):
|
||||||
|
dependency = object()
|
||||||
|
|
||||||
|
async def _get_dependency_async():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
def _get_dependency_sync():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
provider = providers.Callable(_get_dependency_async)
|
||||||
|
dependency1 = self._run(provider())
|
||||||
|
|
||||||
|
provider.override(providers.Callable(_get_dependency_sync))
|
||||||
|
dependency2 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertIs(dependency1, dependency)
|
||||||
|
self.assertIs(dependency2, dependency)
|
||||||
|
|
||||||
|
def test_factory(self):
|
||||||
|
dependency = object()
|
||||||
|
|
||||||
|
async def _get_dependency_async():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
def _get_dependency_sync():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
provider = providers.Factory(_get_dependency_async)
|
||||||
|
dependency1 = self._run(provider())
|
||||||
|
|
||||||
|
provider.override(providers.Callable(_get_dependency_sync))
|
||||||
|
dependency2 = self._run(provider())
|
||||||
|
|
||||||
|
self.assertIs(dependency1, dependency)
|
||||||
|
self.assertIs(dependency2, dependency)
|
||||||
|
|
||||||
|
def test_async_mode_enabling(self):
|
||||||
|
dependency = object()
|
||||||
|
|
||||||
|
async def _get_dependency_async():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
provider = providers.Callable(_get_dependency_async)
|
||||||
|
self.assertTrue(provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
self._run(provider())
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
def test_async_mode_disabling(self):
|
||||||
|
dependency = object()
|
||||||
|
|
||||||
|
def _get_dependency():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
provider = providers.Callable(_get_dependency)
|
||||||
|
self.assertTrue(provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
provider()
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_disabled())
|
||||||
|
|
||||||
|
def test_async_mode_enabling_on_overriding(self):
|
||||||
|
dependency = object()
|
||||||
|
|
||||||
|
async def _get_dependency_async():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
provider = providers.Provider()
|
||||||
|
provider.override(providers.Callable(_get_dependency_async))
|
||||||
|
self.assertTrue(provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
self._run(provider())
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
def test_async_mode_disabling_on_overriding(self):
|
||||||
|
dependency = object()
|
||||||
|
|
||||||
|
def _get_dependency():
|
||||||
|
return dependency
|
||||||
|
|
||||||
|
provider = providers.Provider()
|
||||||
|
provider.override(providers.Callable(_get_dependency))
|
||||||
|
self.assertTrue(provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
provider()
|
||||||
|
|
||||||
|
self.assertTrue(provider.is_async_mode_disabled())
|
||||||
|
|
||||||
|
|
||||||
|
class TestAsyncModeApi(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.provider = providers.Provider()
|
||||||
|
|
||||||
|
def test_default_mode(self):
|
||||||
|
self.assertFalse(self.provider.is_async_mode_enabled())
|
||||||
|
self.assertFalse(self.provider.is_async_mode_disabled())
|
||||||
|
self.assertTrue(self.provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
def test_enable(self):
|
||||||
|
self.provider.enable_async_mode()
|
||||||
|
|
||||||
|
self.assertTrue(self.provider.is_async_mode_enabled())
|
||||||
|
self.assertFalse(self.provider.is_async_mode_disabled())
|
||||||
|
self.assertFalse(self.provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
def test_disable(self):
|
||||||
|
self.provider.disable_async_mode()
|
||||||
|
|
||||||
|
self.assertFalse(self.provider.is_async_mode_enabled())
|
||||||
|
self.assertTrue(self.provider.is_async_mode_disabled())
|
||||||
|
self.assertFalse(self.provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
def test_reset(self):
|
||||||
|
self.provider.enable_async_mode()
|
||||||
|
|
||||||
|
self.assertTrue(self.provider.is_async_mode_enabled())
|
||||||
|
self.assertFalse(self.provider.is_async_mode_disabled())
|
||||||
|
self.assertFalse(self.provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
self.provider.reset_async_mode()
|
||||||
|
|
||||||
|
self.assertFalse(self.provider.is_async_mode_enabled())
|
||||||
|
self.assertFalse(self.provider.is_async_mode_disabled())
|
||||||
|
self.assertTrue(self.provider.is_async_mode_undefined())
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncTypingStubTests(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_async_(self):
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
client1 = self._run(container.client.async_())
|
||||||
|
client2 = self._run(container.client.async_())
|
||||||
|
|
||||||
|
self.assertIsInstance(client1, Client)
|
||||||
|
self.assertIs(client1.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client1.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(client2, Client)
|
||||||
|
self.assertIs(client2.resource1, RESOURCE1)
|
||||||
|
self.assertIs(client2.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
service1 = self._run(container.service.async_())
|
||||||
|
service2 = self._run(container.service.async_())
|
||||||
|
|
||||||
|
self.assertIsInstance(service1, Service)
|
||||||
|
self.assertIsInstance(service1.client, Client)
|
||||||
|
self.assertIs(service1.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service1.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsInstance(service2, Service)
|
||||||
|
self.assertIsInstance(service2.client, Client)
|
||||||
|
self.assertIs(service2.client.resource1, RESOURCE1)
|
||||||
|
self.assertIs(service2.client.resource2, RESOURCE2)
|
||||||
|
|
||||||
|
self.assertIsNot(service1.client, service2.client)
|
|
@ -1,9 +1,6 @@
|
||||||
"""Dependency injector coroutine providers unit tests."""
|
"""Dependency injector coroutine providers unit tests."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
|
||||||
import sys
|
|
||||||
import gc
|
|
||||||
|
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
|
|
||||||
|
@ -12,6 +9,19 @@ from dependency_injector import (
|
||||||
errors,
|
errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Runtime import to get asyncutils module
|
||||||
|
import os
|
||||||
|
_TOP_DIR = os.path.abspath(
|
||||||
|
os.path.sep.join((
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'../',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
import sys
|
||||||
|
sys.path.append(_TOP_DIR)
|
||||||
|
|
||||||
|
from asyncutils import AsyncTestCase
|
||||||
|
|
||||||
|
|
||||||
async def _example(arg1, arg2, arg3, arg4):
|
async def _example(arg1, arg2, arg3, arg4):
|
||||||
future = asyncio.Future()
|
future = asyncio.Future()
|
||||||
|
@ -25,52 +35,6 @@ def run(main):
|
||||||
return loop.run_until_complete(main)
|
return loop.run_until_complete(main)
|
||||||
|
|
||||||
|
|
||||||
def setup_test_loop(
|
|
||||||
loop_factory=asyncio.new_event_loop
|
|
||||||
) -> asyncio.AbstractEventLoop:
|
|
||||||
loop = loop_factory()
|
|
||||||
try:
|
|
||||||
module = loop.__class__.__module__
|
|
||||||
skip_watcher = 'uvloop' in module
|
|
||||||
except AttributeError: # pragma: no cover
|
|
||||||
# Just in case
|
|
||||||
skip_watcher = True
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
if sys.platform != "win32" and not skip_watcher:
|
|
||||||
policy = asyncio.get_event_loop_policy()
|
|
||||||
watcher = asyncio.SafeChildWatcher() # type: ignore
|
|
||||||
watcher.attach_loop(loop)
|
|
||||||
with contextlib.suppress(NotImplementedError):
|
|
||||||
policy.set_child_watcher(watcher)
|
|
||||||
return loop
|
|
||||||
|
|
||||||
|
|
||||||
def teardown_test_loop(loop: asyncio.AbstractEventLoop,
|
|
||||||
fast: bool=False) -> None:
|
|
||||||
closed = loop.is_closed()
|
|
||||||
if not closed:
|
|
||||||
loop.call_soon(loop.stop)
|
|
||||||
loop.run_forever()
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
if not fast:
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
asyncio.set_event_loop(None)
|
|
||||||
|
|
||||||
|
|
||||||
class AsyncTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.loop = setup_test_loop()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
teardown_test_loop(self.loop)
|
|
||||||
|
|
||||||
def _run(self, f):
|
|
||||||
return self.loop.run_until_complete(f)
|
|
||||||
|
|
||||||
|
|
||||||
class CoroutineTests(AsyncTestCase):
|
class CoroutineTests(AsyncTestCase):
|
||||||
|
|
||||||
def test_init_with_coroutine(self):
|
def test_init_with_coroutine(self):
|
||||||
|
|
|
@ -520,6 +520,24 @@ class FactoryAggregateTests(unittest.TestCase):
|
||||||
self.assertEqual(object_b.init_arg3, 33)
|
self.assertEqual(object_b.init_arg3, 33)
|
||||||
self.assertEqual(object_b.init_arg4, 44)
|
self.assertEqual(object_b.init_arg4, 44)
|
||||||
|
|
||||||
|
def test_call_factory_name_as_kwarg(self):
|
||||||
|
object_a = self.factory_aggregate(
|
||||||
|
factory_name='example_a',
|
||||||
|
init_arg1=1,
|
||||||
|
init_arg2=2,
|
||||||
|
init_arg3=3,
|
||||||
|
init_arg4=4,
|
||||||
|
)
|
||||||
|
self.assertIsInstance(object_a, self.ExampleA)
|
||||||
|
self.assertEqual(object_a.init_arg1, 1)
|
||||||
|
self.assertEqual(object_a.init_arg2, 2)
|
||||||
|
self.assertEqual(object_a.init_arg3, 3)
|
||||||
|
self.assertEqual(object_a.init_arg4, 4)
|
||||||
|
|
||||||
|
def test_call_no_factory_name(self):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
self.factory_aggregate()
|
||||||
|
|
||||||
def test_call_no_such_provider(self):
|
def test_call_no_such_provider(self):
|
||||||
with self.assertRaises(errors.NoSuchProviderError):
|
with self.assertRaises(errors.NoSuchProviderError):
|
||||||
self.factory_aggregate('unknown')
|
self.factory_aggregate('unknown')
|
||||||
|
|
|
@ -1,11 +1,24 @@
|
||||||
"""Dependency injector resource provider unit tests."""
|
"""Dependency injector resource provider unit tests."""
|
||||||
|
|
||||||
import sys
|
import asyncio
|
||||||
|
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
|
|
||||||
from dependency_injector import containers, providers, resources, errors
|
from dependency_injector import containers, providers, resources, errors
|
||||||
|
|
||||||
|
# Runtime import to get asyncutils module
|
||||||
|
import os
|
||||||
|
_TOP_DIR = os.path.abspath(
|
||||||
|
os.path.sep.join((
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'../',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
import sys
|
||||||
|
sys.path.append(_TOP_DIR)
|
||||||
|
|
||||||
|
from asyncutils import AsyncTestCase
|
||||||
|
|
||||||
|
|
||||||
def init_fn(*args, **kwargs):
|
def init_fn(*args, **kwargs):
|
||||||
return args, kwargs
|
return args, kwargs
|
||||||
|
@ -156,6 +169,15 @@ class ResourceTests(unittest.TestCase):
|
||||||
self.assertEqual(_init.init_counter, 2)
|
self.assertEqual(_init.init_counter, 2)
|
||||||
self.assertEqual(_init.shutdown_counter, 2)
|
self.assertEqual(_init.shutdown_counter, 2)
|
||||||
|
|
||||||
|
def test_shutdown_of_not_initialized(self):
|
||||||
|
def _init():
|
||||||
|
yield
|
||||||
|
|
||||||
|
provider = providers.Resource(_init)
|
||||||
|
|
||||||
|
result = provider.shutdown()
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
def test_initialized(self):
|
def test_initialized(self):
|
||||||
provider = providers.Resource(init_fn)
|
provider = providers.Resource(init_fn)
|
||||||
self.assertFalse(provider.initialized)
|
self.assertFalse(provider.initialized)
|
||||||
|
@ -320,3 +342,186 @@ class ResourceTests(unittest.TestCase):
|
||||||
provider.initialized,
|
provider.initialized,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncResourceTest(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_init_async_function(self):
|
||||||
|
resource = object()
|
||||||
|
|
||||||
|
async def _init():
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
_init.counter += 1
|
||||||
|
return resource
|
||||||
|
_init.counter = 0
|
||||||
|
|
||||||
|
provider = providers.Resource(_init)
|
||||||
|
|
||||||
|
result1 = self._run(provider())
|
||||||
|
self.assertIs(result1, resource)
|
||||||
|
self.assertEqual(_init.counter, 1)
|
||||||
|
|
||||||
|
result2 = self._run(provider())
|
||||||
|
self.assertIs(result2, resource)
|
||||||
|
self.assertEqual(_init.counter, 1)
|
||||||
|
|
||||||
|
self._run(provider.shutdown())
|
||||||
|
|
||||||
|
def test_init_async_generator(self):
|
||||||
|
resource = object()
|
||||||
|
|
||||||
|
async def _init():
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
_init.init_counter += 1
|
||||||
|
|
||||||
|
yield resource
|
||||||
|
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
_init.shutdown_counter += 1
|
||||||
|
|
||||||
|
_init.init_counter = 0
|
||||||
|
_init.shutdown_counter = 0
|
||||||
|
|
||||||
|
provider = providers.Resource(_init)
|
||||||
|
|
||||||
|
result1 = self._run(provider())
|
||||||
|
self.assertIs(result1, resource)
|
||||||
|
self.assertEqual(_init.init_counter, 1)
|
||||||
|
self.assertEqual(_init.shutdown_counter, 0)
|
||||||
|
|
||||||
|
self._run(provider.shutdown())
|
||||||
|
self.assertEqual(_init.init_counter, 1)
|
||||||
|
self.assertEqual(_init.shutdown_counter, 1)
|
||||||
|
|
||||||
|
result2 = self._run(provider())
|
||||||
|
self.assertIs(result2, resource)
|
||||||
|
self.assertEqual(_init.init_counter, 2)
|
||||||
|
self.assertEqual(_init.shutdown_counter, 1)
|
||||||
|
|
||||||
|
self._run(provider.shutdown())
|
||||||
|
self.assertEqual(_init.init_counter, 2)
|
||||||
|
self.assertEqual(_init.shutdown_counter, 2)
|
||||||
|
|
||||||
|
def test_init_async_class(self):
|
||||||
|
resource = object()
|
||||||
|
|
||||||
|
class TestResource(resources.AsyncResource):
|
||||||
|
init_counter = 0
|
||||||
|
shutdown_counter = 0
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
self.__class__.init_counter += 1
|
||||||
|
return resource
|
||||||
|
|
||||||
|
async def shutdown(self, resource_):
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
self.__class__.shutdown_counter += 1
|
||||||
|
assert resource_ is resource
|
||||||
|
|
||||||
|
provider = providers.Resource(TestResource)
|
||||||
|
|
||||||
|
result1 = self._run(provider())
|
||||||
|
self.assertIs(result1, resource)
|
||||||
|
self.assertEqual(TestResource.init_counter, 1)
|
||||||
|
self.assertEqual(TestResource.shutdown_counter, 0)
|
||||||
|
|
||||||
|
self._run(provider.shutdown())
|
||||||
|
self.assertEqual(TestResource.init_counter, 1)
|
||||||
|
self.assertEqual(TestResource.shutdown_counter, 1)
|
||||||
|
|
||||||
|
result2 = self._run(provider())
|
||||||
|
self.assertIs(result2, resource)
|
||||||
|
self.assertEqual(TestResource.init_counter, 2)
|
||||||
|
self.assertEqual(TestResource.shutdown_counter, 1)
|
||||||
|
|
||||||
|
self._run(provider.shutdown())
|
||||||
|
self.assertEqual(TestResource.init_counter, 2)
|
||||||
|
self.assertEqual(TestResource.shutdown_counter, 2)
|
||||||
|
|
||||||
|
def test_init_with_error(self):
|
||||||
|
async def _init():
|
||||||
|
raise RuntimeError()
|
||||||
|
|
||||||
|
provider = providers.Resource(_init)
|
||||||
|
|
||||||
|
future = provider()
|
||||||
|
self.assertTrue(provider.initialized)
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
# Disable default exception handling to prevent output
|
||||||
|
asyncio.get_event_loop().set_exception_handler(lambda loop, context: ...)
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
self._run(future)
|
||||||
|
|
||||||
|
# Restore default exception handling
|
||||||
|
asyncio.get_event_loop().set_exception_handler(None)
|
||||||
|
|
||||||
|
self.assertFalse(provider.initialized)
|
||||||
|
self.assertTrue(provider.is_async_mode_enabled())
|
||||||
|
|
||||||
|
def test_init_and_shutdown_methods(self):
|
||||||
|
async def _init():
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
_init.init_counter += 1
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
_init.shutdown_counter += 1
|
||||||
|
|
||||||
|
_init.init_counter = 0
|
||||||
|
_init.shutdown_counter = 0
|
||||||
|
|
||||||
|
provider = providers.Resource(_init)
|
||||||
|
|
||||||
|
self._run(provider.init())
|
||||||
|
self.assertEqual(_init.init_counter, 1)
|
||||||
|
self.assertEqual(_init.shutdown_counter, 0)
|
||||||
|
|
||||||
|
self._run(provider.shutdown())
|
||||||
|
self.assertEqual(_init.init_counter, 1)
|
||||||
|
self.assertEqual(_init.shutdown_counter, 1)
|
||||||
|
|
||||||
|
self._run(provider.init())
|
||||||
|
self.assertEqual(_init.init_counter, 2)
|
||||||
|
self.assertEqual(_init.shutdown_counter, 1)
|
||||||
|
|
||||||
|
self._run(provider.shutdown())
|
||||||
|
self.assertEqual(_init.init_counter, 2)
|
||||||
|
self.assertEqual(_init.shutdown_counter, 2)
|
||||||
|
|
||||||
|
def test_shutdown_of_not_initialized(self):
|
||||||
|
async def _init():
|
||||||
|
yield
|
||||||
|
|
||||||
|
provider = providers.Resource(_init)
|
||||||
|
provider.enable_async_mode()
|
||||||
|
|
||||||
|
result = self._run(provider.shutdown())
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_concurrent_init(self):
|
||||||
|
resource = object()
|
||||||
|
|
||||||
|
async def _init():
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
_init.counter += 1
|
||||||
|
return resource
|
||||||
|
_init.counter = 0
|
||||||
|
|
||||||
|
provider = providers.Resource(_init)
|
||||||
|
|
||||||
|
result1, result2 = self._run(
|
||||||
|
asyncio.gather(
|
||||||
|
provider(),
|
||||||
|
provider()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIs(result1, resource)
|
||||||
|
self.assertEqual(_init.counter, 1)
|
||||||
|
|
||||||
|
self.assertIs(result2, resource)
|
||||||
|
self.assertEqual(_init.counter, 1)
|
||||||
|
|
50
tests/unit/samples/wiringsamples/asyncinjections.py
Normal file
50
tests/unit/samples/wiringsamples/asyncinjections.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from dependency_injector.wiring import inject, Provide, Closing
|
||||||
|
|
||||||
|
|
||||||
|
class TestResource:
|
||||||
|
def __init__(self):
|
||||||
|
self.init_counter = 0
|
||||||
|
self.shutdown_counter = 0
|
||||||
|
|
||||||
|
def reset_counters(self):
|
||||||
|
self.init_counter = 0
|
||||||
|
self.shutdown_counter = 0
|
||||||
|
|
||||||
|
|
||||||
|
resource1 = TestResource()
|
||||||
|
resource2 = TestResource()
|
||||||
|
|
||||||
|
|
||||||
|
async def async_resource(resource):
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
resource.init_counter += 1
|
||||||
|
|
||||||
|
yield resource
|
||||||
|
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
resource.shutdown_counter += 1
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
resource1 = providers.Resource(async_resource, providers.Object(resource1))
|
||||||
|
resource2 = providers.Resource(async_resource, providers.Object(resource2))
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def async_injection(
|
||||||
|
resource1: object = Provide[Container.resource1],
|
||||||
|
resource2: object = Provide[Container.resource2],
|
||||||
|
):
|
||||||
|
return resource1, resource2
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def async_injection_with_closing(
|
||||||
|
resource1: object = Closing[Provide[Container.resource1]],
|
||||||
|
resource2: object = Closing[Provide[Container.resource2]],
|
||||||
|
):
|
||||||
|
return resource1, resource2
|
|
@ -5,6 +5,12 @@ from dependency_injector.wiring import wire, Provide, Closing
|
||||||
|
|
||||||
# Runtime import to avoid syntax errors in samples on Python < 3.5
|
# Runtime import to avoid syntax errors in samples on Python < 3.5
|
||||||
import os
|
import os
|
||||||
|
_TOP_DIR = os.path.abspath(
|
||||||
|
os.path.sep.join((
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'../',
|
||||||
|
)),
|
||||||
|
)
|
||||||
_SAMPLES_DIR = os.path.abspath(
|
_SAMPLES_DIR = os.path.abspath(
|
||||||
os.path.sep.join((
|
os.path.sep.join((
|
||||||
os.path.dirname(__file__),
|
os.path.dirname(__file__),
|
||||||
|
@ -12,8 +18,11 @@ _SAMPLES_DIR = os.path.abspath(
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
import sys
|
import sys
|
||||||
|
sys.path.append(_TOP_DIR)
|
||||||
sys.path.append(_SAMPLES_DIR)
|
sys.path.append(_SAMPLES_DIR)
|
||||||
|
|
||||||
|
from asyncutils import AsyncTestCase
|
||||||
|
|
||||||
from wiringsamples import module, package
|
from wiringsamples import module, package
|
||||||
from wiringsamples.service import Service
|
from wiringsamples.service import Service
|
||||||
from wiringsamples.container import Container, SubContainer
|
from wiringsamples.container import Container, SubContainer
|
||||||
|
@ -267,3 +276,56 @@ class WiringAndFastAPITest(unittest.TestCase):
|
||||||
self.assertEqual(result_2.shutdown_counter, 2)
|
self.assertEqual(result_2.shutdown_counter, 2)
|
||||||
|
|
||||||
self.assertIsNot(result_1, result_2)
|
self.assertIsNot(result_1, result_2)
|
||||||
|
|
||||||
|
|
||||||
|
class WiringAsyncInjectionsTest(AsyncTestCase):
|
||||||
|
|
||||||
|
def test_async_injections(self):
|
||||||
|
from wiringsamples import asyncinjections
|
||||||
|
|
||||||
|
container = asyncinjections.Container()
|
||||||
|
container.wire(modules=[asyncinjections])
|
||||||
|
self.addCleanup(container.unwire)
|
||||||
|
|
||||||
|
asyncinjections.resource1.reset_counters()
|
||||||
|
asyncinjections.resource2.reset_counters()
|
||||||
|
|
||||||
|
resource1, resource2 = self._run(asyncinjections.async_injection())
|
||||||
|
|
||||||
|
self.assertIs(resource1, asyncinjections.resource1)
|
||||||
|
self.assertEqual(asyncinjections.resource1.init_counter, 1)
|
||||||
|
self.assertEqual(asyncinjections.resource1.shutdown_counter, 0)
|
||||||
|
|
||||||
|
self.assertIs(resource2, asyncinjections.resource2)
|
||||||
|
self.assertEqual(asyncinjections.resource2.init_counter, 1)
|
||||||
|
self.assertEqual(asyncinjections.resource2.shutdown_counter, 0)
|
||||||
|
|
||||||
|
def test_async_injections_with_closing(self):
|
||||||
|
from wiringsamples import asyncinjections
|
||||||
|
|
||||||
|
container = asyncinjections.Container()
|
||||||
|
container.wire(modules=[asyncinjections])
|
||||||
|
self.addCleanup(container.unwire)
|
||||||
|
|
||||||
|
asyncinjections.resource1.reset_counters()
|
||||||
|
asyncinjections.resource2.reset_counters()
|
||||||
|
|
||||||
|
resource1, resource2 = self._run(asyncinjections.async_injection_with_closing())
|
||||||
|
|
||||||
|
self.assertIs(resource1, asyncinjections.resource1)
|
||||||
|
self.assertEqual(asyncinjections.resource1.init_counter, 1)
|
||||||
|
self.assertEqual(asyncinjections.resource1.shutdown_counter, 1)
|
||||||
|
|
||||||
|
self.assertIs(resource2, asyncinjections.resource2)
|
||||||
|
self.assertEqual(asyncinjections.resource2.init_counter, 1)
|
||||||
|
self.assertEqual(asyncinjections.resource2.shutdown_counter, 1)
|
||||||
|
|
||||||
|
resource1, resource2 = self._run(asyncinjections.async_injection_with_closing())
|
||||||
|
|
||||||
|
self.assertIs(resource1, asyncinjections.resource1)
|
||||||
|
self.assertEqual(asyncinjections.resource1.init_counter, 2)
|
||||||
|
self.assertEqual(asyncinjections.resource1.shutdown_counter, 2)
|
||||||
|
|
||||||
|
self.assertIs(resource2, asyncinjections.resource2)
|
||||||
|
self.assertEqual(asyncinjections.resource2.init_counter, 2)
|
||||||
|
self.assertEqual(asyncinjections.resource2.shutdown_counter, 2)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import asyncio
|
|
||||||
import contextlib
|
|
||||||
import gc
|
|
||||||
import unittest
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
|
|
||||||
# Runtime import to avoid syntax errors in samples on Python < 3.5
|
# Runtime import to avoid syntax errors in samples on Python < 3.5 and reach top-dir
|
||||||
import os
|
import os
|
||||||
|
_TOP_DIR = os.path.abspath(
|
||||||
|
os.path.sep.join((
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'../',
|
||||||
|
)),
|
||||||
|
)
|
||||||
_SAMPLES_DIR = os.path.abspath(
|
_SAMPLES_DIR = os.path.abspath(
|
||||||
os.path.sep.join((
|
os.path.sep.join((
|
||||||
os.path.dirname(__file__),
|
os.path.dirname(__file__),
|
||||||
|
@ -15,58 +15,14 @@ _SAMPLES_DIR = os.path.abspath(
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
import sys
|
import sys
|
||||||
|
sys.path.append(_TOP_DIR)
|
||||||
sys.path.append(_SAMPLES_DIR)
|
sys.path.append(_SAMPLES_DIR)
|
||||||
|
|
||||||
|
from asyncutils import AsyncTestCase
|
||||||
|
|
||||||
from wiringfastapi import web
|
from wiringfastapi import web
|
||||||
|
|
||||||
|
|
||||||
# TODO: Refactor to use common async test case
|
|
||||||
def setup_test_loop(
|
|
||||||
loop_factory=asyncio.new_event_loop
|
|
||||||
) -> asyncio.AbstractEventLoop:
|
|
||||||
loop = loop_factory()
|
|
||||||
try:
|
|
||||||
module = loop.__class__.__module__
|
|
||||||
skip_watcher = 'uvloop' in module
|
|
||||||
except AttributeError: # pragma: no cover
|
|
||||||
# Just in case
|
|
||||||
skip_watcher = True
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
if sys.platform != "win32" and not skip_watcher:
|
|
||||||
policy = asyncio.get_event_loop_policy()
|
|
||||||
watcher = asyncio.SafeChildWatcher() # type: ignore
|
|
||||||
watcher.attach_loop(loop)
|
|
||||||
with contextlib.suppress(NotImplementedError):
|
|
||||||
policy.set_child_watcher(watcher)
|
|
||||||
return loop
|
|
||||||
|
|
||||||
|
|
||||||
def teardown_test_loop(loop: asyncio.AbstractEventLoop,
|
|
||||||
fast: bool=False) -> None:
|
|
||||||
closed = loop.is_closed()
|
|
||||||
if not closed:
|
|
||||||
loop.call_soon(loop.stop)
|
|
||||||
loop.run_forever()
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
if not fast:
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
asyncio.set_event_loop(None)
|
|
||||||
|
|
||||||
|
|
||||||
class AsyncTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.loop = setup_test_loop()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
teardown_test_loop(self.loop)
|
|
||||||
|
|
||||||
def _run(self, f):
|
|
||||||
return self.loop.run_until_complete(f)
|
|
||||||
|
|
||||||
|
|
||||||
class WiringFastAPITest(AsyncTestCase):
|
class WiringFastAPITest(AsyncTestCase):
|
||||||
|
|
||||||
client: AsyncClient
|
client: AsyncClient
|
||||||
|
|
Loading…
Reference in New Issue
Block a user