mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-06-16 11:33:13 +03:00
Merge branch 'release/4.5.0' into master
This commit is contained in:
commit
9f314fd7e9
|
@ -7,6 +7,14 @@ that were made in every particular version.
|
||||||
From version 0.7.6 *Dependency Injector* framework strictly
|
From version 0.7.6 *Dependency Injector* framework strictly
|
||||||
follows `Semantic versioning`_
|
follows `Semantic versioning`_
|
||||||
|
|
||||||
|
4.5.0
|
||||||
|
-----
|
||||||
|
- Add support of non-string keys for ``Dict`` provider.
|
||||||
|
- Add simple ``FastAPI`` example.
|
||||||
|
- Add ``Commands and Handlers`` example from
|
||||||
|
issue `#327 <https://github.com/ets-labs/python-dependency-injector/issues/327>`_.
|
||||||
|
- Add extra typing test for provided instance of ``DependenciesContainer`` provider.
|
||||||
|
|
||||||
4.4.1
|
4.4.1
|
||||||
-----
|
-----
|
||||||
- Improve ``FastAPI`` integration: handle ``Depends(Provide[...])``.
|
- Improve ``FastAPI`` integration: handle ``Depends(Provide[...])``.
|
||||||
|
|
|
@ -17,7 +17,21 @@ Dict provider
|
||||||
|
|
||||||
``Dict`` provider handles keyword arguments the same way as a :ref:`factory-provider`.
|
``Dict`` provider handles keyword arguments the same way as a :ref:`factory-provider`.
|
||||||
|
|
||||||
.. note::
|
To use non-string keys or keys with ``.`` and ``-`` provide a dictionary as a positional argument:
|
||||||
Positional argument are not supported.
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
providers.Dict({
|
||||||
|
SomeClass: providers.Factory(...),
|
||||||
|
'key.with.periods': providers.Factory(...),
|
||||||
|
'key-with-dashes': providers.Factory(...),
|
||||||
|
})
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/dict_non_string_keys.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 40-43
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
14
examples/miniapps/commands-and-handlers/README.rst
Normal file
14
examples/miniapps/commands-and-handlers/README.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
Commands and Handlers Example
|
||||||
|
=============================
|
||||||
|
|
||||||
|
This mixed example from issue `#327 <https://github.com/ets-labs/python-dependency-injector/issues/327>`_.
|
||||||
|
It demonstrates ``Dict`` provider with non-string keys and ``.provided`` attribute.
|
||||||
|
|
||||||
|
Run
|
||||||
|
---
|
||||||
|
|
||||||
|
To run the application do:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python -m application
|
|
@ -0,0 +1 @@
|
||||||
|
"""Top-level package."""
|
|
@ -0,0 +1,12 @@
|
||||||
|
"""Main module."""
|
||||||
|
|
||||||
|
from .containers import Container
|
||||||
|
from .commands import SaveRating, DoSomethingElse
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
container = Container()
|
||||||
|
message_bus = container.message_bus()
|
||||||
|
|
||||||
|
message_bus.handle(SaveRating)
|
||||||
|
message_bus.handle(DoSomethingElse)
|
|
@ -0,0 +1,12 @@
|
||||||
|
"""Commands module."""
|
||||||
|
|
||||||
|
class Command:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SaveRating(Command):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class DoSomethingElse(Command):
|
||||||
|
...
|
|
@ -0,0 +1,23 @@
|
||||||
|
"""Containers module."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
from . import repositories, handler, messagebus, commands
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
rating_repository = providers.Singleton(repositories.RatingRepository)
|
||||||
|
|
||||||
|
command_handler = providers.Singleton(
|
||||||
|
handler.CommandHandler,
|
||||||
|
rating_repo=rating_repository,
|
||||||
|
)
|
||||||
|
|
||||||
|
message_bus = providers.Factory(
|
||||||
|
messagebus.MessageBus,
|
||||||
|
command_handlers=providers.Dict({
|
||||||
|
commands.SaveRating: command_handler.provided.save_rating,
|
||||||
|
commands.DoSomethingElse: command_handler.provided.something_else,
|
||||||
|
}),
|
||||||
|
)
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""Handlers module."""
|
||||||
|
|
||||||
|
from .repositories import RatingRepository
|
||||||
|
|
||||||
|
|
||||||
|
class CommandHandler:
|
||||||
|
def __init__(self, rating_repo: RatingRepository):
|
||||||
|
self.rating_repo = rating_repo
|
||||||
|
|
||||||
|
def save_rating(self):
|
||||||
|
print('Saving rating')
|
||||||
|
|
||||||
|
def something_else(self):
|
||||||
|
print('Doing something else')
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""Message bus module."""
|
||||||
|
|
||||||
|
from typing import Dict, Callable, Any
|
||||||
|
|
||||||
|
from .commands import Command
|
||||||
|
|
||||||
|
|
||||||
|
class MessageBus:
|
||||||
|
|
||||||
|
def __init__(self, command_handlers: Dict[str, Callable[..., Any]]):
|
||||||
|
self.command_handlers = command_handlers
|
||||||
|
|
||||||
|
def handle(self, command: Command):
|
||||||
|
self.command_handlers[command]()
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""Repositories module."""
|
||||||
|
|
||||||
|
|
||||||
|
class RatingRepository:
|
||||||
|
...
|
29
examples/miniapps/fastapi-simple/fastapi_di_example.py
Normal file
29
examples/miniapps/fastapi-simple/fastapi_di_example.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Depends
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from dependency_injector.wiring import inject, Provide
|
||||||
|
|
||||||
|
|
||||||
|
class Service:
|
||||||
|
async def process(self) -> str:
|
||||||
|
return 'Ok'
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
service = providers.Factory(Service)
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
@app.api_route('/')
|
||||||
|
@inject
|
||||||
|
async def index(service: Service = Depends(Provide[Container.service])):
|
||||||
|
result = await service.process()
|
||||||
|
return {'result': result}
|
||||||
|
|
||||||
|
|
||||||
|
container = Container()
|
||||||
|
container.wire(modules=[sys.modules[__name__]])
|
25
examples/miniapps/fastapi-simple/tests.py
Normal file
25
examples/miniapps/fastapi-simple/tests.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from httpx import AsyncClient
|
||||||
|
|
||||||
|
from fastapi_di_example import app, container, 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'}
|
64
examples/providers/dict_non_string_keys.py
Normal file
64
examples/providers/dict_non_string_keys.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
"""`Dict` provider with non-string keys example."""
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
|
||||||
|
class Command:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class CommandA(Command):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class CommandB(Command):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Handler:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class HandlerA(Handler):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class HandlerB(Handler):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class Dispatcher:
|
||||||
|
command_handlers: Dict[Command, Handler]
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
dispatcher_factory = providers.Factory(
|
||||||
|
Dispatcher,
|
||||||
|
command_handlers=providers.Dict({
|
||||||
|
CommandA: providers.Factory(HandlerA),
|
||||||
|
CommandB: providers.Factory(HandlerB),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
dispatcher = container.dispatcher_factory()
|
||||||
|
|
||||||
|
assert isinstance(dispatcher.command_handlers, dict)
|
||||||
|
assert isinstance(dispatcher.command_handlers[CommandA], HandlerA)
|
||||||
|
assert isinstance(dispatcher.command_handlers[CommandB], HandlerB)
|
||||||
|
|
||||||
|
# Call "dispatcher = container.dispatcher_factory()" is equivalent to:
|
||||||
|
# dispatcher = Dispatcher(
|
||||||
|
# command_handlers={
|
||||||
|
# CommandA: HandlerA(),
|
||||||
|
# CommandB: HandlerB(),
|
||||||
|
# },
|
||||||
|
# )
|
|
@ -1,6 +1,6 @@
|
||||||
"""Top-level package."""
|
"""Top-level package."""
|
||||||
|
|
||||||
__version__ = '4.4.1'
|
__version__ = '4.5.0'
|
||||||
"""Version number.
|
"""Version number.
|
||||||
|
|
||||||
:type: str
|
:type: str
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -270,12 +270,12 @@ class List(Provider):
|
||||||
|
|
||||||
|
|
||||||
class Dict(Provider):
|
class Dict(Provider):
|
||||||
def __init__(self, **kwargs: Injection): ...
|
def __init__(self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection): ...
|
||||||
def __call__(self, *args: Injection, **kwargs: Injection) -> _Dict[Any, Any]: ...
|
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, **kwargs: Injection) -> Dict: ...
|
def add_kwargs(self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection) -> Dict: ...
|
||||||
def set_kwargs(self, **kwargs: Injection) -> Dict: ...
|
def set_kwargs(self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection) -> Dict: ...
|
||||||
def clear_kwargs(self) -> Dict: ...
|
def clear_kwargs(self) -> Dict: ...
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2484,11 +2484,11 @@ cdef class Dict(Provider):
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, dict_=None, **kwargs):
|
||||||
"""Initializer."""
|
"""Initializer."""
|
||||||
self.__kwargs = tuple()
|
self.__kwargs = tuple()
|
||||||
self.__kwargs_len = 0
|
self.__kwargs_len = 0
|
||||||
self.set_kwargs(**kwargs)
|
self.add_kwargs(dict_, **kwargs)
|
||||||
super(Dict, self).__init__()
|
super(Dict, self).__init__()
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
|
@ -2497,7 +2497,7 @@ cdef class Dict(Provider):
|
||||||
if copied is not None:
|
if copied is not None:
|
||||||
return copied
|
return copied
|
||||||
|
|
||||||
copied = self.__class__(**deepcopy(self.kwargs, memo))
|
copied = self.__class__(deepcopy(self.kwargs, memo))
|
||||||
self._copy_overridings(copied, memo)
|
self._copy_overridings(copied, memo)
|
||||||
|
|
||||||
return copied
|
return copied
|
||||||
|
@ -2522,24 +2522,34 @@ cdef class Dict(Provider):
|
||||||
kwargs[kwarg.__name] = kwarg.__value
|
kwargs[kwarg.__name] = kwarg.__value
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def add_kwargs(self, **kwargs):
|
def add_kwargs(self, dict_=None, **kwargs):
|
||||||
"""Add keyword argument injections.
|
"""Add keyword argument injections.
|
||||||
|
|
||||||
:return: Reference ``self``
|
:return: Reference ``self``
|
||||||
"""
|
"""
|
||||||
|
if dict_ is None:
|
||||||
|
dict_ = {}
|
||||||
|
|
||||||
|
self.__kwargs += parse_named_injections(dict_)
|
||||||
self.__kwargs += parse_named_injections(kwargs)
|
self.__kwargs += parse_named_injections(kwargs)
|
||||||
self.__kwargs_len = len(self.__kwargs)
|
self.__kwargs_len = len(self.__kwargs)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_kwargs(self, **kwargs):
|
def set_kwargs(self, dict_=None, **kwargs):
|
||||||
"""Set keyword argument injections.
|
"""Set keyword argument injections.
|
||||||
|
|
||||||
Existing keyword argument injections are dropped.
|
Existing keyword argument injections are dropped.
|
||||||
|
|
||||||
:return: Reference ``self``
|
:return: Reference ``self``
|
||||||
"""
|
"""
|
||||||
self.__kwargs = parse_named_injections(kwargs)
|
if dict_ is None:
|
||||||
|
dict_ = {}
|
||||||
|
|
||||||
|
self.__kwargs = parse_named_injections(dict_)
|
||||||
|
self.__kwargs += parse_named_injections(kwargs)
|
||||||
self.__kwargs_len = len(self.__kwargs)
|
self.__kwargs_len = len(self.__kwargs)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def clear_kwargs(self):
|
def clear_kwargs(self):
|
||||||
|
|
|
@ -8,3 +8,4 @@ provider1 = providers.DependenciesContainer(
|
||||||
)
|
)
|
||||||
a1: providers.Provider = provider1.a
|
a1: providers.Provider = provider1.a
|
||||||
b1: providers.Provider = provider1.b
|
b1: providers.Provider = provider1.b
|
||||||
|
c1: providers.ProvidedInstance = provider1.c.provided
|
||||||
|
|
37
tests/typing/dict.py
Normal file
37
tests/typing/dict.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from dependency_injector import providers
|
||||||
|
|
||||||
|
|
||||||
|
# Test 1: to check the return type (class)
|
||||||
|
provider1 = providers.Dict(
|
||||||
|
a1=providers.Factory(object),
|
||||||
|
a2=providers.Factory(object),
|
||||||
|
)
|
||||||
|
var1: Dict[Any, Any] = provider1()
|
||||||
|
|
||||||
|
|
||||||
|
# Test 2: to check init with non-string keys
|
||||||
|
provider2 = providers.Dict({object(): providers.Factory(object)})
|
||||||
|
var2: Dict[Any, Any] = provider2()
|
||||||
|
|
||||||
|
|
||||||
|
# Test 3: to check init with non-string keys
|
||||||
|
provider3 = providers.Dict({object(): providers.Factory(object)}, a2=providers.Factory(object))
|
||||||
|
var3: Dict[Any, Any] = provider3()
|
||||||
|
|
||||||
|
|
||||||
|
# Test 4: to check the .args attributes
|
||||||
|
provider4 = providers.Dict(
|
||||||
|
a1=providers.Factory(object),
|
||||||
|
a2=providers.Factory(object),
|
||||||
|
)
|
||||||
|
args4: Dict[Any, Any] = provider4.kwargs
|
||||||
|
|
||||||
|
|
||||||
|
# Test 5: to check the provided instance interface
|
||||||
|
provider5 = providers.Dict(
|
||||||
|
a1=providers.Factory(object),
|
||||||
|
a2=providers.Factory(object),
|
||||||
|
)
|
||||||
|
provided5: providers.ProvidedInstance = provider5.provided
|
|
@ -18,7 +18,7 @@ provider2 = providers.List(
|
||||||
)
|
)
|
||||||
args2: Tuple[Any] = provider2.args
|
args2: Tuple[Any] = provider2.args
|
||||||
|
|
||||||
# Test 5: to check the provided instance interface
|
# Test 3: to check the provided instance interface
|
||||||
provider3 = providers.List(
|
provider3 = providers.List(
|
||||||
providers.Factory(object),
|
providers.Factory(object),
|
||||||
providers.Factory(object),
|
providers.Factory(object),
|
||||||
|
|
|
@ -16,6 +16,31 @@ class DictTests(unittest.TestCase):
|
||||||
provider = providers.Dict()
|
provider = providers.Dict()
|
||||||
self.assertIsInstance(provider.provided, providers.ProvidedInstance)
|
self.assertIsInstance(provider.provided, providers.ProvidedInstance)
|
||||||
|
|
||||||
|
def test_init_with_non_string_keys(self):
|
||||||
|
a1 = object()
|
||||||
|
a2 = object()
|
||||||
|
provider = providers.Dict({a1: 'i1', a2: 'i2'})
|
||||||
|
|
||||||
|
dict1 = provider()
|
||||||
|
dict2 = provider()
|
||||||
|
|
||||||
|
self.assertEqual(dict1, {a1: 'i1', a2: 'i2'})
|
||||||
|
self.assertEqual(dict2, {a1: 'i1', a2: 'i2'})
|
||||||
|
|
||||||
|
self.assertIsNot(dict1, dict2)
|
||||||
|
|
||||||
|
def test_init_with_string_and_non_string_keys(self):
|
||||||
|
a1 = object()
|
||||||
|
provider = providers.Dict({a1: 'i1'}, a2='i2')
|
||||||
|
|
||||||
|
dict1 = provider()
|
||||||
|
dict2 = provider()
|
||||||
|
|
||||||
|
self.assertEqual(dict1, {a1: 'i1', 'a2': 'i2'})
|
||||||
|
self.assertEqual(dict2, {a1: 'i1', 'a2': 'i2'})
|
||||||
|
|
||||||
|
self.assertIsNot(dict1, dict2)
|
||||||
|
|
||||||
def test_call_with_init_keyword_args(self):
|
def test_call_with_init_keyword_args(self):
|
||||||
provider = providers.Dict(a1='i1', a2='i2')
|
provider = providers.Dict(a1='i1', a2='i2')
|
||||||
|
|
||||||
|
@ -46,12 +71,48 @@ class DictTests(unittest.TestCase):
|
||||||
.add_kwargs(a1='i1', a2='i2')
|
.add_kwargs(a1='i1', a2='i2')
|
||||||
self.assertEqual(provider(), {'a1': 'i1', 'a2': 'i2'})
|
self.assertEqual(provider(), {'a1': 'i1', 'a2': 'i2'})
|
||||||
|
|
||||||
|
def test_add_kwargs(self):
|
||||||
|
provider = providers.Dict() \
|
||||||
|
.add_kwargs(a1='i1') \
|
||||||
|
.add_kwargs(a2='i2')
|
||||||
|
self.assertEqual(provider.kwargs, {'a1': 'i1', 'a2': 'i2'})
|
||||||
|
|
||||||
|
def test_add_kwargs_non_string_keys(self):
|
||||||
|
a1 = object()
|
||||||
|
a2 = object()
|
||||||
|
provider = providers.Dict() \
|
||||||
|
.add_kwargs({a1: 'i1'}) \
|
||||||
|
.add_kwargs({a2: 'i2'})
|
||||||
|
self.assertEqual(provider.kwargs, {a1: 'i1', a2: 'i2'})
|
||||||
|
|
||||||
|
def test_add_kwargs_string_and_non_string_keys(self):
|
||||||
|
a2 = object()
|
||||||
|
provider = providers.Dict() \
|
||||||
|
.add_kwargs(a1='i1') \
|
||||||
|
.add_kwargs({a2: 'i2'})
|
||||||
|
self.assertEqual(provider.kwargs, {'a1': 'i1', a2: 'i2'})
|
||||||
|
|
||||||
def test_set_kwargs(self):
|
def test_set_kwargs(self):
|
||||||
provider = providers.Dict() \
|
provider = providers.Dict() \
|
||||||
.add_kwargs(a1='i1', a2='i2') \
|
.add_kwargs(a1='i1', a2='i2') \
|
||||||
.set_kwargs(a3='i3', a4='i4')
|
.set_kwargs(a3='i3', a4='i4')
|
||||||
self.assertEqual(provider.kwargs, {'a3': 'i3', 'a4': 'i4'})
|
self.assertEqual(provider.kwargs, {'a3': 'i3', 'a4': 'i4'})
|
||||||
|
|
||||||
|
def test_set_kwargs_non_string_keys(self):
|
||||||
|
a3 = object()
|
||||||
|
a4 = object()
|
||||||
|
provider = providers.Dict() \
|
||||||
|
.add_kwargs(a1='i1', a2='i2') \
|
||||||
|
.set_kwargs({a3: 'i3', a4: 'i4'})
|
||||||
|
self.assertEqual(provider.kwargs, {a3: 'i3', a4: 'i4'})
|
||||||
|
|
||||||
|
def test_set_kwargs_string_and_non_string_keys(self):
|
||||||
|
a3 = object()
|
||||||
|
provider = providers.Dict() \
|
||||||
|
.add_kwargs(a1='i1', a2='i2') \
|
||||||
|
.set_kwargs({a3: 'i3'}, a4='i4')
|
||||||
|
self.assertEqual(provider.kwargs, {a3: 'i3', 'a4': 'i4'})
|
||||||
|
|
||||||
def test_clear_kwargs(self):
|
def test_clear_kwargs(self):
|
||||||
provider = providers.Dict() \
|
provider = providers.Dict() \
|
||||||
.add_kwargs(a1='i1', a2='i2') \
|
.add_kwargs(a1='i1', a2='i2') \
|
||||||
|
|
Loading…
Reference in New Issue
Block a user