Coverage for /home/ubuntu/shekels/python/shekels/core/config.py: 100%

94 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-11-15 00:54 +0000

1from typing import Any, Dict, Union # noqa: F401 

2 

3import os 

4from pathlib import Path 

5 

6import schematics.types as sty 

7from schematics.models import Model 

8from schematics.exceptions import CompoundError, DataError, ValidationError 

9 

10 

11# VALIDATORS-------------------------------------------------------------------- 

12COLOR_SCHEME = dict( 

13 dark1='#040404', 

14 dark2='#141414', 

15 bg='#181818', 

16 grey1='#242424', 

17 grey2='#444444', 

18 light1='#A4A4A4', 

19 light2='#F4F4F4', 

20 dialog1='#444459', 

21 dialog2='#5D5D7A', 

22 red1='#F77E70', 

23 red2='#DE958E', 

24 orange1='#EB9E58', 

25 orange2='#EBB483', 

26 yellow1='#E8EA7E', 

27 yellow2='#E9EABE', 

28 green1='#8BD155', 

29 green2='#A0D17B', 

30 cyan1='#7EC4CF', 

31 cyan2='#B6ECF3', 

32 blue1='#5F95DE', 

33 blue2='#93B6E6', 

34 purple1='#C98FDE', 

35 purple2='#AC92DE', 

36) # type: Dict[str, str] 

37 

38 

39def is_color_scheme(item): 

40 # type: (dict) -> None 

41 ''' 

42 Determines if given dict is a valid color scheme. 

43 

44 Args: 

45 item (dict): Color scheme dictionary. 

46 

47 Raises: 

48 ValidationError: If item contains invalid keys. 

49 ''' 

50 keys = list(COLOR_SCHEME.keys()) 

51 ikeys = list(item.keys()) 

52 

53 diff = set(ikeys).difference(keys) # type: Any 

54 diff = sorted(list(diff)) 

55 if len(diff) > 0: 

56 msg = f'Invalid color scheme keys: {diff}.' 

57 raise ValidationError(msg) 

58 

59 

60def is_csv(filepath): 

61 # type: (Union[str, Path]) -> None 

62 ''' 

63 Determines if given filepath is a CSV. 

64 

65 Args: 

66 filepath (str or Path): Filepath. 

67 

68 Raises: 

69 ValidationError: If filepath is not a CSV. 

70 ''' 

71 filepath = Path(filepath).as_posix() 

72 ext = os.path.splitext(filepath)[-1][1:].lower() 

73 if not os.path.isfile(filepath) or ext != 'csv': 

74 msg = f'{filepath} is not a valid CSV file.' 

75 raise ValidationError(msg) 

76 

77 

78def is_comparator(item): 

79 # type: (str) -> None 

80 ''' 

81 Ensures that given string is a legal comparator. 

82 

83 Legal comparators: 

84 

85 * == 

86 * != 

87 * > 

88 * >= 

89 * < 

90 * <= 

91 * ~ 

92 * !~ 

93 

94 Args: 

95 item (str): String to be tested. 

96 

97 Raises: 

98 ValidationError: If item is not a legal comparator. 

99 ''' 

100 comps = ['==', '!=', '>', '>=', '<', '<=', '~', '!~'] 

101 if item not in comps: 

102 msg = f'{item} is not a legal comparator. Legal comparators: {comps}.' 

103 raise ValidationError(msg) 

104 

105 

106def is_metric(item): 

107 # type: (str) -> None 

108 ''' 

109 Ensures that given string is a legal metric. 

110 

111 Legal metrics: 

112 

113 * max 

114 * mean 

115 * min 

116 * std 

117 * sum 

118 * var 

119 * count 

120 

121 Args: 

122 item (str): String to be tested. 

123 

124 Raises: 

125 ValidationError: If item is not a legal metric. 

126 ''' 

127 metrics = ['max', 'mean', 'min', 'std', 'sum', 'var', 'count'] 

128 if item not in metrics: 

129 msg = f'{item} is not a legal metric. Legal metrics: {metrics}.' 

130 raise ValidationError(msg) 

