Skip to content

Commit cd81a92

Browse files
committed
Merged main
2 parents 31bde9d + 43d9c08 commit cd81a92

File tree

13 files changed

+201
-73
lines changed

13 files changed

+201
-73
lines changed

.github/workflows/run_tests.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Tests
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
testing:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
python-version: [3.8, 3.9, "3.10", "3.11"]
11+
12+
steps:
13+
- uses: actions/checkout@v2
14+
15+
- name: Set up Python ${{ matrix.python-version }}
16+
uses: actions/setup-python@v2
17+
with:
18+
python-version: ${{ matrix.python-version }}
19+
20+
- name: Install and configure Poetry
21+
uses: snok/install-poetry@v1
22+
with:
23+
version: 1.5.1
24+
virtualenvs-create: true
25+
virtualenvs-in-project: true
26+
27+
- name: Install dependencies
28+
run: poetry install -v -E toml -E yaml -E azure -E aws -E gcp
29+
30+
- name: Run pytest
31+
run: poetry run pytest --cov=./ --cov-report=xml
32+
33+
- name: Coverage report
34+
uses: codecov/codecov-action@v1

.travis.yml

Lines changed: 0 additions & 22 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,42 @@ All notable changes to this project will be documented in this file.
66

77
### Added
88

9+
- Allow to pass a `ignore_missing_paths` parameter to each config method
10+
11+
## [0.9.0] - 2023-08-04
12+
13+
### Added
14+
915
- Added the `section_prefix` parameter that filters sections by prefix in INI/toml files
1016
- Allow the `ignore_missing_paths` parameter to be specified individually on Configuration Sets
1117

18+
### Fixed
19+
20+
- Errors when passing objects implementing `Mapping` instead of `dict`
21+
- Comparison to objects that are not a `Mapping`
22+
23+
### Changed
24+
25+
- Replaced TravisCI with GitHub Actions
26+
27+
1228
## [0.8.3] - 2021-10-11
1329

1430
### Fixed
1531

16-
- configurations from ini file won't be converted to lower case if `lowercase_keys = False`
32+
- Configurations from ini file won't be converted to lower case if `lowercase_keys = False`
1733

1834
## [0.8.2] - 2021-01-30
1935

2036
### Fixed
2137

22-
- the behavior of merging sets was incorrect since version 0.8.0
38+
- The behavior of merging sets was incorrect since version 0.8.0
2339

2440
## [0.8.0] - 2020-08-01
2541

2642
### Changed
2743

28-
- the behavior of the dict-like methods `keys`, `items`, and `values` now give only the first level configuration keys instead of the old behavior of returning all the nested keys. To achieve the same behavior as before, use the `dotter_iter` context manager:
44+
- The behavior of the dict-like methods `keys`, `items`, and `values` now give only the first level configuration keys instead of the old behavior of returning all the nested keys. To achieve the same behavior as before, use the `dotter_iter` context manager:
2945

