mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-28 04:23:59 +03:00
Wiring by string id (#403)
* Add prototype implementation * Implement wiring by string id * Fix pydocstyle errors * Refactor wiring module * Fix flake8 errors * Update changelog * Fix flake8 errors * Add example and docs
This commit is contained in:
parent
d9d811a4d4
commit
a4a84bea54
|
@ -9,6 +9,7 @@ follows `Semantic versioning`_
|
|||
|
||||
Development version
|
||||
-------------------
|
||||
- Add wiring by string id.
|
||||
- Improve error message for ``Dependency`` provider missing attribute.
|
||||
|
||||
4.25.1
|
||||
|
|
|
@ -88,6 +88,82 @@ Also you can use ``Provide`` marker to inject a container.
|
|||
:emphasize-lines: 16-19
|
||||
:lines: 3-
|
||||
|
||||
Strings identifiers
|
||||
-------------------
|
||||
|
||||
You can use wiring with string identifiers. String identifier should match provider name in the container:
|
||||
|
||||
.. literalinclude:: ../examples/wiring/example_string_id.py
|
||||
:language: python
|
||||
:emphasize-lines: 17
|
||||
:lines: 3-
|
||||
|
||||
With string identifiers you don't need to use a container to specify an injection.
|
||||
|
||||
To specify an injection from a nested container use point ``.`` as a separator:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
def foo(service: UserService = Provide['services.user']) -> None:
|
||||
...
|
||||
|
||||
You can also use injection modifiers:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from dependency_injector.wiring import (
|
||||
inject,
|
||||
Provide,
|
||||
as_int,
|
||||
as_float,
|
||||
as_,
|
||||
required,
|
||||
invariant,
|
||||
provided,
|
||||
)
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide['config.option', as_int()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: float = Provide['config.option', as_float()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: Decimal = Provide['config.option', as_(Decimal)]) -> None:
|
||||
...
|
||||
|
||||
@inject
|
||||
def foo(value: str = Provide['config.option', required()]) -> None:
|
||||
...
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide['config.option', required().as_int()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide['config.option', invariant('config.switch')]) -> None:
|
||||
...
|
||||
|
||||
@inject
|
||||
def foo(value: int = Provide['service', provided().foo['bar'].call()]) -> None:
|
||||
...
|
||||
|
||||
|
||||
To inject a container use special identifier ``<container>``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@inject
|
||||
def foo(container: Container = Provide['<container>']) -> None:
|
||||
...
|
||||
|
||||
Wiring with modules and packages
|
||||
--------------------------------
|
||||
|
||||
|
|
27
examples/wiring/example_string_id.py
Normal file
27
examples/wiring/example_string_id.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
"""Wiring string id example."""
|
||||
|
||||
import sys
|
||||
|
||||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
|
||||
class Service:
|
||||
...
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service = providers.Factory(Service)
|
||||
|
||||
|
||||
@inject
|
||||
def main(service: Service = Provide['service']) -> None:
|
||||
...
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
container = Container()
|
||||
container.wire(modules=[sys.modules[__name__]])
|
||||
|
||||
main()
|
|
@ -50,6 +50,12 @@ __all__ = (
|
|||
'wire',
|
||||
'unwire',
|
||||
'inject',
|
||||
'as_int',
|
||||
'as_float',
|
||||
'as_',
|
||||
'required',
|
||||
'invariant',
|
||||
'provided',
|
||||
'Provide',
|
||||
'Provider',
|
||||
'Closing',
|
||||
|
@ -85,16 +91,23 @@ _patched_registry = Registry()
|
|||
|
||||
class ProvidersMap:
|
||||
|
||||
CONTAINER_STRING_ID = '<container>'
|
||||
|
||||
def __init__(self, container):
|
||||
self._container = container
|
||||
self._map = self._create_providers_map(
|
||||
current_container=container,
|
||||
original_container=container.declarative_parent,
|
||||
original_container=(
|
||||
container.declarative_parent
|
||||
if container.declarative_parent
|
||||
else container
|
||||
),
|
||||
)
|
||||
|
||||
def resolve_provider(
|
||||
self,
|
||||
provider: providers.Provider,
|
||||
provider: Union[providers.Provider, str],
|
||||
modifier: Optional['Modifier'] = None,
|
||||
) -> Optional[providers.Provider]:
|
||||
if isinstance(provider, providers.Delegate):
|
||||
return self._resolve_delegate(provider)
|
||||
|
@ -109,14 +122,29 @@ class ProvidersMap:
|
|||
return self._resolve_config_option(provider)
|
||||
elif isinstance(provider, providers.TypedConfigurationOption):
|
||||
return self._resolve_config_option(provider.option, as_=provider.provides)
|
||||
elif isinstance(provider, str):
|
||||
return self._resolve_string_id(provider, modifier)
|
||||
else:
|
||||
return self._resolve_provider(provider)
|
||||
|
||||
def _resolve_delegate(
|
||||
def _resolve_string_id(
|
||||
self,
|
||||
original: providers.Delegate,
|
||||
id: str,
|
||||
modifier: Optional['Modifier'] = None,
|
||||
) -> Optional[providers.Provider]:
|
||||
return self._resolve_provider(original.provides)
|
||||
if id == self.CONTAINER_STRING_ID:
|
||||
return self._container.__self__
|
||||
|
||||
provider = self._container
|
||||
for segment in id.split('.'):
|
||||
try:
|
||||
provider = getattr(provider, segment)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
if modifier:
|
||||
provider = modifier.modify(provider, providers_map=self)
|
||||
return provider
|
||||
|
||||
def _resolve_provided_instance(
|
||||
self,
|
||||
|
@ -151,6 +179,12 @@ class ProvidersMap:
|
|||
|
||||
return new
|
||||
|
||||
def _resolve_delegate(
|
||||
self,
|
||||
original: providers.Delegate,
|
||||
) -> Optional[providers.Provider]:
|
||||
return self._resolve_provider(original.provides)
|
||||
|
||||
def _resolve_config_option(
|
||||
self,
|
||||
original: providers.ConfigurationOption,
|
||||
|
@ -184,7 +218,7 @@ class ProvidersMap:
|
|||
try:
|
||||
return self._map[original]
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _create_providers_map(
|
||||
|
@ -381,7 +415,7 @@ def _fetch_reference_injections(
|
|||
|
||||
def _bind_injections(fn: Callable[..., Any], providers_map: ProvidersMap) -> None:
|
||||
for injection, marker in fn.__reference_injections__.items():
|
||||
provider = providers_map.resolve_provider(marker.provider)
|
||||
provider = providers_map.resolve_provider(marker.provider, marker.modifier)
|
||||
|
||||
if provider is None:
|
||||
continue
|
||||
|
@ -516,20 +550,161 @@ def _is_declarative_container(instance: Any) -> bool:
|
|||
and getattr(instance, 'declarative_parent', None) is None)
|
||||
|
||||
|
||||
class Modifier:
|
||||
|
||||
def modify(
|
||||
self,
|
||||
provider: providers.ConfigurationOption,
|
||||
providers_map: ProvidersMap,
|
||||
) -> providers.Provider:
|
||||
...
|
||||
|
||||
|
||||
class TypeModifier(Modifier):
|
||||
|
||||
def __init__(self, type_: Type):
|
||||
self.type_ = type_
|
||||
|
||||
def modify(
|
||||
self,
|
||||
provider: providers.ConfigurationOption,
|
||||
providers_map: ProvidersMap,
|
||||
) -> providers.Provider:
|
||||
return provider.as_(self.type_)
|
||||
|
||||
|
||||
def as_int() -> TypeModifier:
|
||||
"""Return int type modifier."""
|
||||
return TypeModifier(int)
|
||||
|
||||
|
||||
def as_float() -> TypeModifier:
|
||||
"""Return float type modifier."""
|
||||
return TypeModifier(float)
|
||||
|
||||
|
||||
def as_(type_: Type) -> TypeModifier:
|
||||
"""Return custom type modifier."""
|
||||
return TypeModifier(type_)
|
||||
|
||||
|
||||
class RequiredModifier(Modifier):
|
||||
|
||||
def __init__(self):
|
||||
self.type_modifier = None
|
||||
|
||||
def as_int(self) -> 'RequiredModifier':
|
||||
self.type_modifier = TypeModifier(int)
|
||||
return self
|
||||
|
||||
def as_float(self) -> 'RequiredModifier':
|
||||
self.type_modifier = TypeModifier(float)
|
||||
return self
|
||||
|
||||
def as_(self, type_: Type) -> 'RequiredModifier':
|
||||
self.type_modifier = TypeModifier(type_)
|
||||
return self
|
||||
|
||||
def modify(
|
||||
self,
|
||||
provider: providers.ConfigurationOption,
|
||||
providers_map: ProvidersMap,
|
||||
) -> providers.Provider:
|
||||
provider = provider.required()
|
||||
if self.type_modifier:
|
||||
provider = provider.as_(self.type_modifier.type_)
|
||||
return provider
|
||||
|
||||
|
||||
def required() -> RequiredModifier:
|
||||
"""Return required modifier."""
|
||||
return RequiredModifier()
|
||||
|
||||
|
||||
class InvariantModifier(Modifier):
|
||||
|
||||
def __init__(self, id: str) -> None:
|
||||
self.id = id
|
||||
|
||||
def modify(
|
||||
self,
|
||||
provider: providers.ConfigurationOption,
|
||||
providers_map: ProvidersMap,
|
||||
) -> providers.Provider:
|
||||
invariant_segment = providers_map.resolve_provider(self.id)
|
||||
return provider[invariant_segment]
|
||||
|
||||
|
||||
def invariant(id: str) -> InvariantModifier:
|
||||
"""Return invariant modifier."""
|
||||
return InvariantModifier(id)
|
||||
|
||||
|
||||
class ProvidedInstance(Modifier):
|
||||
|
||||
TYPE_ATTRIBUTE = 'attr'
|
||||
TYPE_ITEM = 'item'
|
||||
TYPE_CALL = 'call'
|
||||
|
||||
def __init__(self):
|
||||
self.segments = []
|
||||
|
||||
def __getattr__(self, item):
|
||||
self.segments.append((self.TYPE_ATTRIBUTE, item))
|
||||
return self
|
||||
|
||||
def __getitem__(self, item):
|
||||
self.segments.append((self.TYPE_ITEM, item))
|
||||
return self
|
||||
|
||||
def call(self):
|
||||
self.segments.append((self.TYPE_CALL, None))
|
||||
return self
|
||||
|
||||
def modify(
|
||||
self,
|
||||
provider: providers.ConfigurationOption,
|
||||
providers_map: ProvidersMap,
|
||||
) -> providers.Provider:
|
||||
provider = provider.provided
|
||||
for type_, value in self.segments:
|
||||
if type_ == ProvidedInstance.TYPE_ATTRIBUTE:
|
||||
provider = getattr(provider, value)
|
||||
elif type_ == ProvidedInstance.TYPE_ITEM:
|
||||
provider = provider[value]
|
||||
elif type_ == ProvidedInstance.TYPE_CALL:
|
||||
provider = provider.call()
|
||||
return provider
|
||||
|
||||
|
||||
def provided() -> ProvidedInstance:
|
||||
"""Return provided instance modifier."""
|
||||
return ProvidedInstance()
|
||||
|
||||
|
||||
class ClassGetItemMeta(GenericMeta):
|
||||
def __getitem__(cls, item):
|
||||
# Spike for Python 3.6
|
||||
if isinstance(item, tuple):
|
||||
return cls(*item)
|
||||
return cls(item)
|
||||
|
||||
|
||||
class _Marker(Generic[T], metaclass=ClassGetItemMeta):
|
||||
|
||||
def __init__(self, provider: Union[providers.Provider, Container]) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
provider: Union[providers.Provider, Container, str],
|
||||
modifier: Optional[Modifier] = None,
|
||||
) -> None:
|
||||
if _is_declarative_container(provider):
|
||||
provider = provider.__self__
|
||||
self.provider: providers.Provider = provider
|
||||
self.provider = provider
|
||||
self.modifier = modifier
|
||||
|
||||
def __class_getitem__(cls, item) -> T:
|
||||
if isinstance(item, tuple):
|
||||
return cls(*item)
|
||||
return cls(item)
|
||||
|
||||
def __call__(self) -> T:
|
||||
|
|
50
tests/unit/samples/wiringstringidssamples/asyncinjections.py
Normal file
50
tests/unit/samples/wiringstringidssamples/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['resource1'],
|
||||
resource2: object = Provide['resource2'],
|
||||
):
|
||||
return resource1, resource2
|
||||
|
||||
|
||||
@inject
|
||||
async def async_injection_with_closing(
|
||||
resource1: object = Closing[Provide['resource1']],
|
||||
resource2: object = Closing[Provide['resource2']],
|
||||
):
|
||||
return resource1, resource2
|
17
tests/unit/samples/wiringstringidssamples/container.py
Normal file
17
tests/unit/samples/wiringstringidssamples/container.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from dependency_injector import containers, providers
|
||||
|
||||
from .service import Service
|
||||
|
||||
|
||||
class SubContainer(containers.DeclarativeContainer):
|
||||
|
||||
int_object = providers.Object(1)
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
config = providers.Configuration()
|
||||
|
||||
service = providers.Factory(Service)
|
||||
|
||||
sub = providers.Container(SubContainer)
|
116
tests/unit/samples/wiringstringidssamples/module.py
Normal file
116
tests/unit/samples/wiringstringidssamples/module.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
"""Test module for wiring."""
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Callable
|
||||
|
||||
from dependency_injector.wiring import inject, Provide, Provider, as_int, as_, required, invariant, provided
|
||||
|
||||
from .container import Container
|
||||
from .service import Service
|
||||
|
||||
|
||||
class TestClass:
|
||||
|
||||
@inject
|
||||
def __init__(self, service: Service = Provide['service']):
|
||||
self.service = service
|
||||
|
||||
@inject
|
||||
def method(self, service: Service = Provide['service']):
|
||||
return service
|
||||
|
||||
@classmethod
|
||||
@inject
|
||||
def class_method(cls, service: Service = Provide['service']):
|
||||
return service
|
||||
|
||||
@staticmethod
|
||||
@inject
|
||||
def static_method(service: Service = Provide['service']):
|
||||
return service
|
||||
|
||||
|
||||
@inject
|
||||
def test_function(service: Service = Provide['service']):
|
||||
return service
|
||||
|
||||
|
||||
@inject
|
||||
def test_function_provider(service_provider: Callable[..., Service] = Provider['service']):
|
||||
service = service_provider()
|
||||
return service
|
||||
|
||||
|
||||
@inject
|
||||
def test_config_value(
|
||||
value_int: int = Provide['config.a.b.c', as_int()],
|
||||
value_str: str = Provide['config.a.b.c', as_(str)],
|
||||
value_decimal: Decimal = Provide['config.a.b.c', as_(Decimal)],
|
||||
value_required: str = Provide['config.a.b.c', required()],
|
||||
value_required_int: int = Provide['config.a.b.c', required().as_int()],
|
||||
value_required_str: str = Provide['config.a.b.c', required().as_(str)],
|
||||
value_required_decimal: str = Provide['config.a.b.c', required().as_(Decimal)],
|
||||
):
|
||||
return (
|
||||
value_int,
|
||||
value_str,
|
||||
value_decimal,
|
||||
value_required,
|
||||
value_required_int,
|
||||
value_required_str,
|
||||
value_required_decimal,
|
||||
)
|
||||
|
||||
|
||||
@inject
|
||||
def test_config_value_required_undefined(
|
||||
value_required: int = Provide['config.a.b.c', required()],
|
||||
):
|
||||
return value_required
|
||||
|
||||
|
||||
@inject
|
||||
def test_provide_provider(service_provider: Callable[..., Service] = Provide['service.provider']):
|
||||
service = service_provider()
|
||||
return service
|
||||
|
||||
|
||||
@inject
|
||||
def test_provided_instance(some_value: int = Provide['service', provided().foo['bar'].call()]):
|
||||
return some_value
|
||||
|
||||
|
||||
@inject
|
||||
def test_subcontainer_provider(some_value: int = Provide['sub.int_object']):
|
||||
return some_value
|
||||
|
||||
|
||||
@inject
|
||||
def test_config_invariant(some_value: int = Provide['config.option', invariant('config.switch')]):
|
||||
return some_value
|
||||
|
||||
|
||||
@inject
|
||||
def test_provide_from_different_containers(
|
||||
service: Service = Provide['service'],
|
||||
some_value: int = Provide['int_object'],
|
||||
):
|
||||
return service, some_value
|
||||
|
||||
|
||||
class ClassDecorator:
|
||||
def __init__(self, fn):
|
||||
self._fn = fn
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._fn(*args, **kwargs)
|
||||
|
||||
|
||||
@ClassDecorator
|
||||
@inject
|
||||
def test_class_decorator(service: Service = Provide['service']):
|
||||
return service
|
||||
|
||||
|
||||
def test_container(container: Container = Provide['<container>']):
|
||||
return container.service()
|
|
@ -0,0 +1,11 @@
|
|||
import sys
|
||||
|
||||
if sys.version_info >= (3, 6):
|
||||
from dependency_injector.wiring import Provide
|
||||
|
||||
from ..container import Container
|
||||
from ..service import Service
|
||||
|
||||
|
||||
def test_package_function(service: Service = Provide[Container.service]):
|
||||
return service
|
|
@ -0,0 +1,11 @@
|
|||
import sys
|
||||
|
||||
if sys.version_info >= (3, 6):
|
||||
from dependency_injector.wiring import Provide
|
||||
|
||||
from ...container import Container
|
||||
from ...service import Service
|
||||
|
||||
|
||||
def test_package_function(service: Service = Provide[Container.service]):
|
||||
return service
|
|
@ -0,0 +1,8 @@
|
|||
from dependency_injector.wiring import inject, Provide
|
||||
|
||||
from ...service import Service
|
||||
|
||||
|
||||
@inject
|
||||
def test_function(service: Service = Provide['service']):
|
||||
return service
|
37
tests/unit/samples/wiringstringidssamples/resourceclosing.py
Normal file
37
tests/unit/samples/wiringstringidssamples/resourceclosing.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from dependency_injector import containers, providers
|
||||
from dependency_injector.wiring import inject, Provide, Closing
|
||||
|
||||
|
||||
class Service:
|
||||
init_counter: int = 0
|
||||
shutdown_counter: int = 0
|
||||
|
||||
@classmethod
|
||||
def reset_counter(cls):
|
||||
cls.init_counter = 0
|
||||
cls.shutdown_counter = 0
|
||||
|
||||
@classmethod
|
||||
def init(cls):
|
||||
cls.init_counter += 1
|
||||
|
||||
@classmethod
|
||||
def shutdown(cls):
|
||||
cls.shutdown_counter += 1
|
||||
|
||||
|
||||
def init_service():
|
||||
service = Service()
|
||||
service.init()
|
||||
yield service
|
||||
service.shutdown()
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
|
||||
service = providers.Resource(init_service)
|
||||
|
||||
|
||||
@inject
|
||||
def test_function(service: Service = Closing[Provide['service']]):
|
||||
return service
|
2
tests/unit/samples/wiringstringidssamples/service.py
Normal file
2
tests/unit/samples/wiringstringidssamples/service.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
class Service:
|
||||
service_attr: int
|
|
@ -169,6 +169,9 @@ class WiringTest(unittest.TestCase):
|
|||
}
|
||||
self.container.config.from_dict(config)
|
||||
|
||||
value_default = module.test_config_invariant()
|
||||
self.assertEqual(value_default, 1)
|
||||
|
||||
with self.container.config.switch.override('a'):
|
||||
value_a = module.test_config_invariant()
|
||||
self.assertEqual(value_a, 1)
|
||||
|
|
391
tests/unit/wiring/test_wiring_string_ids_py36.py
Normal file
391
tests/unit/wiring/test_wiring_string_ids_py36.py
Normal file
|
@ -0,0 +1,391 @@
|
|||
import contextlib
|
||||
from decimal import Decimal
|
||||
import importlib
|
||||
import unittest
|
||||
|
||||
from dependency_injector.wiring import (
|
||||
wire,
|
||||
Provide,
|
||||
Closing,
|
||||
register_loader_containers,
|
||||
unregister_loader_containers,
|
||||
)
|
||||
from dependency_injector import errors
|
||||
|
||||
# Runtime import to avoid syntax errors in samples on Python < 3.5
|
||||
import os
|
||||
_TOP_DIR = os.path.abspath(
|
||||
os.path.sep.join((
|
||||
os.path.dirname(__file__),
|
||||
'../',
|
||||
)),
|
||||
)
|
||||
_SAMPLES_DIR = os.path.abspath(
|
||||
os.path.sep.join((
|
||||
os.path.dirname(__file__),
|
||||
'../samples/',
|
||||
)),
|
||||
)
|
||||
import sys
|
||||
sys.path.append(_TOP_DIR)
|
||||
sys.path.append(_SAMPLES_DIR)
|
||||
|
||||
from asyncutils import AsyncTestCase
|
||||
|
||||
from wiringstringidssamples import module, package
|
||||
from wiringstringidssamples.service import Service
|
||||
from wiringstringidssamples.container import Container, SubContainer
|
||||
|
||||
|
||||
class WiringTest(unittest.TestCase):
|
||||
|
||||
container: Container
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.container = Container(config={'a': {'b': {'c': 10}}})
|
||||
self.container.wire(
|
||||
modules=[module],
|
||||
packages=[package],
|
||||
)
|
||||
self.addCleanup(self.container.unwire)
|
||||
|
||||
def test_package_lookup(self):
|
||||
from wiringstringidssamples.package import test_package_function
|
||||
service = test_package_function()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_package_subpackage_lookup(self):
|
||||
from wiringstringidssamples.package.subpackage import test_package_function
|
||||
service = test_package_function()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_package_submodule_lookup(self):
|
||||
from wiringstringidssamples.package.subpackage.submodule import test_function
|
||||
service = test_function()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_class_wiring(self):
|
||||
test_class_object = module.TestClass()
|
||||
self.assertIsInstance(test_class_object.service, Service)
|
||||
|
||||
def test_class_wiring_context_arg(self):
|
||||
test_service = self.container.service()
|
||||
|
||||
test_class_object = module.TestClass(service=test_service)
|
||||
self.assertIs(test_class_object.service, test_service)
|
||||
|
||||
def test_class_method_wiring(self):
|
||||
test_class_object = module.TestClass()
|
||||
service = test_class_object.method()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_class_classmethod_wiring(self):
|
||||
service = module.TestClass.class_method()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_instance_classmethod_wiring(self):
|
||||
instance = module.TestClass()
|
||||
service = instance.class_method()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_class_staticmethod_wiring(self):
|
||||
service = module.TestClass.static_method()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_instance_staticmethod_wiring(self):
|
||||
instance = module.TestClass()
|
||||
service = instance.static_method()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_function_wiring(self):
|
||||
service = module.test_function()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_function_wiring_context_arg(self):
|
||||
test_service = self.container.service()
|
||||
|
||||
service = module.test_function(service=test_service)
|
||||
self.assertIs(service, test_service)
|
||||
|
||||
def test_function_wiring_provider(self):
|
||||
service = module.test_function_provider()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_function_wiring_provider_context_arg(self):
|
||||
test_service = self.container.service()
|
||||
|
||||
service = module.test_function_provider(service_provider=lambda: test_service)
|
||||
self.assertIs(service, test_service)
|
||||
|
||||
def test_configuration_option(self):
|
||||
(
|
||||
value_int,
|
||||
value_str,
|
||||
value_decimal,
|
||||
value_required,
|
||||
value_required_int,
|
||||
value_required_str,
|
||||
value_required_decimal,
|
||||
) = module.test_config_value()
|
||||
|
||||
self.assertEqual(value_int, 10)
|
||||
self.assertEqual(value_str, '10')
|
||||
self.assertEqual(value_decimal, Decimal(10))
|
||||
self.assertEqual(value_required, 10)
|
||||
self.assertEqual(value_required_int, 10)
|
||||
self.assertEqual(value_required_str, '10')
|
||||
self.assertEqual(value_required_decimal, Decimal(10))
|
||||
|
||||
def test_configuration_option_required_undefined(self):
|
||||
self.container.config.reset_override()
|
||||
with self.assertRaisesRegex(errors.Error, 'Undefined configuration option "config.a.b.c"'):
|
||||
module.test_config_value_required_undefined()
|
||||
|
||||
def test_provide_provider(self):
|
||||
service = module.test_provide_provider()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_provided_instance(self):
|
||||
class TestService:
|
||||
foo = {
|
||||
'bar': lambda: 10,
|
||||
}
|
||||
|
||||
with self.container.service.override(TestService()):
|
||||
some_value = module.test_provided_instance()
|
||||
self.assertEqual(some_value, 10)
|
||||
|
||||
def test_subcontainer(self):
|
||||
some_value = module.test_subcontainer_provider()
|
||||
self.assertEqual(some_value, 1)
|
||||
|
||||
def test_config_invariant(self):
|
||||
config = {
|
||||
'option': {
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
},
|
||||
'switch': 'a',
|
||||
}
|
||||
self.container.config.from_dict(config)
|
||||
|
||||
value_default = module.test_config_invariant()
|
||||
self.assertEqual(value_default, 1)
|
||||
|
||||
with self.container.config.switch.override('a'):
|
||||
value_a = module.test_config_invariant()
|
||||
self.assertEqual(value_a, 1)
|
||||
|
||||
with self.container.config.switch.override('b'):
|
||||
value_b = module.test_config_invariant()
|
||||
self.assertEqual(value_b, 2)
|
||||
|
||||
def test_wire_with_class_error(self):
|
||||
with self.assertRaises(Exception):
|
||||
wire(
|
||||
container=Container,
|
||||
modules=[module],
|
||||
)
|
||||
|
||||
def test_unwire_function(self):
|
||||
self.container.unwire()
|
||||
self.assertIsInstance(module.test_function(), Provide)
|
||||
|
||||
def test_unwire_class(self):
|
||||
self.container.unwire()
|
||||
test_class_object = module.TestClass()
|
||||
self.assertIsInstance(test_class_object.service, Provide)
|
||||
|
||||
def test_unwire_class_method(self):
|
||||
self.container.unwire()
|
||||
test_class_object = module.TestClass()
|
||||
self.assertIsInstance(test_class_object.method(), Provide)
|
||||
|
||||
def test_unwire_package_function(self):
|
||||
self.container.unwire()
|
||||
from wiringstringidssamples.package.subpackage.submodule import test_function
|
||||
self.assertIsInstance(test_function(), Provide)
|
||||
|
||||
def test_unwire_package_function_by_reference(self):
|
||||
from wiringstringidssamples.package.subpackage import submodule
|
||||
self.container.unwire()
|
||||
self.assertIsInstance(submodule.test_function(), Provide)
|
||||
|
||||
def test_wire_multiple_containers(self):
|
||||
sub_container = SubContainer()
|
||||
sub_container.wire(
|
||||
modules=[module],
|
||||
packages=[package],
|
||||
)
|
||||
self.addCleanup(sub_container.unwire)
|
||||
|
||||
service, some_value = module.test_provide_from_different_containers()
|
||||
|
||||
self.assertIsInstance(service, Service)
|
||||
self.assertEqual(some_value, 1)
|
||||
|
||||
def test_closing_resource(self):
|
||||
from wiringstringidssamples import resourceclosing
|
||||
|
||||
resourceclosing.Service.reset_counter()
|
||||
|
||||
container = resourceclosing.Container()
|
||||
container.wire(modules=[resourceclosing])
|
||||
self.addCleanup(container.unwire)
|
||||
|
||||
result_1 = resourceclosing.test_function()
|
||||
self.assertIsInstance(result_1, resourceclosing.Service)
|
||||
self.assertEqual(result_1.init_counter, 1)
|
||||
self.assertEqual(result_1.shutdown_counter, 1)
|
||||
|
||||
result_2 = resourceclosing.test_function()
|
||||
self.assertIsInstance(result_2, resourceclosing.Service)
|
||||
self.assertEqual(result_2.init_counter, 2)
|
||||
self.assertEqual(result_2.shutdown_counter, 2)
|
||||
|
||||
self.assertIsNot(result_1, result_2)
|
||||
|
||||
def test_closing_resource_context(self):
|
||||
from wiringstringidssamples import resourceclosing
|
||||
|
||||
resourceclosing.Service.reset_counter()
|
||||
service = resourceclosing.Service()
|
||||
|
||||
container = resourceclosing.Container()
|
||||
container.wire(modules=[resourceclosing])
|
||||
self.addCleanup(container.unwire)
|
||||
|
||||
result_1 = resourceclosing.test_function(service=service)
|
||||
self.assertIs(result_1, service)
|
||||
self.assertEqual(result_1.init_counter, 0)
|
||||
self.assertEqual(result_1.shutdown_counter, 0)
|
||||
|
||||
result_2 = resourceclosing.test_function(service=service)
|
||||
self.assertIs(result_2, service)
|
||||
self.assertEqual(result_2.init_counter, 0)
|
||||
self.assertEqual(result_2.shutdown_counter, 0)
|
||||
|
||||
def test_class_decorator(self):
|
||||
service = module.test_class_decorator()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_container(self):
|
||||
service = module.test_container()
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
|
||||
class WiringAndFastAPITest(unittest.TestCase):
|
||||
|
||||
container: Container
|
||||
|
||||
def test_bypass_marker_injection(self):
|
||||
container = Container()
|
||||
container.wire(modules=[module])
|
||||
self.addCleanup(container.unwire)
|
||||
|
||||
service = module.test_function(service=Provide[Container.service])
|
||||
self.assertIsInstance(service, Service)
|
||||
|
||||
def test_closing_resource_bypass_marker_injection(self):
|
||||
from wiringstringidssamples import resourceclosing
|
||||
|
||||
resourceclosing.Service.reset_counter()
|
||||
|
||||
container = resourceclosing.Container()
|
||||
container.wire(modules=[resourceclosing])
|
||||
self.addCleanup(container.unwire)
|
||||
|
||||
result_1 = resourceclosing.test_function(
|
||||
service=Closing[Provide[resourceclosing.Container.service]],
|
||||
)
|
||||
self.assertIsInstance(result_1, resourceclosing.Service)
|
||||
self.assertEqual(result_1.init_counter, 1)
|
||||
self.assertEqual(result_1.shutdown_counter, 1)
|
||||
|
||||
result_2 = resourceclosing.test_function(
|
||||
service=Closing[Provide[resourceclosing.Container.service]],
|
||||
)
|
||||
self.assertIsInstance(result_2, resourceclosing.Service)
|
||||
self.assertEqual(result_2.init_counter, 2)
|
||||
self.assertEqual(result_2.shutdown_counter, 2)
|
||||
|
||||
self.assertIsNot(result_1, result_2)
|
||||
|
||||
|
||||
class WiringAsyncInjectionsTest(AsyncTestCase):
|
||||
|
||||
def test_async_injections(self):
|
||||
from wiringstringidssamples 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 wiringstringidssamples 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)
|
||||
|
||||
|
||||
# class AutoLoaderTest(unittest.TestCase):
|
||||
#
|
||||
# container: Container
|
||||
#
|
||||
# def setUp(self) -> None:
|
||||
# self.container = Container(config={'a': {'b': {'c': 10}}})
|
||||
# importlib.reload(module)
|
||||
#
|
||||
# def tearDown(self) -> None:
|
||||
# with contextlib.suppress(ValueError):
|
||||
# unregister_loader_containers(self.container)
|
||||
#
|
||||
# self.container.unwire()
|
||||
#
|
||||
# @classmethod
|
||||
# def tearDownClass(cls) -> None:
|
||||
# importlib.reload(module)
|
||||
#
|
||||
# def test_register_container(self):
|
||||
# register_loader_containers(self.container)
|
||||
# importlib.reload(module)
|
||||
#
|
||||
# service = module.test_function()
|
||||
# self.assertIsInstance(service, Service)
|
Loading…
Reference in New Issue
Block a user