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
« prev ^ index » next coverage.py v7.1.0, created at 2023-11-15 00:54 +0000
1from typing import Any, Dict, Union # noqa: F401
3import os
4from pathlib import Path
6import schematics.types as sty
7from schematics.models import Model
8from schematics.exceptions import CompoundError, DataError, ValidationError
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]
39def is_color_scheme(item):
40 # type: (dict) -> None
41 '''
42 Determines if given dict is a valid color scheme.
44 Args:
45 item (dict): Color scheme dictionary.
47 Raises:
48 ValidationError: If item contains invalid keys.
49 '''
50 keys = list(COLOR_SCHEME.keys())
51 ikeys = list(item.keys())
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)
60def is_csv(filepath):
61 # type: (Union[str, Path]) -> None
62 '''
63 Determines if given filepath is a CSV.
65 Args:
66 filepath (str or Path): Filepath.
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)
78def is_comparator(item):
79 # type: (str) -> None
80 '''
81 Ensures that given string is a legal comparator.
83 Legal comparators:
85 * ==
86 * !=
87 * >
88 * >=
89 * <
90 * <=
91 * ~
92 * !~
94 Args:
95 item (str): String to be tested.
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)
106def is_metric(item):
107 # type: (str) -> None
108 '''
109 Ensures that given string is a legal metric.
111 Legal metrics:
113 * max
114 * mean
115 * min
116 * std
117 * sum
118 * var
119 * count
121 Args:
122 item (str): String to be tested.
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)
133def is_plot_kind(item):
134 '''
135 Ensures item is a kind of plotly plot.
137 Args:
138 item (str): Kind of plot.
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)
151def is_bar_mode(item):
152 '''
153 Ensures mode is a legal bar mode.
155 Args:
156 item (str): Mode.
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)
167def is_percentage(number):
168 '''
169 Ensures number is between 0 and 100.
171 Args:
172 number (float): Number to be tested.
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)
183# SCHEMATICS--------------------------------------------------------------------
184class FilterAction(Model):
185 '''
186 Schematic for filter actions.
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)
198class GroupAction(Model):
199 '''
200 Schematic for group actions.
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')
213class PivotAction(Model):
214 '''
215 Schematic for group actions.
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)
230class ConformAction(Model):
231 '''
232 Schematic for conform actions.
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 )
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.
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.
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())
279class FigureItem(Model):
280 '''
281 Schematic for a plot figure.
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')
308class PlotItem(Model):
309 '''
310 Schematic for a plot.
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])
326class Config(Model):
327 '''
328 Configuration of database.
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=[])