3046
```python
3147
cfg.keys() # returns only the top level keys
@@ -36,7 +52,7 @@ with cfg.dotted_iter():
3652

3753
### Fixed
3854

39-
- configuration objects are now immutable
55+
- Configuration objects are now immutable
4056

4157
### Added
4258

@@ -48,7 +64,7 @@ with cfg.dotted_iter():
4864

4965
### Fixed
5066

51-
- installation with `poetry` because of changes to pytest-black
67+
- Installation with `poetry` because of changes to pytest-black
5268

5369
## [0.7.0] - 2020-05-06
5470

@@ -116,8 +132,9 @@ with cfg.dotted_iter():
116132

117133
- Initial version
118134

119-
[unreleased]: https://github.com/tr11/python-configuration/compare/0.8.3...HEAD
120-
[0.8.2]: https://github.com/tr11/python-configuration/compare/0.8.2...0.8.3
135+
[unreleased]: https://github.com/tr11/python-configuration/compare/0.9.0...HEAD
136+
[0.9.0]: https://github.com/tr11/python-configuration/compare/0.8.3...0.9.0
137+
[0.8.3]: https://github.com/tr11/python-configuration/compare/0.8.2...0.8.3
121138
[0.8.2]: https://github.com/tr11/python-configuration/compare/0.8.0...0.8.2
122139
[0.8.0]: https://github.com/tr11/python-configuration/compare/0.7.1...0.8.0
123140
[0.7.1]: https://github.com/tr11/python-configuration/compare/0.7.0...0.7.1

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
![python](https://img.shields.io/pypi/pyversions/python-configuration)
66
![wheel](https://img.shields.io/pypi/wheel/python-configuration)
77
![license](https://img.shields.io/pypi/l/python-configuration)
8-
[![build](https://img.shields.io/travis/tr11/python-configuration)](https://travis-ci.org/tr11/python-configuration)
9-
[![codecov](https://codecov.io/gh/tr11/python-configuration/branch/master/graph/badge.svg)](https://codecov.io/gh/tr11/python-configuration)
8+
[![tests](https://github.com/tr11/python-configuration/actions/workflows/run_tests.yml/badge.svg)](https://github.com/tr11/python-configuration/actions/workflows/run_tests.yml)
9+
[![codecov](https://codecov.io/gh/tr11/python-configuration/branch/main/graph/badge.svg?token=5zRYlGnDs7)](https://codecov.io/gh/tr11/python-configuration)
1010
[![Documentation Status](https://readthedocs.org/projects/python-configuration/badge/?version=latest)](https://python-configuration.readthedocs.io/en/latest/?badge=latest)
1111

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

config/__init__.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
from importlib.abc import InspectLoader
66
from types import ModuleType
7-
from typing import Any, Dict, Iterable, List, Optional, TextIO, Union, cast
7+
from typing import Any, Dict, Iterable, List, Mapping, Optional, TextIO, Union, cast
88

99
try:
1010
import yaml
@@ -60,7 +60,7 @@ def config(
6060
}
6161

6262
for config_ in configs:
63-
if isinstance(config_, dict):
63+
if isinstance(config_, Mapping):
6464
instances.append(config_from_dict(config_, **default_kwargs))
6565
continue
6666
elif isinstance(config_, str):
@@ -521,7 +521,34 @@ def config_from_ini(
521521
class DotEnvConfiguration(FileConfiguration):
522522
"""Configuration from a .env type file input."""
523523

524-
def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> None:
524+
def __init__(
525+
self,
526+
data: Union[str, TextIO],
527+
read_from_file: bool = False,
528+
prefix: str = "",
529+
separator: str = "__",
530+
*,
531+
lowercase_keys: bool = False,
532+
interpolate: InterpolateType = False,
533+
interpolate_type: InterpolateEnumType = InterpolateEnumType.STANDARD,
534+
ignore_missing_paths: bool = False,
535+
):
536+
self._prefix = prefix
537+
self._separator = separator
538+
super().__init__(
539+
data=data,
540+
read_from_file=read_from_file,
541+
lowercase_keys=lowercase_keys,
542+
interpolate=interpolate,
543+
interpolate_type=interpolate_type,
544+
ignore_missing_paths=ignore_missing_paths,
545+
)
546+
547+
def _reload(
548+
self,
549+
data: Union[str, TextIO],
550+
read_from_file: bool = False,
551+
) -> None:
525552
"""Reload the .env data."""
526553
if read_from_file:
527554
if isinstance(data, str):
@@ -534,12 +561,23 @@ def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> Non
534561
for x in data.splitlines()
535562
if x
536563
)
564+
565+
result = {
566+
k[len(self._prefix) :].replace(self._separator, ".").strip("."): v
567+
for k, v in result.items()
568+
if k.startswith(self._prefix)
569+
}
570+
571+
print(self._prefix, self._separator, result)
572+
537573
self._config = self._flatten_dict(result)
538574

539575

540576
def config_from_dotenv(
541577
data: Union[str, TextIO],
542578
read_from_file: bool = False,
579+
prefix: str = "",
580+
separator: str = "__",
543581
*,
544582
lowercase_keys: bool = False,
545583
interpolate: InterpolateType = False,
@@ -560,6 +598,8 @@ def config_from_dotenv(
560598
return DotEnvConfiguration(
561599
data,
562600
read_from_file,
601+
prefix=prefix,
602+
separator=separator,
563603
lowercase_keys=lowercase_keys,
564604
interpolate=interpolate,
565605
interpolate_type=interpolate_type,
@@ -678,7 +718,7 @@ def config_from_python(
678718

679719

680720
def config_from_dict(
681-
data: dict,
721+
data: Mapping,
682722
*,
683723
lowercase_keys: bool = False,
684724
interpolate: InterpolateType = False,
@@ -736,7 +776,7 @@ def _reload(
736776
loaded = yaml.load(open(data, "rt"), Loader=yaml.FullLoader)
737777
else:
738778
loaded = yaml.load(data, Loader=yaml.FullLoader)
739-
if not isinstance(loaded, dict):
779+
if not isinstance(loaded, Mapping):
740780
raise ValueError("Data should be a dictionary")
741781
self._config = self._flatten_dict(loaded)
742782

config/configuration.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ def __init__(
6969

7070
def __eq__(self, other): # type: ignore
7171
"""Equality operator."""
72+
if not isinstance(other, (Configuration, Mapping)):
73+
return False
7274
return self.as_dict() == Configuration(other).as_dict()
7375

7476
def _filter_dict(self, d: Dict[str, Any], prefix: str) -> Dict[str, Any]:
@@ -99,7 +101,7 @@ def _flatten_dict(self, d: Mapping[str, Any]) -> Dict[str, Any]:
99101
:param d: dict
100102
:return: a flattened dict
101103
"""
102-
nested = {k for k, v in d.items() if isinstance(v, (dict, Configuration))}
104+
nested = {k for k, v in d.items() if isinstance(v, (Mapping, Configuration))}
103105
if self._lowercase:
104106
result = {
105107
k.lower() + "." + ki: vi
@@ -109,7 +111,7 @@ def _flatten_dict(self, d: Mapping[str, Any]) -> Dict[str, Any]:
109111
result.update(
110112
(k.lower(), v)
111113
for k, v in d.items()
112-
if not isinstance(v, (dict, Configuration))
114+
if not isinstance(v, (Mapping, Configuration))
113115
)
114116
else:
115117
result = {
@@ -118,7 +120,9 @@ def _flatten_dict(self, d: Mapping[str, Any]) -> Dict[str, Any]:
118120
for ki, vi in self._flatten_dict(d[k]).items()
119121
}
120122
result.update(
121-
(k, v) for k, v in d.items() if not isinstance(v, (dict, Configuration))
123+
(k, v)
124+
for k, v in d.items()
125+
if not isinstance(v, (Mapping, Configuration))
122126
)
123127
return result
124128

@@ -153,11 +157,11 @@ def __getitem__(self, item: str) -> Union["Configuration", Any]: # noqa: D105
153157

154158
if v == {}:
155159
raise KeyError(item)
156-
if isinstance(v, dict):
160+
if isinstance(v, Mapping):
157161
return Configuration(v)
158162
elif self._interpolate is not False:
159163
d = self.as_dict()
160-
d.update(cast(Dict[str, str], self._interpolate))
164+
d.update(self._interpolate)
161165
return interpolate_object(item, v, [d], self._interpolate_type)
162166
else:
163167
return v
@@ -186,7 +190,7 @@ def as_attrdict(self) -> AttributeDict:
186190
"""Return the representation as an attribute dictionary."""
187191
return AttributeDict(
188192
{
189-
x: Configuration(v).as_attrdict() if isinstance(v, dict) else v
193+
x: Configuration(v).as_attrdict() if isinstance(v, Mapping) else v
190194
for x, v in self.items(levels=1)
191195
}
192196
)
@@ -307,9 +311,7 @@ def __iter__(self) -> Iterator[Tuple[str, Any]]: # noqa: D105
307311

308312
def __reversed__(self) -> Iterator[Tuple[str, Any]]: # noqa: D105
309313
if version_info < (3, 8):
310-
return OrderedDict( # type: ignore
311-
reversed(list(self.items()))
312-
) # pragma: no cover
314+
return OrderedDict(reversed(list(self.items()))) # type: ignore # pragma: no cover
313315
else:
314316
return reversed(dict(self.items())) # type: ignore
315317

@@ -404,7 +406,7 @@ def dotted_iter(self) -> Iterator["Configuration"]:
404406
self._default_levels = 1
405407

406408
def __repr__(self) -> str: # noqa: D105
407-
return "<Configuration: %s>" % hex(id(self))
409+
return "<%s: %s>" % (type(self).__name__, hex(id(self)))
408410

409411
def __str__(self) -> str: # noqa: D105
410412
return str({k: clean(k, v) for k, v in sorted(self.as_dict().items())})

config/configuration_set.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def _from_configs(self, attr: str, *args: Any, **kwargs: dict) -> Any:
7979
return Configuration(result)
8080
elif self._interpolate is not False:
8181
d = [d.as_dict() for d in self._configs]
82-
d[0].update(cast(Dict[str, str], self._interpolate))
82+
d[0].update(self._interpolate)
8383
return interpolate_object(args[0], values[0], d, self._interpolate_type)
8484
else:
8585
return values[0]

config/contrib/azure.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
from azure.identity import ClientSecretCredential
88
from azure.keyvault.secrets import SecretClient
99

10+
1011
from .. import Configuration, InterpolateType
1112

1213

1314
class Cache:
1415
"""Cache class."""
1516

16-
def __init__(self, value: str, ts: float): # noqa: D107
17+
def __init__(self, value: Optional[str], ts: float): # noqa: D107
1718
self.value = value
1819
self.ts = ts
1920

@@ -123,7 +124,7 @@ def values(
123124
return cast(
124125
ValuesView[str],
125126
(
126-
self._get_secret(k.name)
127+
self._get_secret(cast(str, k.name))
127128
for k in self._kv_client.list_properties_of_secrets()
128129
),
129130
)
@@ -136,7 +137,7 @@ def items(
136137
return cast(
137138
ItemsView[str, Any],
138139
(
139-
(k.name, self._get_secret(k.name))
140+
(k.name, self._get_secret(cast(str, k.name)))
140141
for k in self._kv_client.list_properties_of_secrets()
141142
),
142143
)

config/contrib/gcp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def _get_secret(self, key: str) -> Optional[str]:
7373
request={"name": path}
7474
).payload.data.decode()
7575
self._cache[key] = Cache(value=secret, ts=now)
76-
return cast(str, secret)
76+
return secret
7777
except NotFound:
7878
if key in self._cache:
7979
del self._cache[key]

0 commit comments

Comments
 (0)