Merge branch 'release/3.18.1' into master

This commit is contained in:
Roman Mogylatov 2020-06-25 23:49:37 -04:00
commit 783be3ad36
6 changed files with 5945 additions and 4210 deletions

View File

@ -7,6 +7,12 @@ 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`_
3.18.1
------
- Add interpolation of environment variables to ``Configuration.from_yaml()`` and
``Configuration.from_ini()``.
- Add ignoring of ``IOError`` to ``Configuration.from_yaml()``.
3.18.0 3.18.0
------ ------
- Add ``Configuration.from_yaml()`` method to load configuration from the yaml file. - Add ``Configuration.from_yaml()`` method to load configuration from the yaml file.

View File

@ -30,6 +30,10 @@ where ``examples/providers/configuration/config.ini`` is:
:language: ini :language: ini
:linenos: :linenos:
:py:meth:`Configuration.from_ini` supports environment variables interpolation. Use
``${ENV_NAME}`` format in the configuration file to substitute value of environment
variable ``ENV_NAME``.
Loading from ``yaml`` file Loading from ``yaml`` file
~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -48,6 +52,10 @@ where ``examples/providers/configuration/config.yml`` is:
:language: ini :language: ini
:linenos: :linenos:
:py:meth:`Configuration.from_yaml` supports environment variables interpolation. Use
``${ENV_NAME}`` format in the configuration file to substitute value of environment
variable ``ENV_NAME``.
.. note:: .. note::
Loading configuration from yaml requires ``PyYAML`` package. You can install Loading configuration from yaml requires ``PyYAML`` package. You can install

View File

@ -1,6 +1,6 @@
"""Dependency injector top-level package.""" """Dependency injector top-level package."""
__version__ = '3.18.0' __version__ = '3.18.1'
"""Version number that follows semantic versioning. """Version number that follows semantic versioning.
:type: str :type: str

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ Powered by Cython.
import copy import copy
import os import os
import re
import sys import sys
import types import types
import threading import threading
@ -49,6 +50,41 @@ else: # pragma: no cover
copy.deepcopy(obj.im_self, memo), copy.deepcopy(obj.im_self, memo),
obj.im_class) obj.im_class)
if yaml:
yaml_env_marker_pattern = re.compile(r'\$\{([^}^{]+)\}')
def yaml_env_marker_constructor(_, node):
""""Replace environment variable marker with its value."""
return os.path.expandvars(node.value)
yaml.add_implicit_resolver('!path', yaml_env_marker_pattern)
yaml.add_constructor('!path', yaml_env_marker_constructor)
if sys.version_info[0] == 3:
class EnvInterpolation(iniconfigparser.BasicInterpolation):
"""Interpolation which expands environment variables in values."""
def before_get(self, parser, section, option, value, defaults):
value = super().before_get(parser, section, option, value, defaults)
return os.path.expandvars(value)
def _parse_ini_file(filepath):
parser = iniconfigparser.ConfigParser(interpolation=EnvInterpolation())
parser.read(filepath)
return parser
else:
import StringIO
def _parse_ini_file(filepath):
parser = iniconfigparser.ConfigParser()
try:
with open(filepath) as config_file:
config_string = os.path.expandvars(config_file.read())
except IOError:
return parser
else:
parser.readfp(StringIO.StringIO(config_string))
return parser
cdef class Provider(object): cdef class Provider(object):
"""Base provider class. """Base provider class.
@ -1178,8 +1214,7 @@ cdef class Configuration(Object):
:rtype: None :rtype: None
""" """
parser = iniconfigparser.ConfigParser() parser = _parse_ini_file(filepath)
parser.read([filepath])
config = {} config = {}
for section in parser.sections(): for section in parser.sections():
@ -1207,8 +1242,11 @@ cdef class Configuration(Object):
'"pip install dependency-injector[yaml]"' '"pip install dependency-injector[yaml]"'
) )
with open(filepath) as opened_file: try:
config = yaml.load(opened_file, yaml.Loader) with open(filepath) as opened_file:
config = yaml.load(opened_file, yaml.Loader)
except IOError:
return
current_config = self.__call__() current_config = self.__call__()
if not current_config: if not current_config:

View File

@ -324,6 +324,41 @@ class ConfigFromIniTests(unittest.TestCase):
self.assertEqual(self.config.section3.value3(), '3') self.assertEqual(self.config.section3.value3(), '3')
class ConfigFromIniWithEnvInterpolationTests(unittest.TestCase):
def setUp(self):
self.config = providers.Configuration(name='config')
os.environ['CONFIG_TEST_ENV'] = 'test-value'
_, self.config_file = tempfile.mkstemp()
with open(self.config_file, 'w') as config_file:
config_file.write(
'[section1]\n'
'value1=${CONFIG_TEST_ENV}\n'
)
def tearDown(self):
del self.config
del os.environ['CONFIG_TEST_ENV']
os.unlink(self.config_file)
def test_env_variable_interpolation(self):
self.config.from_ini(self.config_file)
self.assertEqual(
self.config(),
{
'section1': {
'value1': 'test-value',
},
},
)
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):
@ -414,6 +449,41 @@ class ConfigFromYamlTests(unittest.TestCase):
) )
class ConfigFromYamlWithEnvInterpolationTests(unittest.TestCase):
def setUp(self):
self.config = providers.Configuration(name='config')
os.environ['CONFIG_TEST_ENV'] = 'test-value'
_, self.config_file = tempfile.mkstemp()
with open(self.config_file, 'w') as config_file:
config_file.write(
'section1:\n'
' value1: ${CONFIG_TEST_ENV}\n'
)
def tearDown(self):
del self.config
del os.environ['CONFIG_TEST_ENV']
os.unlink(self.config_file)
@unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
def test_env_variable_interpolation(self):
self.config.from_yaml(self.config_file)
self.assertEqual(
self.config(),
{
'section1': {
'value1': 'test-value',
},
},
)
self.assertEqual(self.config.section1(), {'value1': 'test-value'})
self.assertEqual(self.config.section1.value1(), 'test-value')
class ConfigFromDict(unittest.TestCase): class ConfigFromDict(unittest.TestCase):
def setUp(self): def setUp(self):