Skip to content

Commit 475f0e6

Browse files
Added replacing dashes with underscores in section names and keys, added basic tests
1 parent bb08ec0 commit 475f0e6

File tree

16 files changed

+507
-14
lines changed

16 files changed

+507
-14
lines changed

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@ __pycache__/
55
.mypy_cache
66
.coverage
77
.env
8-
config.yaml
98
.test.env
10-
*.log
9+
*.log

LICENSE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2024 shadowy-pycoder
3+
Copyright (c) 2025 shadowy-pycoder
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
21+
SOFTWARE.

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ config = init_config(
7373
convert_keys_to_snake_case = False,
7474
add_underscore_prefix_to_keywords = False,
7575
raise_error_non_identifiers = False,
76+
replace_dashes_with_underscores = False,
7677
merge_configs = True,
7778
sections_ignored_on_merge = ['redis'], # do not include redis in your config
7879
validate_data_types = True,
@@ -113,6 +114,11 @@ add_underscore_prefix_to_keywords=False
113114
raise_error_non_identifiers=False
114115
```
115116

117+
```python
118+
# replace dashes with underscores in section names and keys
119+
replace_dashes_with_underscores=False
120+
```
121+
116122
```python
117123
# merge default and production configuration files
118124
# setting to `False` disables below flags and makes default config optional

pyproject.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pyya"
3-
version = "0.1.11"
3+
version = "0.1.12"
44
description = "Convert YAML/TOML configuration files to Python objects"
55
readme = "README.md"
66
requires-python = ">=3.8"
@@ -38,6 +38,11 @@ Repository = "https://github.com/shadowy-pycoder/pyya"
3838
[project.scripts]
3939
pyya = "pyya.__main__:main"
4040

41+
[dependency-groups]
42+
dev = [
43+
"pytest>=8.3.5",
44+
]
45+
4146
[tool.mypy]
4247
python_version = "3.8"
4348
cache_dir = ".mypy_cache/strict"
@@ -67,9 +72,6 @@ filterwarnings = [
6772
"ignore::ResourceWarning",
6873
]
6974
pythonpath = "."
70-
asyncio_mode = "auto"
71-
env_files = ".test.env"
72-
env_override_existing_values = true
7375

7476
[tool.ruff]
7577
exclude = [

pyya.egg-info/PKG-INFO

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Metadata-Version: 2.1
22
Name: pyya
3-
Version: 0.1.11
3+
Version: 0.1.12
44
Summary: Convert YAML/TOML configuration files to Python objects
55
Author-email: shadowy-pycoder <[email protected]>
66
Project-URL: Homepage, https://github.com/shadowy-pycoder/pyya
@@ -102,6 +102,7 @@ config = init_config(
102102
convert_keys_to_snake_case = False,
103103
add_underscore_prefix_to_keywords = False,
104104
raise_error_non_identifiers = False,
105+
replace_dashes_with_underscores = False,
105106
merge_configs = True,
106107
sections_ignored_on_merge = ['redis'], # do not include redis in your config
107108
validate_data_types = True,
@@ -142,6 +143,11 @@ add_underscore_prefix_to_keywords=False
142143
raise_error_non_identifiers=False
143144
```
144145

146+
```python
147+
# replace dashes with underscores in section names and keys
148+
replace_dashes_with_underscores=False
149+
```
150+
145151
```python
146152
# merge default and production configuration files
147153
# setting to `False` disables below flags and makes default config optional

pyya.egg-info/SOURCES.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ pyya.egg-info/SOURCES.txt
99
pyya.egg-info/dependency_links.txt
1010
pyya.egg-info/entry_points.txt
1111
pyya.egg-info/requires.txt
12-
pyya.egg-info/top_level.txt
12+
pyya.egg-info/top_level.txt
13+
tests/test_toml.py
14+
tests/test_yaml.py

pyya/__init__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def init_config(
3333
convert_keys_to_snake_case: bool = False,
3434
add_underscore_prefix_to_keywords: bool = False,
3535
raise_error_non_identifiers: bool = False,
36+
replace_dashes_with_underscores: bool = False,
3637
merge_configs: bool = True,
3738
sections_ignored_on_merge: Optional[List[str]] = None,
3839
validate_data_types: bool = True,
@@ -49,6 +50,7 @@ def init_config(
4950
convert_keys_to_snake_case: convert config section names to snake case
5051
add_underscore_prefix_to_keywords: add underscore prefix to Python keywords
5152
raise_error_non_identifiers: raise error if config section name is not a valid identifier
53+
replace_dashes_with_underscores: replace dashes with underscores in section names and keys
5254
merge_configs: merge default config with production config (setting to `False` disables flags below)
5355
sections_ignored_on_merge: list of sections to ignore when merging configs
5456
validate_data_types: raise error if data types in production config are not the same as default
@@ -94,8 +96,10 @@ def _sanitize_section(section: str) -> str:
9496
err_msg = f'section `{section}` is not a valid identifier, aborting'
9597
logger.error(err_msg)
9698
raise PyyaError(err_msg)
99+
if replace_dashes_with_underscores:
100+
section = section.replace('-', '_')
97101
if add_underscore_prefix_to_keywords and keyword.iskeyword(section):
98-
logger.info(f'section `{section}` is a keyword, renaming to `_{section}`')
102+
logger.debug(f'section `{section}` is a keyword, renaming to `_{section}`')
99103
section = f'_{section}'
100104
return section
101105

@@ -163,7 +167,9 @@ def _model_and_stub_from_dict(
163167
fields: Dict[Any, Any] = {}
164168
if path is None:
165169
path = []
166-
class_name = ''.join(part.capitalize() if i > 0 else part for i, part in enumerate(path + [name]))
170+
class_name = ''.join(part.capitalize() if i > 0 else part for i, part in enumerate(path + [name])).replace(
171+
'-', '_'
172+
)
167173
stub_lines = [f'class {class_name}(Dict[str, Any]):']
168174
nested_stubs = []
169175
py_type: Any
@@ -198,7 +204,7 @@ def _model_and_stub_from_dict(
198204
if not keyword.iskeyword(section) and section.isidentifier():
199205
stub_lines.append(f' {section}: {py_type.__name__}')
200206
fields[section] = (py_type, entry)
201-
stub_code = '\n\n'.join(nested_stubs + ['\n'.join(stub_lines)]).replace('-', '_')
207+
stub_code = '\n\n'.join(nested_stubs + ['\n'.join(stub_lines)])
202208
return create_model(name, **fields, __base__=ExtraBase), stub_code
203209

204210
def _get_default_raw_data() -> ConfigType:

pyya/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def main() -> None:
1414
parser.add_argument('--var-name', default='config', help='variable name to refer to config object')
1515
parser.add_argument('--to-snake', action='store_true', help='convert config section names to snake case')
1616
parser.add_argument('--add-prefix', action='store_true', help='add underscore prefix to Python keywords')
17+
parser.add_argument('--replace-dashes', action='store_true', help='replace dashes with underscores in names')
1718
parser.add_argument('--debug', action='store_true', help='print debug messages')
1819
args = parser.parse_args()
1920
logger.setLevel(logging.INFO)
@@ -25,6 +26,7 @@ def main() -> None:
2526
args.input,
2627
convert_keys_to_snake_case=args.to_snake,
2728
add_underscore_prefix_to_keywords=args.add_prefix,
29+
replace_dashes_with_underscores=args.replace_dashes,
2830
_generate_stub=True,
2931
_stub_variable_name=args.var_name,
3032
)

tests/__init__.py

Whitespace-only changes.

tests/test_toml.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
5+
import pyya
6+
7+
8+
config_path = Path(__file__).parent / 'testdata/config.toml'
9+
default_config_path = Path(__file__).parent / 'testdata/default.config.toml'
10+
11+
12+
def test_default_setup() -> None:
13+
config = pyya.init_config(config=config_path, default_config=default_config_path, validate_data_types=False)
14+
assert config.app.name == 'myapp_changed'
15+
assert config.app.debug is True
16+
assert config.app.port == 9091
17+
assert config.database.password == 'secret_changed'
18+
assert len(config.database.replicas) == 1
19+
assert config.logging.level == 'debug'
20+
assert config.logging.rotate.enabled is False
21+
assert len(config.cache.servers) == 1
22+
23+
24+
def test_replace_dashes_with_undescores() -> None:
25+
config = pyya.init_config(
26+
config=config_path,
27+
default_config=default_config_path,
28+
replace_dashes_with_underscores=True,
29+
validate_data_types=False,
30+
)
31+
assert config.logging.rotate.keep_files == 3
32+
33+
34+
def test_sections_ignored_on_merge() -> None:
35+
with pytest.raises(AttributeError, match=r'environments'):
36+
config = pyya.init_config(
37+
config=config_path,
38+
default_config=default_config_path,
39+
sections_ignored_on_merge=['environments'],
40+
validate_data_types=False,
41+
)
42+
_ = config.environments
43+
44+
45+
def test_validate_data_types() -> None:
46+
with pytest.raises(pyya.PyyaError, match=r'cache.enabled'):
47+
_ = pyya.init_config(config=config_path, default_config=default_config_path)

0 commit comments

Comments
 (0)