131 

132 

133def is_plot_kind(item): 

134 ''' 

135 Ensures item is a kind of plotly plot. 

136 

137 Args: 

138 item (str): Kind of plot. 

139 

140 Raises: 

141 ValidationError: If item is a plot kind. 

142 ''' 

143 kinds = [ 

144 'area', 'bar', 'barh', 'line', 'lines', 'ratio', 'scatter', 'spread' 

145 ] 

146 if item not in kinds: 

147 msg = f'{item} is not a legal plot kind. Legal kinds: {kinds}.' 

148 raise ValidationError(msg) 

149 

150 

151def is_bar_mode(item): 

152 ''' 

153 Ensures mode is a legal bar mode. 

154 

155 Args: 

156 item (str): Mode. 

157 

158 Raises: 

159 ValidationError: If mode is not a legal bar mode. 

160 ''' 

161 modes = ['stack', 'group', 'overlay'] 

162 if item not in modes: 

163 msg = f'{item} is not a legal bar mode. Legal bar modes: {modes}.' 

164 raise ValidationError(msg) 

165 

166 

167def is_percentage(number): 

168 ''' 

169 Ensures number is between 0 and 100. 

170 

171 Args: 

172 number (float): Number to be tested. 

173 

174 Raises: 

175 ValidationError: If number is not between 0 and 100. 

176 ''' 

177 if number < 0 or number > 100: 

178 msg = f'{number} is not a legal percentage. ' 

179 msg += f'{number} is not between 0 and 100.' 

180 raise ValidationError(msg) 

181 

182 

183# SCHEMATICS-------------------------------------------------------------------- 

184class FilterAction(Model): 

185 ''' 

186 Schematic for filter actions. 

187 

188 Attributes: 

189 column (str): Column name. 

190 comparator (str): String representation of comparator. 

191 value (object): Value to be compared. 

192 ''' 

193 column = sty.StringType(required=True) 

194 comparator = sty.StringType(required=True, validators=[is_comparator]) 

195 value = sty.BaseType(required=True) 

196 

197 

198class GroupAction(Model): 

199 ''' 

200 Schematic for group actions. 

201 

202 Attributes: 

203 columns (str or list[str]): Columns to group data by. 

204 metric (str): Aggregation metric. 

205 datetime_column (str, optinal): Datetime column for time grouping. 

206 Default: date. 

207 ''' 

208 columns = sty.ListType(sty.StringType(), required=True) 

209 metric = sty.StringType(required=True, validators=[is_metric]) 

210 datetime_column = sty.StringType(required=True, default='date') 

211 

212 

213class PivotAction(Model): 

214 ''' 

215 Schematic for group actions. 

216 

217 Attributes: 

218 columns (list[str]): Columns whose unique values become separate traces 

219 within a plot. 

220 values (list[str], optional): Columns whose values become the values 

221 within each trace of a plot. Default: []. 

222 index (str, optional): Column whose values become the y axis values of a 

223 plot. Default: None. 

224 ''' 

225 columns = sty.ListType(sty.StringType(), required=True) 

226 values = sty.ListType(sty.StringType(), required=True, default=[]) 

227 index = sty.StringType(required=True, default=None) 

228 

229 

230class ConformAction(Model): 

231 ''' 

232 Schematic for conform actions. 

233 

234 Attributes: 

235 action (str): Must be 'overwrite' or 'substitute'. 

236 source_column (str): Source column to be matched. 

237 target_column (str): Target column to be overwritten. 

238 mapping (dict): Mapping of matched key in source column with replacement 

239 value in target column. 

240 ''' 

241 action = sty.StringType(required=True, choices=['overwrite', 'substitute']) 

242 source_column = sty.StringType(required=True) 

243 target_column = sty.StringType(required=True) 

244 mapping = sty.DictType( 

245 sty.UnionType( 

246 types=[sty.FloatType, sty.IntType, sty.BooleanType, sty.StringType] 

247 ), 

248 required=True, 

249 ) 

250 

251 def validate(self): 

