Skip to content

Commit

Permalink
Minor improvements to dependency reporting (#88)
Browse files Browse the repository at this point in the history
* More verbose dependency reporting for optional classes

* Minor improvements to README to clarify features/dependencies
  • Loading branch information
DeadlyFirex authored Jan 8, 2024
1 parent 5ee55a6 commit ffbd890
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 111 deletions.
35 changes: 20 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,27 @@
[![codecov](https://codecov.io/gh/tr11/python-configuration/branch/main/graph/badge.svg?token=5zRYlGnDs7)](https://codecov.io/gh/tr11/python-configuration)
[![Documentation Status](https://readthedocs.org/projects/python-configuration/badge/?version=latest)](https://python-configuration.readthedocs.io/en/latest/?badge=latest)

This library is intended as a helper mechanism to load configuration files hierarchically. Supported format types are:
This library is intended as a helper mechanism to load configuration files hierarchically.

* Python files
* Dictionaries
* Environment variables
* Filesystem paths
* JSON files
* INI files
* dotenv type files
## Supported Formats

and optionally
The `python-configuration` library supports the following configuration formats and sources:

- Python files: ...
- Dictionaries: ...
- Environment variables: ...
- Filesystem paths: ...
- JSON files: ...
- INI files: ...
- dotenv type files: ...
- Optional support for:
- YAML files: requires `yaml`
- TOML files: requires `toml`
- Azure Key Vault credentials: ...
- AWS Secrets Manager credentials: ...
- GCP Secret Manager credentials: ...
- Hashicorp Vault credentials: ...

* YAML files
* TOML files
* Azure Key Vault credentials
* AWS Secrets Manager credentials
* GCP Secret Manager credentials
* Hashicorp Vault credentials

## Installing

Expand All @@ -42,6 +45,8 @@ To include the optional TOML and/or YAML loaders, install the optional dependenc
pip install python-configuration[toml,yaml]
```

Without the optional dependencies, the TOML and YAML loaders will not be available,
and attempting to use them will raise an exception.
## Getting started

`python-configuration` converts the various config types into dictionaries with dotted-based keys. For example, given this JSON configuration
Expand Down
214 changes: 118 additions & 96 deletions config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,101 +763,77 @@ def create_path_from_config(
return cfg


if yaml is not None: # pragma: no branch
class YAMLConfiguration(FileConfiguration):
"""Configuration from a YAML input."""

class YAMLConfiguration(FileConfiguration):
"""Configuration from a YAML input."""

def _reload(
self, data: Union[str, TextIO], read_from_file: bool = False
) -> None:
"""Reload the YAML data."""
if read_from_file and isinstance(data, str):
loaded = yaml.load(open(data, "rt"), Loader=yaml.FullLoader)
else:
loaded = yaml.load(data, Loader=yaml.FullLoader)
if not isinstance(loaded, Mapping):
raise ValueError("Data should be a dictionary")
self._config = self._flatten_dict(loaded)

def config_from_yaml(
data: Union[str, TextIO],
read_from_file: bool = False,
*,
lowercase_keys: bool = False,
interpolate: InterpolateType = False,
interpolate_type: InterpolateEnumType = InterpolateEnumType.STANDARD,
ignore_missing_paths: bool = False,
) -> Configuration:
"""
Return a Configuration instance from YAML files.
:param data: string or file
:param read_from_file: whether `data` is a file or a YAML formatted string
:param lowercase_keys: whether to convert every key to lower case.
:param interpolate: whether to apply string interpolation when looking for items
:param ignore_missing_paths: if true it will not throw on missing paths
:return: a Configuration instance
"""
return YAMLConfiguration(
data,
read_from_file,
lowercase_keys=lowercase_keys,
interpolate=interpolate,
interpolate_type=interpolate_type,
ignore_missing_paths=ignore_missing_paths,
)


if toml is not None: # pragma: no branch

class TOMLConfiguration(FileConfiguration):
"""Configuration from a TOML input."""

def __init__(
def __init__(
self,
data: Union[str, TextIO],
read_from_file: bool = False,
*,
section_prefix: str = "",
lowercase_keys: bool = False,
interpolate: InterpolateType = False,
interpolate_type: InterpolateEnumType = InterpolateEnumType.STANDARD,
ignore_missing_paths: bool = False,
):
self._section_prefix = section_prefix
super().__init__(
data=data,
read_from_file=read_from_file,
lowercase_keys=lowercase_keys,
interpolate=interpolate,
interpolate_type=interpolate_type,
ignore_missing_paths=ignore_missing_paths,
)
):
if yaml is None:
raise ImportError("Dependency <yaml> is not found, but required by this class.")
super().__init__(
data=data,
read_from_file=read_from_file,
lowercase_keys=lowercase_keys,
interpolate=interpolate,
interpolate_type=interpolate_type,
ignore_missing_paths=ignore_missing_paths,
)

def _reload(
self, data: Union[str, TextIO], read_from_file: bool = False
) -> None:
"""Reload the TOML data."""
if read_from_file:
if isinstance(data, str):
loaded = toml.load(open(data, "rt"))
else:
loaded = toml.load(data)
else:
data = cast(str, data)
loaded = toml.loads(data)
loaded = cast(dict, loaded)
def _reload(
self, data: Union[str, TextIO], read_from_file: bool = False
) -> None:
"""Reload the YAML data."""
if read_from_file and isinstance(data, str):
loaded = yaml.load(open(data, "rt"), Loader=yaml.FullLoader)
else:
loaded = yaml.load(data, Loader=yaml.FullLoader)
if not isinstance(loaded, Mapping):
raise ValueError("Data should be a dictionary")
self._config = self._flatten_dict(loaded)


def config_from_yaml(
data: Union[str, TextIO],
read_from_file: bool = False,
*,
lowercase_keys: bool = False,
interpolate: InterpolateType = False,
interpolate_type: InterpolateEnumType = InterpolateEnumType.STANDARD,
ignore_missing_paths: bool = False,
) -> Configuration:
"""
Return a Configuration instance from YAML files.
:param data: string or file
:param read_from_file: whether `data` is a file or a YAML formatted string
:param lowercase_keys: whether to convert every key to lower case.
:param interpolate: whether to apply string interpolation when looking for items
:param ignore_missing_paths: if true it will not throw on missing paths
:return: a Configuration instance
"""
return YAMLConfiguration(
data,
read_from_file,
lowercase_keys=lowercase_keys,
interpolate=interpolate,
interpolate_type=interpolate_type,
ignore_missing_paths=ignore_missing_paths,
)

result = {
k[len(self._section_prefix) :]: v
for k, v in self._flatten_dict(loaded).items()
if k.startswith(self._section_prefix)
}

self._config = result
class TOMLConfiguration(FileConfiguration):
"""Configuration from a TOML input."""

def config_from_toml(
def __init__(
self,
data: Union[str, TextIO],
read_from_file: bool = False,
*,
Expand All @@ -866,23 +842,69 @@ def config_from_toml(
interpolate: InterpolateType = False,
interpolate_type: InterpolateEnumType = InterpolateEnumType.STANDARD,
ignore_missing_paths: bool = False,
) -> Configuration:
"""
Return a Configuration instance from TOML files.
):
if toml is None:
raise ImportError("Dependency <toml> is not found, but required by this class.")

:param data: string or file
:param read_from_file: whether `data` is a file or a TOML formatted string
:param lowercase_keys: whether to convert every key to lower case.
:param interpolate: whether to apply string interpolation when looking for items
:param ignore_missing_paths: if true it will not throw on missing paths
:return: a Configuration instance
"""
return TOMLConfiguration(
data,
read_from_file,
section_prefix=section_prefix,
self._section_prefix = section_prefix
super().__init__(
data=data,
read_from_file=read_from_file,
lowercase_keys=lowercase_keys,
interpolate=interpolate,
interpolate_type=interpolate_type,
ignore_missing_paths=ignore_missing_paths,
)

def _reload(
self, data: Union[str, TextIO], read_from_file: bool = False
) -> None:
"""Reload the TOML data."""
if read_from_file:
if isinstance(data, str):
loaded = toml.load(open(data, "rt"))
else:
loaded = toml.load(data)
else:
data = cast(str, data)
loaded = toml.loads(data)
loaded = cast(dict, loaded)

result = {
k[len(self._section_prefix) :]: v
for k, v in self._flatten_dict(loaded).items()
if k.startswith(self._section_prefix)
}

self._config = result


def config_from_toml(
data: Union[str, TextIO],
read_from_file: bool = False,
*,
section_prefix: str = "",
lowercase_keys: bool = False,
interpolate: InterpolateType = False,
interpolate_type: InterpolateEnumType = InterpolateEnumType.STANDARD,
ignore_missing_paths: bool = False,
) -> Configuration:
"""
Return a Configuration instance from TOML files.
:param data: string or file
:param read_from_file: whether `data` is a file or a TOML formatted string
:param lowercase_keys: whether to convert every key to lower case.
:param interpolate: whether to apply string interpolation when looking for items
:param ignore_missing_paths: if true it will not throw on missing paths
:return: a Configuration instance
"""
return TOMLConfiguration(
data,
read_from_file,
section_prefix=section_prefix,
lowercase_keys=lowercase_keys,
interpolate=interpolate,
interpolate_type=interpolate_type,
ignore_missing_paths=ignore_missing_paths,
)

0 comments on commit ffbd890

Please sign in to comment.