Skip to content

Commit b1f4112

Browse files
Impoved config merge, bumped to v0.1.3
1 parent adee254 commit b1f4112

File tree

3 files changed

+48
-15
lines changed

3 files changed

+48
-15
lines changed

README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ pip install pyya
2525

2626
## Usage
2727

28-
Create YAML configuration files for your project:
28+
### Example
2929

30+
Create YAML configuration files for your project:
3031

3132
```yaml
3233
# default.config.yaml - this file usually goes to version control system
@@ -54,6 +55,7 @@ from pyya import init_config
5455
config = init_config(
5556
'config.yaml', 'default.config.yaml',
5657
convert_keys_to_snake_case = False,
58+
add_underscore_prefix_to_keywords = False
5759
raise_error_non_identifiers = False)
5860
print(json.dumps(config.database))
5961

@@ -66,9 +68,21 @@ As you can see, `pyya` automatically merges default config file with production
6668

6769
Under the hood `pyya` uses [PyYAML](https://pypi.org/project/PyYAML/) to parse YAML files and [munch](https://pypi.org/project/munch/) library to create attribute-stylish dictionaries.
6870

69-
`pyya` automatically adds underscore prefix to Python keywords and can be configured to convert `camelCase` or `PascalCase` keys to `snake_case`.
71+
and can be configured to .
72+
73+
### Flags
7074

71-
If `raise_error_non_identifiers=True`, `pyya` will raise error if section name is not valid Python identifier.
75+
```python
76+
convert_keys_to_snake_case=True # `pyya` converts `camelCase` or `PascalCase` keys to `snake_case`
77+
```
78+
79+
```python
80+
add_underscore_prefix_to_keywords=True # `pyya` adds underscore prefix to keys that are Python keywords
81+
```
82+
83+
```python
84+
raise_error_non_identifiers=True # `pyya` raises error if key name is not valid Python identifier
85+
```
7286

7387
## Contributing
7488

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.2"
3+
version = "0.1.3"
44
authors = [
55
{ name="shadowy-pycoder", email="[email protected]" },
66
]

pyya/__init__.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,34 +23,49 @@ def init_config(
2323
default_config: Union[str, Path] = 'default.config.yaml',
2424
*,
2525
convert_keys_to_snake_case: bool = False,
26+
add_underscore_prefix_to_keywords: bool = False,
2627
raise_error_non_identifiers: bool = False,
2728
) -> Munch:
2829
def _merge_configs(_raw_data: ConfigType, _default_raw_data: ConfigType) -> None:
2930
for section, entry in _default_raw_data.items():
3031
if section not in _raw_data or _raw_data[section] is None:
31-
section = _sanitize_section(section)
32-
_raw_data[section] = entry
33-
logger.warning(f'section `{section}` with value `{entry}` taken from {default_config}')
32+
f_section = _sanitize_section(section)
33+
if f_section not in _raw_data:
34+
_raw_data[f_section] = entry
35+
logger.warning(f'section `{f_section}` with value `{entry}` taken from {default_config}')
36+
else:
37+
logger.debug(f'section `{f_section}` already exists in {config}, skipping')
3438
elif isinstance(entry, dict):
35-
section = _sanitize_section(section)
3639
_merge_configs(_raw_data[section], entry)
40+
# TODO: add support for merging lists
41+
else:
42+
f_section = _sanitize_section(section)
43+
if f_section not in _raw_data:
44+
_raw_data[f_section] = _raw_data.pop(section, None)
45+
else:
46+
logger.debug(f'section `{f_section}` already exists in {config}, skipping')
3747

3848
def _sanitize_section(section: str) -> str:
3949
if convert_keys_to_snake_case:
40-
logger.warning(f'converting section `{section}` to snake case')
50+
logger.debug(f'converting section `{section}` to snake case')
4151
section = to_snake(section)
4252
if raise_error_non_identifiers and not section.isidentifier():
4353
err_msg = f'section `{section}` is not a valid identifier, aborting'
4454
logger.error(err_msg)
4555
raise PyyaError(err_msg)
46-
if keyword.iskeyword(section):
56+
if add_underscore_prefix_to_keywords and keyword.iskeyword(section):
4757
logger.warning(f'section `{section}` is a keyword, renaming to `_{section}`')
4858
section = f'_{section}'
4959
return section
5060

5161
try:
52-
with open(Path(default_config)) as fstream:
53-
_default_raw_data: Optional[ConfigType] = yaml.safe_load(fstream)
62+
try:
63+
with open(Path(default_config)) as fstream:
64+
_default_raw_data: Optional[ConfigType] = yaml.safe_load(fstream)
65+
except yaml.YAMLError as e:
66+
err_msg = f'{default_config} file is corrupted: {e}'
67+
logger.error(err_msg)
68+
raise PyyaError(err_msg) from None
5469
if _default_raw_data is None:
5570
raise FileNotFoundError()
5671
except FileNotFoundError as e:
@@ -59,13 +74,17 @@ def _sanitize_section(section: str) -> str:
5974
try:
6075
with open(Path(config)) as fstream:
6176
_raw_data: ConfigType = yaml.safe_load(fstream) or {}
77+
except yaml.YAMLError as e:
78+
err_msg = f'{config} file is corrupted: {e}'
79+
logger.error(err_msg)
80+
raise PyyaError(err_msg) from None
6281
except FileNotFoundError:
6382
logger.warning(f'{config} file not found, using {default_config}')
6483
_raw_data = {}
6584
_merge_configs(_raw_data, _default_raw_data)
6685
try:
6786
return munchify(_raw_data)
6887
except Exception as e:
69-
message = f'{default_config} file is corrupted: {repr(e)}'
70-
logger.error(message)
71-
raise PyyaError(message) from None
88+
err_msg = f'Failed parsing config file: {e}'
89+
logger.error(err_msg)
90+
raise PyyaError(err_msg) from None

0 commit comments

Comments
 (0)