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

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 

10 

11import tomli 

12import yaml as yaml_lib 

13from dotenv import dotenv_values 

14 

15from ._types import T_config, as_tconfig 

16 

17 

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) 

24 

25 

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) 

32 

33 

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) 

40 

41 

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) 

48 

49 

50def _convert_key(key: str) -> str: 

51 return key.replace(" ", "_").replace("-", "_") 

52 

53 

54def _convert_value(value: str) -> str: 

55 if value.startswith('"') and value.endswith('"'): 

56 value = value.removeprefix('"').removesuffix('"') 

57 return value 

58 

59 

60RecursiveDict = dict[str, typing.Union[str, "RecursiveDict"]] 

61 

62 

63def ini(_: typing.Optional[BinaryIO], fullpath: Path) -> T_config: 

64 """ 

65 Load an ini file. 

66 """ 

67 config = configparser.ConfigParser() 

68 config.read(fullpath) 

69 

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] 

79 

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 

85 

86 return as_tconfig(dict(final_data))