Skip to content

Commit 7d46d0a

Browse files
updated to v0.1.7
1 parent a831348 commit 7d46d0a

File tree

4 files changed

+56
-14
lines changed

4 files changed

+56
-14
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ config = init_config(
7474
raise_error_non_identifiers = False,
7575
validate_data_types = True,
7676
allow_extra_sections = True,
77+
warn_extra_sections = True,
7778
)
7879
print(json.dumps(config))
7980
@@ -98,7 +99,7 @@ merge_configs=True
9899
```python
99100
# list of sections to ignore when merging configs
100101
# it is useful when you have examples in your default config but do not want to have in the main one
101-
sections_ignored_on_merge: Optional[List[str]] = None
102+
sections_ignored_on_merge=None
102103
```
103104

104105
```python
@@ -127,6 +128,11 @@ validate_data_types=True
127128
allow_extra_sections=True
128129
```
129130

131+
```python
132+
# warn about extra keys and values on the first level
133+
warn_extra_sections=True
134+
```
135+
130136
## Contributing
131137

132138
Are you a developer?

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pyya"
3-
version = "0.1.6"
3+
version = "0.1.7"
44
authors = [
55
{ name = "shadowy-pycoder", email = "shadowy-pycoder@example.com" },
66
]

pyya.egg-info/PKG-INFO

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Metadata-Version: 2.1
22
Name: pyya
3-
Version: 0.1.6
3+
Version: 0.1.7
44
Summary: Convert YAML configuration files to Python objects
55
Author-email: shadowy-pycoder <shadowy-pycoder@example.com>
66
Project-URL: Homepage, https://github.com/shadowy-pycoder/pyya
@@ -97,10 +97,11 @@ config = init_config(
9797
merge_configs = True,
9898
sections_ignored_on_merge = ['redis'], # do not include redis in your config
9999
convert_keys_to_snake_case = False,
100-
add_underscore_prefix_to_keywords = False
100+
add_underscore_prefix_to_keywords = False,
101101
raise_error_non_identifiers = False,
102102
validate_data_types = True,
103103
allow_extra_sections = True,
104+
warn_extra_sections = True,
104105
)
105106
print(json.dumps(config))
106107

@@ -125,7 +126,7 @@ merge_configs=True
125126
```python
126127
# list of sections to ignore when merging configs
127128
# it is useful when you have examples in your default config but do not want to have in the main one
128-
sections_ignored_on_merge: Optional[List[str]] = None
129+
sections_ignored_on_merge=None
129130
```
130131

131132
```python
@@ -154,6 +155,11 @@ validate_data_types=True
154155
allow_extra_sections=True
155156
```
156157

158+
```python
159+
# warn about extra keys and values on the first level
160+
warn_extra_sections=True
161+
```
162+
157163
## Contributing
158164

159165
Are you a developer?

pyya/__init__.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import keyword
22
import logging
3+
from copy import deepcopy
34
from pathlib import Path
5+
from pprint import pformat
46
from typing import Any, Dict, List, Literal, Optional, Type, Union
57

6-
import yaml
8+
import yaml as _yaml
79
from camel_converter import to_snake as _to_snake
810
from munch import Munch as _Munch
911
from munch import munchify as _munchify
@@ -34,6 +36,7 @@ def init_config(
3436
raise_error_non_identifiers: bool = False,
3537
validate_data_types: bool = True,
3638
allow_extra_sections: bool = True,
39+
warn_extra_sections: bool = True,
3740
) -> PyyaConfig:
3841
"""Initialize attribute-stylish configuration from YAML file.
3942
@@ -47,6 +50,7 @@ def init_config(
4750
raise_error_non_identifiers: raise error if config section name is not a valid identifier
4851
validate_data_types: raise error if data types in config are not the same as default (makes sense only if merge is enabled)
4952
allow_extra_sections: raise error if there are extra sections in config (may break if section name formatting is enabled)
53+
warn_extra_sections: warn about extra keys and values on the first level
5054
"""
5155