252 ''' 

253 Validates the state of the model. If the data is invalid, raises a 

254 DataError with error messages. Also, performs a stricter validation on 

255 mapping if action is substitute. 

256 

257 Args: 

258 partial (bool, optional): Allow partial data to validate. 

259 Essentially drops the required=True settings from field 

260 definitions. Default: False. 

261 convert (bool, optional): Controls whether to perform import 

262 conversion before validating. Can be turned off to skip an 

263 unnecessary conversion step if all values are known to have the 

264 right datatypes (e.g., when validating immediately after the 

265 initial import). Default: True. 

266 

267 Raises: 

268 DataError: If data is invalid. 

269 ''' 

270 super().validate() 

271 if self.action == 'substitute': 

272 try: 

273 sty.DictType(sty.StringType(), required=True)\ 

274 .validate(self.mapping) 

275 except CompoundError as e: 

276 raise DataError(e.to_primitive()) 

277 

278 

279class FigureItem(Model): 

280 ''' 

281 Schematic for a plot figure. 

282 

283 Attributes: 

284 kind (str): Type of plot. Default: bar. 

285 color_scheme (dict[str, str]): Color scheme for plot. 

286 Default: {'grey1': '#181818', 'bg': '#242424'}. 

287 x_axis (str): Column to use as x axis: Default: None. 

288 y_axis (str): Column to use as y axis: Default: None. 

289 title (str): Title of plot. Default: None. 

290 x_title (str): Title of plot x axis. Default: None. 

291 y_title (str): Title of plot y axis. Default: None. 

292 bins (int): Number of bins if histogram. Default: 50. 

293 bar_mode (str): How bars in bar graph are presented. Default: stack. 

294 ''' 

295 kind = sty.StringType(default='bar', validators=[is_plot_kind]) 

296 color_scheme = sty.DictType( 

297 sty.StringType(), default=dict(grey1='#181818', bg='#242424') 

298 ) 

299 x_axis = sty.StringType(default=None) 

300 y_axis = sty.StringType(default=None) 

301 title = sty.StringType(default=None) 

302 x_title = sty.StringType(default=None) 

303 y_title = sty.StringType(default=None) 

304 bins = sty.IntType(default=50) 

305 bar_mode = sty.StringType(validators=[is_bar_mode], default='stack') 

306 

307 

308class PlotItem(Model): 

309 ''' 

310 Schematic for a plot. 

311 

312 Attributes: 

313 filters (list[dict]): How data is filtered. Default: []. 

314 group (dict): How data is grouped. Default: {}. 

315 pivot (dict): How data is pivoted. Default: {}. 

316 figure (dict): Plot figure details. Default: {}. 

317 min_width (float): Minimum width of plot. Default: 0.25. 

318 ''' 

319 filters = sty.ListType(sty.ModelType(FilterAction), default=[]) 

320 group = sty.ModelType(GroupAction) 

321 pivot = sty.ModelType(PivotAction) 

322 figure = sty.ModelType(FigureItem, default={}) 

323 min_width = sty.FloatType(default=25, validators=[is_percentage]) 

324 

325 

326class Config(Model): 

327 ''' 

328 Configuration of database. 

329 

330 Attributes: 

331 data_path (str): Path to CSV file. 

332 columns (list[str]): Columns to be displayed in data. 

333 default_query (str): Placeholder SQL query string. 

334 font_family (str): Font family. 

335 color_scheme (dict): Color scheme. 

336 conform (lit[dict]): List of conform actions. 

337 plots (list[dict]): List of plots. 

338 ''' 

339 data_path = sty.StringType(required=True, validators=[is_csv]) 

340 columns = sty.ListType(sty.StringType, default=[]) 

341 default_query = sty.StringType(default='select * from data') 

342 font_family = sty.StringType(default='sans-serif, "sans serif"') 

343 color_scheme = sty.DictType( 

344 sty.StringType(), validators=[is_color_scheme], default=COLOR_SCHEME 

345 ) 

346 conform = sty.ListType(sty.ModelType(ConformAction), default=[]) 

347 plots = sty.ListType(sty.ModelType(PlotItem), default=[])