mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-11-04 09:57:37 +03:00 
			
		
		
		
	Make prototype with enterpolation before parsing
This commit is contained in:
		
							parent
							
								
									9abf34cb88
								
							
						
					
					
						commit
						b1d00915fd
					
				
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -201,7 +201,7 @@ class ConfigurationOption(Provider[Any]):
 | 
				
			||||||
    def is_required(self) -> bool: ...
 | 
					    def is_required(self) -> bool: ...
 | 
				
			||||||
    def update(self, value: Any) -> None: ...
 | 
					    def update(self, value: Any) -> None: ...
 | 
				
			||||||
    def from_ini(self, filepath: Union[Path, str], required: bool = False) -> None: ...
 | 
					    def from_ini(self, filepath: Union[Path, str], required: bool = False) -> None: ...
 | 
				
			||||||
    def from_yaml(self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any]=None) -> None: ...
 | 
					    def from_yaml(self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any] = None, envs_required: bool = False) -> None: ...
 | 
				
			||||||
    def from_pydantic(self, settings: PydanticSettings, required: bool = False, **kwargs: Any) -> None: ...
 | 
					    def from_pydantic(self, settings: PydanticSettings, required: bool = False, **kwargs: Any) -> None: ...
 | 
				
			||||||
    def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
 | 
					    def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
 | 
				
			||||||
    def from_env(self, name: str, default: Optional[Any] = None, required: bool = False) -> None: ...
 | 
					    def from_env(self, name: str, default: Optional[Any] = None, required: bool = False) -> None: ...
 | 
				
			||||||
