Configuration option typed injections (#284)

* Add implementation and tests

* Add docs page and examples

* Revert the api_client miniapp accidental changes
This commit is contained in:
Roman Mogylatov 2020-08-24 13:34:47 -04:00 committed by GitHub
parent 69ebc19b5f
commit f5b97ca92e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 2905 additions and 2479 deletions

View File

@ -96,4 +96,34 @@ where ``examples/providers/configuration/config.local.yml`` is:
.. literalinclude:: ../../examples/providers/configuration/config.local.yml .. literalinclude:: ../../examples/providers/configuration/config.local.yml
:language: ini :language: ini
Specifying value type
~~~~~~~~~~~~~~~~~~~~~
You can specify the type of the injected configuration value explicitly.
This helps when you read the value from the ini file or the environment variable and need to
convert it into an ``int`` or a ``float``.
.. literalinclude:: ../../examples/providers/configuration/configuration_type.py
:language: python
:lines: 3-
:emphasize-lines: 17
:py:class:`Configuration` provider has next helper methods:
- ``.as_int()``
- ``.as_float()``
- ``.as_(callback, *args, **kwargs)``
The last method ``.as_(callback, *args, **kwargs)`` helps to implement a other conversions.
.. literalinclude:: ../../examples/providers/configuration/configuration_type_custom.py
:language: python
:lines: 3-
:emphasize-lines: 16
With the ``.as_(callback, *args, **kwargs)`` you can specify the function that will be called
before the injection. The value from the config will be passed as a first argument. The returned
value will be injected. Parameters ``*args`` and ``**kwargs`` are handled as any other injections.
.. disqus:: .. disqus::

View File

@ -62,3 +62,5 @@ should use the :py:class:`ProvidedInstance` provider.
In all other cases you should not use :py:class:`ProvidedInstance`, :py:class:`AttributeGetter`, In all other cases you should not use :py:class:`ProvidedInstance`, :py:class:`AttributeGetter`,
:py:class:`ItemGetter`, or :py:class:`MethodCaller` providers directly. Use the ``.provided`` :py:class:`ItemGetter`, or :py:class:`MethodCaller` providers directly. Use the ``.provided``
attribute of the injected provider instead. attribute of the injected provider instead.
.. disqus::

View File

@ -0,0 +1,34 @@
"""`Configuration` provider type specification example."""
import os
from dependency_injector import providers
class ApiClient:
def __init__(self, api_key: str, timeout: int):
self.api_key = api_key
self.timeout = timeout
config = providers.Configuration()
api_client_factory = providers.Factory(
ApiClient,
api_key=config.api.key,
timeout=config.api.timeout.as_int(),
)
if __name__ == '__main__':
# Emulate environment variables
os.environ['API_KEY'] = 'secret'
os.environ['API_TIMEOUT'] = '5'
config.api.key.from_env('API_KEY')
config.api.timeout.from_env('API_TIMEOUT')
api_client = api_client_factory()
assert api_client.api_key == 'secret'
assert api_client.timeout == 5

View File

@ -0,0 +1,30 @@
"""`Configuration` provider custom type specification example."""
import os
import decimal
from dependency_injector import providers
class Calculator:
def __init__(self, pi: decimal.Decimal):
self.pi = pi
config = providers.Configuration()
calculator_factory = providers.Factory(
Calculator,
pi=config.pi.as_(decimal.Decimal),
)
if __name__ == '__main__':
# Emulate environment variables
os.environ['PI'] = '3.1415926535897932384626433832'
config.pi.from_env('PI')
calculator = calculator_factory()
assert calculator.pi == decimal.Decimal('3.1415926535897932384626433832')

File diff suppressed because it is too large Load Diff

View File

@ -1140,6 +1140,15 @@ cdef class ConfigurationOption(Provider):
root = self.__root_ref() root = self.__root_ref()
return '.'.join((root.get_name(), self._get_self_name())) return '.'.join((root.get_name(), self._get_self_name()))
def as_int(self):
return Callable(int, self)
def as_float(self):
return Callable(float, self)
def as_(self, callback, *args, **kwargs):
return Callable(callback, self, *args, **kwargs)
def override(self, value): def override(self, value):
if isinstance(value, Provider): if isinstance(value, Provider):
raise Error('Configuration option can only be overridden by a value') raise Error('Configuration option can only be overridden by a value')

View File

@ -1,6 +1,7 @@
"""Dependency injector config providers unit tests.""" """Dependency injector config providers unit tests."""
import contextlib import contextlib
import decimal
import os import os
import sys import sys
import tempfile import tempfile
@ -69,6 +70,33 @@ class ConfigTests(unittest.TestCase):
self.assertEqual(abc(), 1) self.assertEqual(abc(), 1)
self.assertEqual(abd(), 2) self.assertEqual(abd(), 2)
def test_as_int(self):
value_provider = providers.Callable(lambda value: value, self.config.test.as_int())
self.config.from_dict({'test': '123'})
value = value_provider()
self.assertEqual(value, 123)
def test_as_float(self):
value_provider = providers.Callable(lambda value: value, self.config.test.as_float())
self.config.from_dict({'test': '123.123'})
value = value_provider()
self.assertEqual(value, 123.123)
def test_as_(self):
value_provider = providers.Callable(
lambda value: value,
self.config.test.as_(decimal.Decimal),
)
self.config.from_dict({'test': '123.123'})
value = value_provider()
self.assertEqual(value, decimal.Decimal('123.123'))
def test_providers_value_override(self): def test_providers_value_override(self):
a = self.config.a a = self.config.a
ab = self.config.a.b ab = self.config.a.b
@ -358,7 +386,6 @@ class ConfigFromIniWithEnvInterpolationTests(unittest.TestCase):
self.assertEqual(self.config.section1.value1(), 'test-value') self.assertEqual(self.config.section1.value1(), 'test-value')
class ConfigFromYamlTests(unittest.TestCase): class ConfigFromYamlTests(unittest.TestCase):
def setUp(self): def setUp(self):