Skip to content

Commit bb08ec0

Browse files
Added support for TOML file parsing
1 parent 41ca590 commit bb08ec0

File tree

7 files changed

+83
-29
lines changed

7 files changed

+83
-29
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# pyya - Simple tool that converts YAML configuration files to Python objects
1+
# pyya - Simple tool that converts YAML/TOML configuration files to Python objects
22

33
![PyPI - Downloads](https://img.shields.io/pypi/dd/pyya)
44
[![ClickPy Dashboard](https://img.shields.io/badge/clickpy-dashboard-orange)](https://clickpy.clickhouse.com/dashboard/pyya)
@@ -13,10 +13,10 @@
1313

1414
- Very `lightweight` and `simple` API (currently it contains only one function)
1515
- `Easy` to use
16-
- Based on popular and well-tested libraries (like `pydantic`, `camel-converter`, `PyYAML` and `munch`)
16+
- Based on popular and well-tested libraries (like `pydantic`, `camel-converter`, `PyYAML`, `toml` and `munch`)
1717
- Automatically `merge` default and production configuration files
1818
- Convert keys in configuration files to `snake_case`
19-
- YAML validation with `Pydantic` models
19+
- YAML/TOML validation with `Pydantic` models
2020
- Generate stub files for your dynamic configuration with `pyya` CLI tool.
2121

2222
## Installation
@@ -94,6 +94,8 @@ As you can see, `pyya` automatically merges default config file with production
9494

9595
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.
9696

97+
For TOML files the logic is the same except you should point `pyya` to correct TOML files (e.g. `config.toml`)
98+
9799
### Flags
98100

99101
```python

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "pyya"
3-
version = "0.1.10"
4-
description = "Convert YAML configuration files to Python objects"
3+
version = "0.1.11"
4+
description = "Convert YAML/TOML configuration files to Python objects"
55
readme = "README.md"
66
requires-python = ">=3.8"
77
authors = [
@@ -25,7 +25,9 @@ dependencies = [
2525
"munch>=4.0.0",
2626
"pydantic>=2.5.2",
2727
"pyyaml>=6.0.2",
28+
"toml>=0.10.2",
2829
"types-pyyaml>=6.0.12.20240917",
30+
"types-toml>=0.10.8.20240310",
2931
]
3032

3133
[project.urls]

pyya.egg-info/PKG-INFO

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Metadata-Version: 2.1
22
Name: pyya
3-
Version: 0.1.10
4-
Summary: Convert YAML configuration files to Python objects
3+
Version: 0.1.11
4+
Summary: Convert YAML/TOML configuration files to Python objects
55
Author-email: shadowy-pycoder <shadowy-pycoder@example.com>
66
Project-URL: Homepage, https://github.com/shadowy-pycoder/pyya
77
Project-URL: Issues, https://github.com/shadowy-pycoder/pyya/issues
@@ -23,9 +23,11 @@ Requires-Dist: camel-converter>=3.1.2
2323
Requires-Dist: munch>=4.0.0
2424
Requires-Dist: pydantic>=2.5.2
2525
Requires-Dist: pyyaml>=6.0.2
26+
Requires-Dist: toml>=0.10.2
2627
Requires-Dist: types-pyyaml>=6.0.12.20240917
28+
Requires-Dist: types-toml>=0.10.8.20240310
2729

28-
# pyya - Simple tool that converts YAML configuration files to Python objects
30+
# pyya - Simple tool that converts YAML/TOML configuration files to Python objects
2931

3032
![PyPI - Downloads](https://img.shields.io/pypi/dd/pyya)
3133
[![ClickPy Dashboard](https://img.shields.io/badge/clickpy-dashboard-orange)](https://clickpy.clickhouse.com/dashboard/pyya)
@@ -40,10 +42,10 @@ Requires-Dist: types-pyyaml>=6.0.12.20240917
4042

4143
- Very `lightweight` and `simple` API (currently it contains only one function)
4244
- `Easy` to use
43-
- Based on popular and well-tested libraries (like `pydantic`, `camel-converter`, `PyYAML` and `munch`)
45+
- Based on popular and well-tested libraries (like `pydantic`, `camel-converter`, `PyYAML`, `toml` and `munch`)
4446
- Automatically `merge` default and production configuration files
4547
- Convert keys in configuration files to `snake_case`
46-
- YAML validation with `Pydantic` models
48+
- YAML/TOML validation with `Pydantic` models
4749
- Generate stub files for your dynamic configuration with `pyya` CLI tool.
4850

4951
## Installation
@@ -121,6 +123,8 @@ As you can see, `pyya` automatically merges default config file with production
121123

122124
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.
123125

126+
For TOML files the logic is the same except you should point `pyya` to correct TOML files (e.g. `config.toml`)
127+
124128
### Flags
125129

126130
```python

pyya.egg-info/requires.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ camel-converter>=3.1.2
22
munch>=4.0.0
33
pydantic>=2.5.2
44
pyyaml>=6.0.2
5+
toml>=0.10.2
56
types-pyyaml>=6.0.12.20240917
7+
types-toml>=0.10.8.20240310

pyya/__init__.py

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
from pprint import pformat
66
from typing import Any, Dict, List, Optional, Tuple, Type, Union
77

8+
import toml as _toml
89
import yaml as _yaml
910
from camel_converter import to_snake as _to_snake
10-
from munch import Munch as _Munch
1111
from munch import munchify as _munchify
1212
from pydantic import BaseModel, ConfigDict, Field, create_model, model_validator
13+
from toml import decoder as _toml_decoder
1314

1415

1516
logging.basicConfig(format='%(asctime)-15s \t%(levelname)-8s \t%(name)-8s \t%(message)s')
@@ -19,7 +20,7 @@
1920
ConfigType = Dict[str, Any]
2021

2122

22-
class PyyaConfig(_Munch): ...
23+
PyyaConfig = Any
2324

2425

2526
class PyyaError(RuntimeError): ...
@@ -40,7 +41,7 @@ def init_config(
4041
_generate_stub: bool = False,
4142
_stub_variable_name: str = 'config',
4243
) -> PyyaConfig:
43-
"""Initialize attribute-stylish configuration from YAML file.
44+
"""Initialize attribute-stylish configuration from YAML/TOML file.
4445
4546
Args:
4647
config: path to production config file
@@ -100,7 +101,7 @@ def _sanitize_section(section: str) -> str:
100101

101102
def _pop_ignored_keys(data: ConfigType) -> ConfigType:
102103
for key, entry in data.copy().items():
103-
if key in sections_ignored_on_merge:
104+
if sections_ignored_on_merge and key in sections_ignored_on_merge:
104105
data.pop(key)
105106
logger.debug(f'section `{key}` ignored on merge')
106107
elif isinstance(entry, Dict):
@@ -203,17 +204,26 @@ def _model_and_stub_from_dict(
203204
def _get_default_raw_data() -> ConfigType:
204205
try:
205206
try:
207+
file_path = Path(default_config)
208+
if (ext := file_path.suffix) not in ('.yaml', '.yml', '.toml'):
209+
raise PyyaError(f'{default_config} file format is not supported') from None
210+
file_handler = _yaml.safe_load if ext in ('.yaml', '.yml') else _toml.load
206211
with open(Path(default_config)) as fstream:
207-
_default_raw_data: Optional[ConfigType] = _yaml.safe_load(fstream)
208-
except _yaml.YAMLError as e:
209-
err_msg = f'{default_config} file is corrupted: {e}'
210-
logger.error(err_msg)
211-
raise PyyaError(err_msg) from None
212+
_default_raw_data: Optional[ConfigType] = file_handler(fstream)
213+
except (_yaml.YAMLError, _toml_decoder.TomlDecodeError) as e:
214+
raise PyyaError(f'{default_config} file is corrupted: {e}') from None
212215
if _default_raw_data is None:
213216
raise FileNotFoundError()
214217
except FileNotFoundError as e:
215218
logger.error(e)
216219
raise PyyaError(f'{default_config} file is missing or empty') from None
220+
except PyyaError as e:
221+
logger.error(e)
222+
raise e from None
223+
except Exception as e:
224+
err_msg = f'{default_config} Unknown error: {e}'
225+
logger.error(err_msg)
226+
raise PyyaError(err_msg) from None
217227
_default_raw_data = _sanitize_keys(_default_raw_data)
218228
return _default_raw_data
219229

@@ -226,27 +236,36 @@ def _get_default_raw_data() -> ConfigType:
226236
_default_raw_data = _get_default_raw_data()
227237
_, stub = _model_and_stub_from_dict('Config', _default_raw_data)
228238
stub_full = (
229-
f'# {output_file} was autogenerated with pyya CLI tool, see `pyya -h`\nfrom typing import Any, Dict, List\n\n'
239+
f'# {output_file} was autogenerated from {default_config} with pyya CLI tool, see `pyya -h`\nfrom typing import Any, Dict, List\n\n'
230240
f'{stub}\n\n'
231241
'# for type hints to work the variable name created with pyya.init_config\n'
232242
'# should have the same name (e.g. config = pyya.init_config())\n'
233243
f'{_stub_variable_name}: Config\n'
234244
)
235245
output_file.write_text(stub_full)
236246
logger.info(f'{output_file} created')
237-
return PyyaConfig()
247+
return None
238248

239249
try:
250+
file_path = Path(config)
251+
if (ext := file_path.suffix) not in ('.yaml', '.yml', '.toml'):
252+
raise PyyaError(f'{config} file format is not supported') from None
253+
file_handler = _yaml.safe_load if ext in ('.yaml', '.yml') else _toml.load
240254
with open(Path(config)) as fstream:
241-
_raw_data: ConfigType = _yaml.safe_load(fstream) or {}
255+
_raw_data: ConfigType = file_handler(fstream) or {}
242256
_raw_data = _sanitize_keys(_raw_data)
243-
except _yaml.YAMLError as e:
244-
err_msg = f'{config} file is corrupted: {e}'
245-
logger.error(err_msg)
246-
raise PyyaError(err_msg) from None
257+
except (_yaml.YAMLError, _toml_decoder.TomlDecodeError) as e:
258+
raise PyyaError(f'{config} file is corrupted: {e}') from None
247259
except FileNotFoundError:
248260
logger.warning(f'{config} file not found, using {default_config}')
249261
_raw_data = {}
262+
except PyyaError as e:
263+
logger.error(e)
264+
raise e from None
265+
except Exception as e:
266+
err_msg = f'{config} Unknown error: {e}'
267+
logger.error(err_msg)
268+
raise PyyaError(err_msg) from None
250269

251270
if merge_configs:
252271
if sections_ignored_on_merge is None:
@@ -281,8 +300,11 @@ def _get_default_raw_data() -> ConfigType:
281300
logger.info(f'The following sections were overwritten:\n{pformat(_raw_data_copy)}')
282301
try:
283302
logger.debug(f'Resulting config:\n{pformat(_raw_data)}')
284-
return PyyaConfig(_munchify(_raw_data))
303+
return _munchify(_raw_data)
285304
except Exception as e:
286305
err_msg = f'Failed parsing config file: {e!r}'
287306
logger.error(err_msg)
288307
raise PyyaError(err_msg) from None
308+
309+
310+
__all__ = ['init_config', 'logger']

pyya/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
def main() -> None:
99
parser = argparse.ArgumentParser(description='stub generator for pyya')
1010
parser.add_argument(
11-
'-i', '--input', default='default.config.yaml', help='path to YAML file from which to generate stub file'
11+
'-i', '--input', default='default.config.yaml', help='path to YAML/TOML file from which to generate stub file'
1212
)
1313
parser.add_argument('-o', '--output', default='config.pyi', help='path to resulting stub pyi file')
1414
parser.add_argument('--var-name', default='config', help='variable name to refer to config object')

uv.lock

Lines changed: 23 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)