Skip to content

Commit d4f825d

Browse files
Added ignoring for merging sections, bumped to v0.1.5
1 parent 5a800b7 commit d4f825d

File tree

5 files changed

+73
-23
lines changed

5 files changed

+73
-23
lines changed

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ database:
4444
port: 5432
4545
username: postgres
4646
password: postgres
47+
48+
redis:
49+
host: localhost
50+
port: 6379
4751
```
4852
4953
```yaml
@@ -53,7 +57,7 @@ database:
5357
password: password
5458
```
5559
56-
Import configuration files in your Python code with pyya:
60+
Import configuration files in your Python code with `pyya`:
5761

5862
```python
5963
import json
@@ -63,13 +67,14 @@ from pyya import init_config
6367
config = init_config(
6468
'config.yaml', 'default.config.yaml',
6569
merge_configs = True,
70+
sections_ignored_on_merge = ['redis'], # do not include redis on your config
6671
convert_keys_to_snake_case = False,
6772
add_underscore_prefix_to_keywords = False
6873
raise_error_non_identifiers = False)
69-
print(json.dumps(config.database))
74+
print(json.dumps(config))
7075
7176
# Output:
72-
# {"host": "localhost", "port": 5432, "username": "username", "password": "password"}
77+
# {database: {"host": "localhost", "port": 5432, "username": "username", "password": "password"}}
7378
7479
```
7580

@@ -86,6 +91,11 @@ Under the hood `pyya` uses [PyYAML](https://pypi.org/project/PyYAML/) to parse Y
8691
# `False` means "open config file and apply `ymal.safe_load` and `munchify` with no formatting"
8792
merge_configs=True
8893
```
94+
```python
95+
# list of sections to ignore when merging configs
96+
# it is useful when you have examples in your default config but do not want to have in the main one
97+
sections_ignored_on_merge: Optional[List[str]] = None
98+
```
8999
```python
90100
# convert `camelCase` or `PascalCase` keys to `snake_case`
91101
convert_keys_to_snake_case=True

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pyya"
3-
version = "0.1.4"
3+
version = "0.1.5"
44
authors = [
55
{ name="shadowy-pycoder", email="[email protected]" },
66
]
@@ -45,7 +45,7 @@ warn_no_return = true
4545
disallow_any_unimported = false
4646
warn_unused_configs = true
4747
disallow_any_generics = true
48-
disallow_subclassing_any = true
48+
disallow_subclassing_any = false
4949
disallow_untyped_calls = false
5050
disallow_untyped_defs = true
5151
disallow_incomplete_defs = true

pyya.egg-info/PKG-INFO

Lines changed: 22 additions & 4 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.4
3+
Version: 0.1.5
44
Summary: Convert YAML configuration files to Python objects
55
Author-email: shadowy-pycoder <[email protected]>
66
Project-URL: Homepage, https://github.com/shadowy-pycoder/pyya
@@ -28,6 +28,7 @@ Requires-Dist: types-pyyaml>=6.0.12.20240917
2828

2929
![PyPI - Downloads](https://img.shields.io/pypi/dd/pyya)
3030
[![ClickPy Dashboard](https://img.shields.io/badge/clickpy-dashboard-orange)](https://clickpy.clickhouse.com/dashboard/pyya)
31+
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/shadowy-pycoder/pyya/total)
3132
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyya)
3233
[![PyPI - Link](https://img.shields.io/badge/pypi-link-blue)](https://pypi.org/project/pyya/)
3334
![PyPI - Version](https://img.shields.io/pypi/v/pyya)
@@ -49,6 +50,13 @@ Requires-Dist: types-pyyaml>=6.0.12.20240917
4950
pip install pyya
5051
```
5152

