Coverage for /home/runner/work/coxbuild/coxbuild/src/coxbuild/configurations/builders.py: 70%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

60 statements  

1import json 

2import os 

3from abc import ABC, abstractmethod 

4from pathlib import Path 

5from typing import Any 

6 

7import yaml 

8 

9from . import Configuration 

10 

11 

12class ConfigurationBuilder(ABC): 

13 @abstractmethod 

14 def build(self, config: Configuration) -> None: pass 

15 

16 

17class EnvironmentConfigurationBuilder(ConfigurationBuilder): 

18 def build(self, config: Configuration) -> None: 

19 env = config.env() 

20 envbuild = env.section("coxbuild") 

21 for key in os.environ: 

22 env[key] = os.getenv(key) 

23 for key in envbuild: 

24 config[key] = envbuild[key] 

25 

26 

27class DictionaryConfigurationBuilder(ConfigurationBuilder): 

28 """ 

29 Use dictionary to build configuration. 

30 

31 Nested dictionary will be converted to a subsection. 

32 To avoid this behavior, use an entry with key '__keep__'. 

33 """ 

34 

35 def __init__(self, data: dict[str, Any]) -> None: 

36 super().__init__() 

37 self.data = data 

38 

39 def build(self, config: Configuration) -> None: 

40 for key in self.data: 

41 value = self.data[key] 

42 

43 if isinstance(value, dict): 

44 if "__keep__" in value: 

45 del value["__keep__"] 

46 config[key] = value 

47 else: 

48 sub = config.section(key) 

49 DictionaryConfigurationBuilder(value).build(sub) 

50 else: 

51 config[key] = value 

52 

53 

54class JsonConfigurationBuilder(ConfigurationBuilder): 

55 def __init__(self, path: Path) -> None: 

56 super().__init__() 

57 self.path = path 

58 

59 def build(self, config: Configuration) -> None: 

60 if not self.path.exists(): 

61 return 

62 DictionaryConfigurationBuilder( 

63 json.loads(self.path.read_text())).build(config) 

64 

65 

66class YamlConfigurationBuilder(ConfigurationBuilder): 

67 def __init__(self, path: Path) -> None: 

68 super().__init__() 

69 self.path = path 

70 

71 def build(self, config: Configuration) -> None: 

72 if not self.path.exists(): 

73 return 

74 DictionaryConfigurationBuilder( 

75 yaml.safe_load(self.path.read_text())).build(config) 

76 

77 

78class ConfigurationBuilderCollection(ConfigurationBuilder): 

79 def __init__(self) -> None: 

80 super().__init__() 

81 self.builders: list[ConfigurationBuilder] = [] 

82 

83 def add(self, builder: ConfigurationBuilder) -> "ConfigurationBuilderCollection": 

84 self.builders.append(builder) 

85 return self 

86 

87 def build(self, config: Configuration) -> None: 

88 for builder in self.builders: 

89 builder.build(config) 

90 

91 

92def getDefaultBuilder() -> ConfigurationBuilderCollection: 

93 """Use environment variables and file buildcox.[json/yaml] to build configuration.""" 

94 return ConfigurationBuilderCollection() \ 

95 .add(EnvironmentConfigurationBuilder()) \ 

96 .add(JsonConfigurationBuilder(Path("buildcox.json"))) \ 

97 .add(YamlConfigurationBuilder(Path("buildcox.yaml"))) \ 

98 .add(YamlConfigurationBuilder(Path("buildcox.yml")))