| 
						 | 
					@ -238,7 +238,7 @@ class Configuration(Object[Any]):
 | 
				
			||||||
    def reset_cache(self) -> None: ...
 | 
					    def reset_cache(self) -> None: ...
 | 
				
			||||||
    def update(self, value: Any) -> None: ...
 | 
					    def update(self, value: Any) -> None: ...
 | 
				
			||||||
    def from_ini(self, filepath: Union[Path, str], required: bool = False) -> None: ...
 | 
					    def from_ini(self, filepath: Union[Path, str], required: bool = False) -> None: ...
 | 
				
			||||||
    def from_yaml(self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any]=None) -> None: ...
 | 
					    def from_yaml(self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any] = None, envs_required: bool = False) -> None: ...
 | 
				
			||||||
    def from_pydantic(self, settings: PydanticSettings, required: bool = False, **kwargs: Any) -> None: ...
 | 
					    def from_pydantic(self, settings: PydanticSettings, required: bool = False, **kwargs: Any) -> None: ...
 | 
				
			||||||
    def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
 | 
					    def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
 | 
				
			||||||
    def from_env(self, name: str, default: Optional[Any] = None, required: bool = False) -> None: ...
 | 
					    def from_env(self, name: str, default: Optional[Any] = None, required: bool = False) -> None: ...
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,15 +68,18 @@ config_env_marker_pattern = re.compile(
 | 
				
			||||||
    r'\${(?P<name>[^}^{:]+)(?P<separator>:?)(?P<default>.*?)}',
 | 
					    r'\${(?P<name>[^}^{:]+)(?P<separator>:?)(?P<default>.*?)}',
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _resolve_config_env_markers(config_value):
 | 
					def _resolve_config_env_markers(config_value, envs_required=False):
 | 
				
			||||||
    """"Replace environment variable markers with their values."""
 | 
					    """Replace environment variable markers with their values."""
 | 
				
			||||||
    for match in reversed(list(config_env_marker_pattern.finditer(config_value))):
 | 
					    findings = list(config_env_marker_pattern.finditer(config_value))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for match in reversed(findings):
 | 
				
			||||||
 | 
					        env_name = match.group('name')
 | 
				
			||||||
        has_default = match.group('separator') == ':'
 | 
					        has_default = match.group('separator') == ':'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        value = os.getenv(match.group('name'))
 | 
					        value = os.getenv(env_name)
 | 
				
			||||||
        if value is None:
 | 
					        if value is None:
 | 
				
			||||||
            if not has_default:
 | 
					            if not has_default and envs_required:
 | 
				
			||||||
                continue
 | 
					                raise ValueError(f'Missing required environment variable "{env_name}"')
 | 
				
			||||||
            value = match.group('default')
 | 
					            value = match.group('default')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        span_min, span_max = match.span()
 | 
					        span_min, span_max = match.span()
 | 
				
			||||||
| 
						 | 
					@ -85,17 +88,11 @@ def _resolve_config_env_markers(config_value):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if sys.version_info[0] == 3:
 | 
					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 _resolve_config_env_markers(value)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _parse_ini_file(filepath):
 | 
					    def _parse_ini_file(filepath):
 | 
				
			||||||
        parser = iniconfigparser.ConfigParser(interpolation=EnvInterpolation())
 | 
					        parser = iniconfigparser.ConfigParser()
 | 
				
			||||||
        with open(filepath) as config_file:
 | 
					        with open(filepath) as config_file:
 | 
				
			||||||
            parser.read_file(config_file)
 | 
					            config_string = _resolve_config_env_markers(config_file.read())
 | 
				
			||||||
 | 
					        parser.read_string(config_string)
 | 
				
			||||||
        return parser
 | 
					        return parser
 | 
				
			||||||
else:
 | 
					else:
 | 
				
			||||||
    import StringIO
 | 
					    import StringIO
 | 
				
			||||||
| 
						 | 
					@ -109,27 +106,16 @@ else:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if yaml:
 | 
					if yaml:
 | 
				
			||||||
    # TODO: use SafeLoader without env interpolation by default in version 5.*
 | 
					 | 
				
			||||||
    def yaml_env_marker_constructor(_, node):
 | 
					 | 
				
			||||||
        """"Replace environment variable marker with its value."""
 | 
					 | 
				
			||||||
        return _resolve_config_env_markers(node.value)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    yaml.add_implicit_resolver('!path', config_env_marker_pattern)
 | 
					 | 
				
			||||||
    yaml.add_constructor('!path', yaml_env_marker_constructor)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class YamlLoader(yaml.SafeLoader):
 | 
					    class YamlLoader(yaml.SafeLoader):
 | 
				
			||||||
        """Custom YAML loader.
 | 
					        """YAML loader.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Inherits ``yaml.SafeLoader`` and add environment variables interpolation.
 | 
					        This loader mimics ``yaml.SafeLoader``.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					 | 
				
			||||||
    YamlLoader.add_implicit_resolver('!path', config_env_marker_pattern, None)
 | 
					 | 
				
			||||||
    YamlLoader.add_constructor('!path', yaml_env_marker_constructor)
 | 
					 | 
				
			||||||
else:
 | 
					else:
 | 
				
			||||||
    class YamlLoader:
 | 
					    class YamlLoader:
 | 
				
			||||||
        """Custom YAML loader.
 | 
					        """YAML loader.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Inherits ``yaml.SafeLoader`` and add environment variables interpolation.
 | 
					        This loader mimics ``yaml.SafeLoader``.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1567,7 +1553,7 @@ cdef class ConfigurationOption(Provider):
 | 
				
			||||||
            current_config = {}
 | 
					            current_config = {}
 | 
				
			||||||
        self.override(merge_dicts(current_config, config))
 | 
					        self.override(merge_dicts(current_config, config))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def from_yaml(self, filepath, required=UNDEFINED, loader=None):
 | 
					    def from_yaml(self, filepath, required=UNDEFINED, loader=None, envs_required=False):
 | 
				
			||||||
        """Load configuration from the yaml file.
 | 
					        """Load configuration from the yaml file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Loaded configuration is merged recursively over existing configuration.
 | 
					        Loaded configuration is merged recursively over existing configuration.
 | 
				
			||||||
| 
						 | 
					@ -1581,6 +1567,9 @@ cdef class ConfigurationOption(Provider):
 | 
				
			||||||
        :param loader: YAML loader, :py:class:`YamlLoader` is used if not specified.
 | 
					        :param loader: YAML loader, :py:class:`YamlLoader` is used if not specified.
 | 
				
			||||||
        :type loader: ``yaml.Loader``
 | 
					        :type loader: ``yaml.Loader``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param envs_required: When True, raises an error on undefined environment variable.
 | 
				
			||||||
 | 
					        :type envs_required: bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :rtype: None
 | 
					        :rtype: None
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if yaml is None:
 | 
					        if yaml is None:
 | 
				
			||||||
| 
						 | 
					@ -1595,7 +1584,7 @@ cdef class ConfigurationOption(Provider):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            with open(filepath) as opened_file:
 | 
					            with open(filepath) as opened_file:
 | 
				
			||||||
                config = yaml.load(opened_file, loader)
 | 
					                config_content = opened_file.read()
 | 
				
			||||||
        except IOError as exception:
 | 
					        except IOError as exception:
 | 
				
			||||||
            if required is not False \
 | 
					            if required is not False \
 | 
				
			||||||
                    and (self._is_strict_mode_enabled() or required is True) \
 | 
					                    and (self._is_strict_mode_enabled() or required is True) \
 | 
				
			||||||
| 
						 | 
					@ -1604,6 +1593,12 @@ cdef class ConfigurationOption(Provider):
 | 
				
			||||||
                raise
 | 
					                raise
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        config_content = _resolve_config_env_markers(
 | 
				
			||||||
 | 
					            config_content,
 | 
				
			||||||
 | 
					            envs_required=envs_required,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        config = yaml.load(config_content, loader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        current_config = self.__call__()
 | 
					        current_config = self.__call__()
 | 
				
			||||||
        if not current_config:
 | 
					        if not current_config:
 | 
				
			||||||
            current_config = {}
 | 
					            current_config = {}
 | 
				
			||||||
| 
						 | 
					@ -1988,7 +1983,7 @@ cdef class Configuration(Object):
 | 
				
			||||||
            current_config = {}
 | 
					            current_config = {}
 | 
				
			||||||
        self.override(merge_dicts(current_config, config))
 | 
					        self.override(merge_dicts(current_config, config))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def from_yaml(self, filepath, required=UNDEFINED, loader=None):
 | 
					    def from_yaml(self, filepath, required=UNDEFINED, loader=None, envs_required=False):
 | 
				
			||||||
        """Load configuration from the yaml file.
 | 
					        """Load configuration from the yaml file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Loaded configuration is merged recursively over existing configuration.
 | 
					        Loaded configuration is merged recursively over existing configuration.
 | 
				
			||||||
| 
						 | 
					@ -2002,6 +1997,9 @@ cdef class Configuration(Object):
 | 
				
			||||||
        :param loader: YAML loader, :py:class:`YamlLoader` is used if not specified.
 | 
					        :param loader: YAML loader, :py:class:`YamlLoader` is used if not specified.
 | 
				
			||||||
        :type loader: ``yaml.Loader``
 | 
					        :type loader: ``yaml.Loader``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param envs_required: When True, raises an error on undefined environment variable.
 | 
				
			||||||
 | 
					        :type envs_required: bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :rtype: None
 | 
					        :rtype: None
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if yaml is None:
 | 
					        if yaml is None:
 | 
				
			||||||
| 
						 | 
					@ -2016,7 +2014,7 @@ cdef class Configuration(Object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            with open(filepath) as opened_file:
 | 
					            with open(filepath) as opened_file:
 | 
				
			||||||
                config = yaml.load(opened_file, loader)
 | 
					                config_content = opened_file.read()
 | 
				
			||||||
        except IOError as exception:
 | 
					        except IOError as exception:
 | 
				
			||||||
            if required is not False \
 | 
					            if required is not False \
 | 
				
			||||||
                    and (self._is_strict_mode_enabled() or required is True) \
 | 
					                    and (self._is_strict_mode_enabled() or required is True) \
 | 
				
			||||||
| 
						 | 
					@ -2025,6 +2023,12 @@ cdef class Configuration(Object):
 | 
				
			||||||
                raise
 | 
					                raise
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        config_content = _resolve_config_env_markers(
 | 
				
			||||||
 | 
					            config_content,
 | 
				
			||||||
 | 
					            envs_required=envs_required,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        config = yaml.load(config_content, loader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        current_config = self.__call__()
 | 
					        current_config = self.__call__()
 | 
				
			||||||
        if not current_config:
 | 
					        if not current_config:
 | 
				
			||||||
            current_config = {}
 | 
					            current_config = {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -639,20 +639,20 @@ class ConfigFromIniWithEnvInterpolationTests(unittest.TestCase):
 | 
				
			||||||
            self.config(),
 | 
					            self.config(),
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                'section1': {
 | 
					                'section1': {
 | 
				
			||||||
                    'value1': '${CONFIG_TEST_ENV}',
 | 
					                    'value1': '',
 | 
				
			||||||
                    'value2': '${CONFIG_TEST_PATH}/path',
 | 
					                    'value2': '/path',
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(
 | 
					        self.assertEqual(
 | 
				
			||||||
            self.config.section1(),
 | 
					            self.config.section1(),
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                'value1': '${CONFIG_TEST_ENV}',
 | 
					                'value1': '',
 | 
				
			||||||
                'value2': '${CONFIG_TEST_PATH}/path',
 | 
					                'value2': '/path',
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(self.config.section1.value1(), '${CONFIG_TEST_ENV}')
 | 
					        self.assertEqual(self.config.section1.value1(), '')
 | 
				
			||||||
        self.assertEqual(self.config.section1.value2(), '${CONFIG_TEST_PATH}/path')
 | 
					        self.assertEqual(self.config.section1.value2(), '/path')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_default_values(self):
 | 
					    def test_default_values(self):
 | 
				
			||||||
        os.environ['DEFINED'] = 'defined'
 | 
					        os.environ['DEFINED'] = 'defined'
 | 
				
			||||||
| 
						 | 
					@ -673,7 +673,7 @@ class ConfigFromIniWithEnvInterpolationTests(unittest.TestCase):
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                'defined_with_default': 'defined',
 | 
					                'defined_with_default': 'defined',
 | 
				
			||||||
                'undefined_with_default': 'default',
 | 
					                'undefined_with_default': 'default',
 | 
				
			||||||
                'complex': 'defined/path/defined/${UNDEFINED}/default',
 | 
					                'complex': 'defined/path/defined//default',
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -890,20 +890,20 @@ class ConfigFromYamlWithEnvInterpolationTests(unittest.TestCase):
 | 
				
			||||||
            self.config(),
 | 
					            self.config(),
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                'section1': {
 | 
					                'section1': {
 | 
				
			||||||
                    'value1': '${CONFIG_TEST_ENV}',
 | 
					                    'value1': None,
 | 
				
			||||||
                    'value2': '${CONFIG_TEST_PATH}/path',
 | 
					                    'value2': '/path',
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(
 | 
					        self.assertEqual(
 | 
				
			||||||
            self.config.section1(),
 | 
					            self.config.section1(),
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                'value1': '${CONFIG_TEST_ENV}',
 | 
					                'value1': None,
 | 
				
			||||||
                'value2': '${CONFIG_TEST_PATH}/path',
 | 
					                'value2': '/path',
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(self.config.section1.value1(), '${CONFIG_TEST_ENV}')
 | 
					        self.assertIsNone(self.config.section1.value1())
 | 
				
			||||||
        self.assertEqual(self.config.section1.value2(), '${CONFIG_TEST_PATH}/path')
 | 
					        self.assertEqual(self.config.section1.value2(), '/path')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
 | 
					    @unittest.skipIf(sys.version_info[:2] == (3, 4), 'PyYAML does not support Python 3.4')
 | 
				
			||||||
    def test_default_values(self):
 | 
					    def test_default_values(self):
 | 
				
			||||||
| 
						 | 
					@ -925,7 +925,7 @@ class ConfigFromYamlWithEnvInterpolationTests(unittest.TestCase):
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                'defined_with_default': 'defined',
 | 
					                'defined_with_default': 'defined',
 | 
				
			||||||
                'undefined_with_default': 'default',
 | 
					                'undefined_with_default': 'default',
 | 
				
			||||||
                'complex': 'defined/path/defined/${UNDEFINED}/default',
 | 
					                'complex': 'defined/path/defined//default',
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user