53+
Or download a specific version from [Releases](https://github.com/shadowy-pycoder/pyya/releases) page and install it with:
54+
55+
```shell
56+
pip install /path/to/pyya-[version]-py3-none-any.whl
57+
```
58+
59+
5260
## Usage
5361

5462
### Example
@@ -62,6 +70,10 @@ database:
6270
port: 5432
6371
username: postgres
6472
password: postgres
73+
74+
redis:
75+
host: localhost
76+
port: 6379
6577
```
6678

6779
```yaml
@@ -71,7 +83,7 @@ database:
7183
password: password
7284
```
7385

74-
Import configuration files in your Python code with pyya:
86+
Import configuration files in your Python code with `pyya`:
7587

7688
```python
7789
import json
@@ -81,13 +93,14 @@ from pyya import init_config
8193
config = init_config(
8294
'config.yaml', 'default.config.yaml',
8395
merge_configs = True,
96+
sections_ignored_on_merge = ['redis'], # do not include redis on your config
8497
convert_keys_to_snake_case = False,
8598
add_underscore_prefix_to_keywords = False
8699
raise_error_non_identifiers = False)
87-
print(json.dumps(config.database))
100+
print(json.dumps(config))
88101

89102
# Output:
90-
# {"host": "localhost", "port": 5432, "username": "username", "password": "password"}
103+
# {database: {"host": "localhost", "port": 5432, "username": "username", "password": "password"}}
91104

92105
```
93106

@@ -104,6 +117,11 @@ Under the hood `pyya` uses [PyYAML](https://pypi.org/project/PyYAML/) to parse Y
104117
# `False` means "open config file and apply `ymal.safe_load` and `munchify` with no formatting"
105118
merge_configs=True
106119
```
120+
```python
121+
# list of sections to ignore when merging configs
122+
# it is useful when you have examples in your default config but do not want to have in the main one
123+
sections_ignored_on_merge: Optional[List[str]] = None
124+
```
107125
```python
108126
# convert `camelCase` or `PascalCase` keys to `snake_case`
109127
convert_keys_to_snake_case=True

pyya/__init__.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import keyword
22
import logging
33
from pathlib import Path
4-
from typing import Any, Dict, Optional, Union
4+
from typing import Any, Dict, List, Optional, Union
55

66
import yaml
7-
from camel_converter import to_snake
8-
from munch import Munch, munchify
7+
from camel_converter import to_snake as _to_snake
8+
from munch import Munch as _Munch
9+
from munch import munchify as _munchify
910

1011

1112
logging.basicConfig(format='%(asctime)-15s \t%(levelname)-8s \t%(name)-8s \t%(message)s')
@@ -15,6 +16,9 @@
1516
ConfigType = Dict[str, Any]
1617

1718

19+
class PyyaConfig(_Munch): ...
20+
21+
1822
class PyyaError(RuntimeError): ...
1923

2024

@@ -23,29 +27,37 @@ def init_config(
2327
default_config: Union[str, Path] = 'default.config.yaml',
2428
*,
2529
merge_configs: bool = True,
30+
sections_ignored_on_merge: Optional[List[str]] = None,
2631
convert_keys_to_snake_case: bool = False,
2732
add_underscore_prefix_to_keywords: bool = False,
2833
raise_error_non_identifiers: bool = False,
29-
) -> Munch:
30-
"""
31-
Initialize attribute-stylish configuration from YAML file.
34+
) -> PyyaConfig:
35+
"""Initialize attribute-stylish configuration from YAML file.
3236
3337
Args:
3438
config: path to config file
3539
default_config: path to default config file
3640
merge_configs: merge default config with config (setting to `False` disables other flags)
37-
convert_keys_to_snake_case: convert config keys to snake case
41+
sections_ignored_on_merge: list of sections to ignore when merging configs
42+
convert_keys_to_snake_case: convert config section names to snake case
3843
add_underscore_prefix_to_keywords: add underscore prefix to Python keywords
39-
raise_error_non_identifiers: raise error if config key is not a valid identifier
44+
raise_error_non_identifiers: raise error if config section name is not a valid identifier
4045
"""
4146

4247
def _merge_configs(_raw_data: ConfigType, _default_raw_data: ConfigType) -> None:
4348
for section, entry in _default_raw_data.items():
49+
if sections_ignored_on_merge:
50+
if section in sections_ignored_on_merge:
51+
logger.debug(f'section `{section}` ignored on merge')
52+
continue
53+
elif isinstance(entry, dict):
54+
# is it fine to proccess already poped dicts on recursion?
55+
entry = _pop_ignored_keys(entry)
4456
if section not in _raw_data or _raw_data[section] is None:
4557
f_section = _sanitize_section(section)
4658
if f_section not in _raw_data:
4759
_raw_data[f_section] = entry
48-
logger.warning(f'section `{f_section}` with value `{entry}` taken from {default_config}')
60+
logger.info(f'section `{f_section}` with value `{entry}` taken from {default_config}')
4961
else:
5062
logger.debug(f'section `{f_section}` already exists in {config}, skipping')
5163
elif isinstance(entry, dict):
@@ -61,16 +73,25 @@ def _merge_configs(_raw_data: ConfigType, _default_raw_data: ConfigType) -> None
6173
def _sanitize_section(section: str) -> str:
6274
if convert_keys_to_snake_case:
6375
logger.debug(f'converting section `{section}` to snake case')
64-
section = to_snake(section)
76+
section = _to_snake(section)
6577
if raise_error_non_identifiers and not section.isidentifier():
6678
err_msg = f'section `{section}` is not a valid identifier, aborting'
6779
logger.error(err_msg)
6880
raise PyyaError(err_msg)
6981
if add_underscore_prefix_to_keywords and keyword.iskeyword(section):
70-
logger.warning(f'section `{section}` is a keyword, renaming to `_{section}`')
82+
logger.info(f'section `{section}` is a keyword, renaming to `_{section}`')
7183
section = f'_{section}'
7284
return section
7385

86+
def _pop_ignored_keys(data: ConfigType) -> ConfigType:
87+
for key, entry in data.copy().items():
88+
if key in sections_ignored_on_merge:
89+
data.pop(key)
90+
logger.debug(f'section `{key}` ignored on merge')
91+
elif isinstance(entry, dict):
92+
_pop_ignored_keys(entry)
93+
return data
94+
7495
try:
7596
with open(Path(config)) as fstream:
7697
_raw_data: ConfigType = yaml.safe_load(fstream) or {}
@@ -79,7 +100,7 @@ def _sanitize_section(section: str) -> str:
79100
logger.error(err_msg)
80101
raise PyyaError(err_msg) from None
81102
except FileNotFoundError:
82-
logger.warning(f'{config} file not found, using {default_config}')
103+
logger.info(f'{config} file not found, using {default_config}')
83104
_raw_data = {}
84105

85106
if merge_configs:
@@ -98,7 +119,7 @@ def _sanitize_section(section: str) -> str:
98119
raise PyyaError(f'{default_config} file is missing or empty') from None
99120
_merge_configs(_raw_data, _default_raw_data)
100121
try:
101-
return munchify(_raw_data)
122+
return _munchify(_raw_data)
102123
except Exception as e:
103124
err_msg = f'Failed parsing config file: {e}'
104125
logger.error(err_msg)

uv.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)