Coverage for src/configuraptor/loaders/loaders_shared.py: 100%
45 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-03 15:35 +0200
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-03 15:35 +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
18def json(f: BinaryIO, _: typing.Optional[Path]) -> T_config:
19 """
20 Load a JSON file.
21 """
22 data = json_lib.load(f)
23 return as_tconfig(data)
26def yaml(f: BinaryIO, _: typing.Optional[Path]) -> T_config:
27 """
28 Load a YAML file.
29 """
30 data = yaml_lib.load(f, yaml_lib.SafeLoader)
31 return as_tconfig(data)
34def toml(f: BinaryIO, _: typing.Optional[Path]) -> T_config:
35 """
36 Load a toml file.
37 """
38 data = tomli.load(f)
39 return as_tconfig(data)
42def dotenv(_: typing.Optional[BinaryIO], fullpath: Path) -> T_config:
43 """
44 Load a toml file.
45 """
46 data = dotenv_values(fullpath)
47 return as_tconfig(data)
50def _convert_key(key: str) -> str:
51 return key.replace(" ", "_").replace("-", "_")
54def _convert_value(value: str) -> str:
55 if value.startswith('"') and value.endswith('"'):
56 value = value.removeprefix('"').removesuffix('"')
57 return value
60RecursiveDict = dict[str, typing.Union[str, "RecursiveDict"]]
63def ini(_: typing.Optional[BinaryIO], fullpath: Path) -> T_config:
64 """
65 Load an ini file.
66 """
67 config = configparser.ConfigParser()
68 config.read(fullpath)
70 final_data: defaultdict[str, RecursiveDict] = defaultdict(dict)
71 for section in config.sections():
72 data: RecursiveDict = {_convert_key(k): _convert_value(v) for k, v in dict(config[section]).items()}
73 section = _convert_key(section)
74 if "." in section:
75 _section = _current = {} # type: ignore
76 for part in section.split("."):
77 _current[part] = _current.get(part) or {}
78 _current = _current[part]
80 # nested structure is set up, now load the right data into it:
81 _current |= data
82 final_data |= _section
83 else:
84 final_data[section] = data
86 return as_tconfig(dict(final_data))