Coverage for src/configuraptor/loaders/loaders_shared.py: 100%
48 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-21 10:22 +0200
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-21 10:22 +0200
1"""
2File loaders that work regardless of Python version.
3"""
4import configparser
5import json as json_lib
6import typing
7from collections import defaultdict
8from pathlib import Path
9from typing import BinaryIO
11import tomli
12import yaml as yaml_lib
13from dotenv import dotenv_values
15from ._types import T_config, as_tconfig
16from .register import register_loader
19@register_loader
20def json(f: BinaryIO, _: typing.Optional[Path]) -> T_config:
21 """
22 Load a JSON file.
23 """
24 data = json_lib.load(f)
25 return as_tconfig(data)
28@register_loader(".yaml", ".yml")
29def yaml(f: BinaryIO, _: typing.Optional[Path]) -> typing.Any:
30 """
31 Load a YAML file.
32 """
33 return yaml_lib.load(f, yaml_lib.SafeLoader)
36@register_loader
37def toml(f: BinaryIO, _: typing.Optional[Path]) -> typing.Any:
38 """
39 Load a toml file.
40 """
41 return tomli.load(f)
44@register_loader(".env")
45def dotenv(_: typing.Optional[BinaryIO], fullpath: Path) -> typing.Any:
46 """
47 Load a toml file.
48 """
49 return dotenv_values(fullpath)
52def _convert_key(key: str) -> str:
53 return key.replace(" ", "_").replace("-", "_")
56def _convert_value(value: str) -> str:
57 if value.startswith('"') and value.endswith('"'):
58 value = value.removeprefix('"').removesuffix('"')
59 return value
62RecursiveDict = dict[str, typing.Union[str, "RecursiveDict"]]
65@register_loader
66def ini(_: typing.Optional[BinaryIO], fullpath: Path) -> typing.Any:
67 """
68 Load an ini file.
69 """
70 config = configparser.ConfigParser()
71 config.read(fullpath)
73 final_data: defaultdict[str, RecursiveDict] = defaultdict(dict)
74 for section in config.sections():
75 data: RecursiveDict = {_convert_key(k): _convert_value(v) for k, v in dict(config[section]).items()}
76 section = _convert_key(section)
77 if "." in section:
78 _section = _current = {} # type: ignore
79 for part in section.split("."):
80 _current[part] = _current.get(part) or {}
81 _current = _current[part]
83 # nested structure is set up, now load the right data into it:
84 _current |= data
85 final_data |= _section
86 else:
87 final_data[section] = data
89 return dict(final_data)