5256
def _merge_configs(
@@ -67,7 +71,7 @@ def _merge_configs(
6771
sections.append(f_section)
6872
if f_section not in _raw_data:
6973
_raw_data[f_section] = entry
70-
logger.info(f'section `{".".join(sections)}` with value `{entry}` taken from {default_config}')
74+
logger.debug(f'section `{".".join(sections)}` with value `{entry}` taken from {default_config}')
7175
else:
7276
logger.debug(f'section `{".".join(sections)}` already exists in {config}, skipping')
7377
elif isinstance(entry, Dict):
@@ -127,21 +131,21 @@ def _model_from_dict(name: str, data: Dict[str, Any], extra: bool) -> Type[BaseM
127131

128132
try:
129133
with open(Path(config)) as fstream:
130-
_raw_data: ConfigType = yaml.safe_load(fstream) or {}
131-
except yaml.YAMLError as e:
134+
_raw_data: ConfigType = _yaml.safe_load(fstream) or {}
135+
except _yaml.YAMLError as e:
132136
err_msg = f'{config} file is corrupted: {e}'
133137
logger.error(err_msg)
134138
raise PyyaError(err_msg) from None
135139
except FileNotFoundError:
136-
logger.info(f'{config} file not found, using {default_config}')
140+
logger.warning(f'{config} file not found, using {default_config}')
137141
_raw_data = {}
138142

139143
if merge_configs:
140144
try:
141145
try:
142146
with open(Path(default_config)) as fstream:
143-
_default_raw_data: Optional[ConfigType] = yaml.safe_load(fstream)
144-
except yaml.YAMLError as e:
147+
_default_raw_data: Optional[ConfigType] = _yaml.safe_load(fstream)
148+
except _yaml.YAMLError as e:
145149
err_msg = f'{default_config} file is corrupted: {e}'
146150
logger.error(err_msg)
147151
raise PyyaError(err_msg) from None
@@ -150,17 +154,43 @@ def _model_from_dict(name: str, data: Dict[str, Any], extra: bool) -> Type[BaseM
150154
except FileNotFoundError as e:
151155
logger.error(e)
152156
raise PyyaError(f'{default_config} file is missing or empty') from None
157+
# create copy for logging (only overwritten fields)
158+
_raw_data_copy = deepcopy(_raw_data)
153159
_merge_configs(_raw_data, _default_raw_data)
154160
if validate_data_types:
155161
ConfigModel = _model_from_dict('ConfigModel', _default_raw_data, allow_extra_sections)
156162
try:
157-
ConfigModel.model_validate(_raw_data)
163+
validated_raw_data = ConfigModel.model_validate(_raw_data)
164+
if validated_raw_data.model_extra:
165+
extra_sections = validated_raw_data.model_extra
166+
# remove formatted sections from extra
167+
for k in _default_raw_data:
168+
sk = _sanitize_section(k)
169+
if sk in extra_sections:
170+
extra_sections.pop(sk)
171+
if extra_sections and warn_extra_sections:
172+
logger.warning(
173+
f'\n\nThe following extra sections will be ignored:\n\n{pformat(extra_sections)}'
174+
)
175+
# remove extra sections from resulting config
176+
for k in extra_sections:
177+
_raw_data_copy.pop(k, None)
178+
_raw_data.pop(k, None)
158179
except Exception as e:
159180
err_msg = f'Failed validating config file: {e!r}'
160181
logger.error(err_msg)
161182
raise PyyaError(err_msg) from None
183+
# replace formatted sections in the copy of the config for logging
184+
for k in _raw_data_copy.copy():
185+
sk = _sanitize_section(k)
186+
if sk in _raw_data:
187+
_raw_data_copy[sk] = _raw_data[sk]
188+
_raw_data_copy.pop(k, None)
189+
logger.info(f'\n\nThe following sections were overwritten:\n\n{pformat(_raw_data_copy)}')
162190
try:
163-
return _munchify(_raw_data)
191+
raw_data = _munchify(_raw_data)
192+
logger.debug(f'\n\nResulting config:\n\n{pformat(raw_data)}')
193+
return raw_data
164194
except Exception as e:
165195
err_msg = f'Failed parsing config file: {e!r}'
166196
logger.error(err_msg)

0 commit comments

Comments
 (0)