Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

# encoding: utf-8 

from __future__ import print_function, division, absolute_import 

 

from configparser import ConfigParser 

from collections import OrderedDict, ChainMap 

import io 

import os 

import re 

import sys 

import tempfile 

 

 

class Config(OrderedDict): 

 

"""augments a nested dictionary by accessing keys as attributes + some methods 

for output and to-string conversion. 

""" 

 

__getattr__ = OrderedDict.__getitem__ 

__setattr__ = OrderedDict.__setitem__ 

__delattr__ = OrderedDict.__delitem__ 

 

def print_(self, indent=0, fh=sys.stdout): 

for (k, v) in self.items(): 

if isinstance(v, Config): 

print(" " * indent, "{}:".format(k), file=fh) 

v.print_(indent + 4, fh) 

else: 

print(" " * indent, "{}: {}".format(k, v), file=fh) 

 

def __str__(self): 

fh = io.StringIO() 

self.print_(0, fh) 

return fh.getvalue() 

 

def as_dict(self): 

result = OrderedDict() 

for name, value in self.items(): 

if isinstance(value, Config): 

value = value.as_dict() 

result[name] = value 

 

return result 

 

def __eq__(self, other): 

return isinstance(other, Config) and self.as_dict() == other.as_dict() 

 

def __ne__(self, other): 

return not (self == other) 

 

 

class MagicConfig(Config): 

 

"""allows nested setting and access of attributes without explicitly constructing 

the intermediate objects: 

 

>>> c = Config() 

>>> c.logger.level = 10 

>>> c.logger.file = "~/datapool.config" 

>>> print(c.logger.level) 

>>> 10 

 

instead of attributes dictionary key style can be used: 

>>> c.logger["file"] = "~/datapool.config" 

 

This is only an example, levels might be 1, 2 or more and mixed ! 

""" 

 

def __getitem__(self, name): 

if name not in self.keys(): 

self[name] = self.__class__() 

return super().__getitem__(name) 

 

__getattr__ = __getitem__ 

 

 

def write_ini(config, path_or_handle): 

""" 

writes a Config object `config` or nested dict (Config is such a nested dict) 

in .ini file style to `path`. 

""" 

assert isinstance(config, dict) 

if isinstance(path_or_handle, str): 

with open(path_or_handle, "w") as fh: 

_write_ini(config, fh) 

else: 

_write_ini(config, path_or_handle) 

 

 

def _write_ini(config, fh): 

 

cp = ConfigParser() 

for section, mapping in config.items(): 

if section.startswith("__"): 

# eg skip __file__ attribute on config objects 

continue 

assert isinstance(mapping, dict) 

cp.add_section(section) 

for (key, value) in mapping.items(): 

cp.set(section, key, str(value)) 

 

cp.write(fh) 

if hasattr(fh, "name"): 

config.__file__ = fh.name 

 

 

def _convert_and_resolve_variables(v, variable_settings): 

"""tries to convert `v` to int, float, str in this order""" 

for type_ in (int, float): 

try: 

return type_(v) 

except ValueError: 

pass 

 

tmpdir = tempfile.mkdtemp() 

 

fallbacks = {"TMP": tmpdir, 

} 

 

lookup = ChainMap(variable_settings or {}, dict(os.environ), fallbacks) 

 

def resolve(match): 

s, e = match.start(), match.end() 

var = match.string[s + 1: e] 

return lookup.get(var, "<invalid var ${}>".format(var)) 

 

return re.sub(r"\$[A-Z]+", resolve, v) 

 

 

def read_ini(path, variable_settings=None): 

"""reads a ini file as a Config object. 

 

features: 

- sets attribute __file__ so relative pathes in the config file might be resolved 

 

value handling: 

- resolves environment variables in the value fields 

- resovles fallback variables as $TMP which are not defined on every system 

- tries best conversion, so "10" will be converted to int, and "1.23" to float 

""" 

cp = ConfigParser() 

cp.read(path) 

config = Config() 

for section in cp.sections(): 

for key, value in cp.items(section): 

value = _convert_and_resolve_variables(value, variable_settings) 

if section not in config: 

config[section] = Config() 

config[section][key] = value 

config.__file__ = os.path.abspath(path) 

return config