squirrels 0.1.1.post1__py3-none-any.whl → 0.2.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of squirrels might be problematic. Click here for more details.

Files changed (74) hide show
  1. squirrels/__init__.py +10 -16
  2. squirrels/_api_server.py +234 -80
  3. squirrels/_authenticator.py +84 -0
  4. squirrels/_command_line.py +60 -72
  5. squirrels/_connection_set.py +96 -0
  6. squirrels/_constants.py +114 -33
  7. squirrels/_environcfg.py +77 -0
  8. squirrels/_initializer.py +126 -67
  9. squirrels/_manifest.py +195 -168
  10. squirrels/_models.py +495 -0
  11. squirrels/_package_loader.py +26 -0
  12. squirrels/_parameter_configs.py +401 -0
  13. squirrels/_parameter_sets.py +188 -0
  14. squirrels/_py_module.py +60 -0
  15. squirrels/_timer.py +36 -0
  16. squirrels/_utils.py +81 -49
  17. squirrels/_version.py +2 -2
  18. squirrels/arguments/init_time_args.py +32 -0
  19. squirrels/arguments/run_time_args.py +82 -0
  20. squirrels/data_sources.py +380 -155
  21. squirrels/dateutils.py +86 -57
  22. squirrels/package_data/base_project/Dockerfile +15 -0
  23. squirrels/package_data/base_project/connections.yml +7 -0
  24. squirrels/package_data/base_project/database/{sample_database.db → expenses.db} +0 -0
  25. squirrels/package_data/base_project/environcfg.yml +29 -0
  26. squirrels/package_data/base_project/ignores/.dockerignore +8 -0
  27. squirrels/package_data/base_project/ignores/.gitignore +7 -0
  28. squirrels/package_data/base_project/models/dbviews/database_view1.py +36 -0
  29. squirrels/package_data/base_project/models/dbviews/database_view1.sql +15 -0
  30. squirrels/package_data/base_project/models/federates/dataset_example.py +20 -0
  31. squirrels/package_data/base_project/models/federates/dataset_example.sql +3 -0
  32. squirrels/package_data/base_project/parameters.yml +109 -0
  33. squirrels/package_data/base_project/pyconfigs/auth.py +47 -0
  34. squirrels/package_data/base_project/pyconfigs/connections.py +28 -0
  35. squirrels/package_data/base_project/pyconfigs/context.py +45 -0
  36. squirrels/package_data/base_project/pyconfigs/parameters.py +55 -0
  37. squirrels/package_data/base_project/seeds/mocks/category.csv +3 -0
  38. squirrels/package_data/base_project/seeds/mocks/max_filter.csv +2 -0
  39. squirrels/package_data/base_project/seeds/mocks/subcategory.csv +6 -0
  40. squirrels/package_data/base_project/squirrels.yml.j2 +57 -0
  41. squirrels/package_data/base_project/tmp/.gitignore +2 -0
  42. squirrels/package_data/static/script.js +159 -63
  43. squirrels/package_data/static/style.css +79 -15
  44. squirrels/package_data/static/widgets.js +133 -0
  45. squirrels/package_data/templates/index.html +65 -23
  46. squirrels/package_data/templates/index2.html +22 -0
  47. squirrels/parameter_options.py +216 -119
  48. squirrels/parameters.py +407 -478
  49. squirrels/user_base.py +58 -0
  50. squirrels-0.2.0.dev0.dist-info/METADATA +126 -0
  51. squirrels-0.2.0.dev0.dist-info/RECORD +56 -0
  52. {squirrels-0.1.1.post1.dist-info → squirrels-0.2.0.dev0.dist-info}/WHEEL +1 -2
  53. squirrels-0.2.0.dev0.dist-info/entry_points.txt +3 -0
  54. squirrels/_credentials_manager.py +0 -87
  55. squirrels/_module_loader.py +0 -37
  56. squirrels/_parameter_set.py +0 -151
  57. squirrels/_renderer.py +0 -286
  58. squirrels/_timed_imports.py +0 -37
  59. squirrels/connection_set.py +0 -126
  60. squirrels/package_data/base_project/.gitignore +0 -4
  61. squirrels/package_data/base_project/connections.py +0 -20
  62. squirrels/package_data/base_project/datasets/sample_dataset/context.py +0 -22
  63. squirrels/package_data/base_project/datasets/sample_dataset/database_view1.py +0 -29
  64. squirrels/package_data/base_project/datasets/sample_dataset/database_view1.sql.j2 +0 -12
  65. squirrels/package_data/base_project/datasets/sample_dataset/final_view.py +0 -11
  66. squirrels/package_data/base_project/datasets/sample_dataset/final_view.sql.j2 +0 -3
  67. squirrels/package_data/base_project/datasets/sample_dataset/parameters.py +0 -47
  68. squirrels/package_data/base_project/datasets/sample_dataset/selections.cfg +0 -9
  69. squirrels/package_data/base_project/squirrels.yaml +0 -22
  70. squirrels-0.1.1.post1.dist-info/METADATA +0 -67
  71. squirrels-0.1.1.post1.dist-info/RECORD +0 -40
  72. squirrels-0.1.1.post1.dist-info/entry_points.txt +0 -2
  73. squirrels-0.1.1.post1.dist-info/top_level.txt +0 -1
  74. {squirrels-0.1.1.post1.dist-info → squirrels-0.2.0.dev0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,401 @@
1
+ from __future__ import annotations
2
+ from typing import Type, Optional, Union, Sequence, Iterator
3
+ from dataclasses import dataclass, field
4
+ from abc import ABCMeta, abstractmethod
5
+ from copy import copy
6
+ import pandas as pd
7
+
8
+ from . import parameter_options as po, parameters as p, data_sources as d, _utils as u
9
+ from ._authenticator import User
10
+
11
+
12
+ @dataclass
13
+ class ParameterConfigBase(metaclass=ABCMeta):
14
+ """
15
+ Abstract class for all parameter classes
16
+ """
17
+ name: str
18
+ label: str
19
+ is_hidden: bool # = field(default=False, kw_only=True)
20
+ user_attribute: Optional[str] # = field(default=None, kw_only=True)
21
+ parent_name: Optional[str] # = field(default=None, kw_only=True)
22
+
23
+ @abstractmethod
24
+ def __init__(
25
+ self, widget_type: str, name: str, label: str, *, is_hidden: bool = False, user_attribute: Optional[str] = None,
26
+ parent_name: Optional[str] = None, **kwargs
27
+ ) -> None:
28
+ self.widget_type = widget_type
29
+ self.name = name
30
+ self.label = label
31
+ self.is_hidden = is_hidden
32
+ self.user_attribute = user_attribute
33
+ self.parent_name = parent_name
34
+
35
+ def _get_user_group(self, user: Optional[User]) -> Optional[str]:
36
+ if self.user_attribute is not None:
37
+ if user is None:
38
+ raise u.ConfigurationError(f"Public datasets with non-authenticated users cannot use parameter named " +
39
+ f"'{self.name}' because 'user_attribute' is defined on this parameter.")
40
+ return getattr(user, self.user_attribute)
41
+
42
+ def copy(self):
43
+ """
44
+ Use for unit testing only
45
+ """
46
+ return copy(self)
47
+
48
+ def to_json_dict0(self) -> dict:
49
+ return {
50
+ 'widget_type': self.widget_type,
51
+ 'name': self.name,
52
+ 'label': self.label
53
+ }
54
+
55
+
56
+ @dataclass
57
+ class ParameterConfig(ParameterConfigBase):
58
+ """
59
+ Abstract class for all parameter classes (except DataSourceParameters)
60
+ """
61
+ all_options: Sequence[po.ParameterOption] = field(repr=False)
62
+
63
+ @abstractmethod
64
+ def __init__(
65
+ self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *, is_hidden: bool = False,
66
+ user_attribute: Optional[str] = None, parent_name: Optional[str] = None, **kwargs
67
+ ) -> None:
68
+ super().__init__(widget_type, name, label, is_hidden=is_hidden, user_attribute=user_attribute,
69
+ parent_name=parent_name)
70
+ self.all_options = tuple(self.__to_param_option(x) for x in all_options)
71
+
72
+ def __to_param_option(self, option: Union[po.ParameterOption, dict]) -> po.ParameterOption:
73
+ return self.__class__.ParameterOption(**option) if isinstance(option, dict) else option
74
+
75
+ @staticmethod
76
+ @abstractmethod
77
+ def ParameterOption(*args, **kwargs) -> po.ParameterOption:
78
+ pass
79
+
80
+ @staticmethod
81
+ @abstractmethod
82
+ def DataSource(*args, **kwargs) -> d.DataSource:
83
+ pass
84
+
85
+ def _raise_invalid_input_error(self, selection: str, more_details: str = '', e: Exception = None) -> None:
86
+ raise u.InvalidInputError(f'Selected value "{selection}" is not valid for parameter "{self.name}". ' + more_details) from e
87
+
88
+ @abstractmethod
89
+ def with_selection(
90
+ self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
91
+ *, request_version: Optional[int] = None
92
+ ) -> p.Parameter:
93
+ pass
94
+
95
+ def _get_options_iterator(self, user: Optional[User], parent_param: Optional[p._SelectionParameter]) -> Iterator[po.ParameterOption]:
96
+ user_group = self._get_user_group(user)
97
+ selected_parent_option_ids = frozenset(parent_param._get_selected_ids_as_list()) if parent_param else None
98
+ return (x for x in self.all_options if x._is_valid(user_group, selected_parent_option_ids))
99
+
100
+
101
+ @dataclass
102
+ class SelectionParameterConfig(ParameterConfig):
103
+ """
104
+ Abstract class for select parameter classes (single-select, multi-select, etc)
105
+ """
106
+ children: dict[str, ParameterConfigBase] = field(default_factory=dict, init=False, repr=False)
107
+ trigger_refresh: bool = field(default=False, init=False)
108
+
109
+ @abstractmethod
110
+ def __init__(
111
+ self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.SelectParameterOption, dict]], *, is_hidden: bool = False,
112
+ user_attribute: Optional[str] = None, parent_name: Optional[str] = None, **kwargs
113
+ ) -> None:
114
+ super().__init__(widget_type, name, label, all_options, is_hidden=is_hidden, user_attribute=user_attribute,
115
+ parent_name=parent_name)
116
+ self.children: dict[str, ParameterConfigBase] = dict()
117
+ self.trigger_refresh = False
118
+
119
+ @staticmethod
120
+ def ParameterOption(*args, **kwargs):
121
+ return po.SelectParameterOption(*args, **kwargs)
122
+
123
+ def _add_child_mutate(self, child: ParameterConfigBase):
124
+ self.children[child.name] = child
125
+ self.trigger_refresh = True
126
+
127
+ def _get_options(self, user: Optional[User], parent_param: Optional[p._SelectionParameter]) -> Sequence[po.SelectParameterOption]:
128
+ return tuple(self._get_options_iterator(user, parent_param))
129
+
130
+ def _get_default_ids_iterator(self, options: Sequence[po.SelectParameterOption]) -> Iterator[str]:
131
+ return (x._identifier for x in options if x._is_default)
132
+
133
+ def copy(self) -> MultiSelectParameterConfig:
134
+ """
135
+ Use for unit testing only
136
+ """
137
+ other = super().copy()
138
+ other.children = self.children.copy()
139
+ return other
140
+
141
+ def to_json_dict0(self) -> dict:
142
+ output = super().to_json_dict0()
143
+ output['trigger_refresh'] = self.trigger_refresh
144
+ return output
145
+
146
+
147
+ @dataclass
148
+ class SingleSelectParameterConfig(SelectionParameterConfig):
149
+ """
150
+ Class to define configurations for single-select parameter widgets.
151
+ """
152
+
153
+ def __init__(
154
+ self, name: str, label: str, all_options: Sequence[Union[po.SelectParameterOption, dict]], *, is_hidden: bool = False,
155
+ user_attribute: Optional[str] = None, parent_name: Optional[str] = None, **kwargs
156
+ ) -> None:
157
+ super().__init__("single_select", name, label, all_options, is_hidden=is_hidden, user_attribute=user_attribute,
158
+ parent_name=parent_name)
159
+
160
+ @staticmethod
161
+ def DataSource(*args, **kwargs):
162
+ return d.SingleSelectDataSource(*args, **kwargs)
163
+
164
+ def with_selection(
165
+ self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
166
+ *, request_version: Optional[int] = None
167
+ ) -> p.SingleSelectParameter:
168
+ options = self._get_options(user, parent_param)
169
+ if selection is None:
170
+ selected_id = next(self._get_default_ids_iterator(options), None)
171
+ if selected_id is None and len(options) > 0:
172
+ selected_id = options[0]._identifier
173
+ else:
174
+ selected_id = selection
175
+ return p.SingleSelectParameter(self, options, selected_id)
176
+
177
+
178
+ @dataclass
179
+ class MultiSelectParameterConfig(SelectionParameterConfig):
180
+ """
181
+ Class to define configurations for multi-select parameter widgets.
182
+ """
183
+ include_all: bool # = field(default=True, kw_only=True)
184
+ order_matters: bool # = field(default=False, kw_only=True)
185
+
186
+ def __init__(
187
+ self, name: str, label: str, all_options: Sequence[Union[po.SelectParameterOption, dict]], *, include_all: bool = True,
188
+ order_matters: bool = False, is_hidden: bool = False, user_attribute: Optional[str] = None, parent_name: Optional[str] = None,
189
+ **kwargs
190
+ ) -> None:
191
+ super().__init__("multi_select", name, label, all_options, is_hidden=is_hidden, user_attribute=user_attribute,
192
+ parent_name=parent_name)
193
+ self.include_all = include_all
194
+ self.order_matters = order_matters
195
+
196
+ @staticmethod
197
+ def DataSource(*args, **kwargs):
198
+ return d.MultiSelectDataSource(*args, **kwargs)
199
+
200
+ def with_selection(
201
+ self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
202
+ *, request_version: Optional[int] = None
203
+ ) -> p.MultiSelectParameter:
204
+ options = self._get_options(user, parent_param)
205
+ if selection is None:
206
+ selected_ids = tuple(self._get_default_ids_iterator(options))
207
+ else:
208
+ selected_ids = u.load_json_or_comma_delimited_str_as_list(selection)
209
+ return p.MultiSelectParameter(self, options, selected_ids)
210
+
211
+ def to_json_dict0(self) -> dict:
212
+ output = super().to_json_dict0()
213
+ output['include_all'] = self.include_all
214
+ output['order_matters'] = self.order_matters
215
+ return output
216
+
217
+
218
+ @dataclass
219
+ class _DateTypeParameterConfig(ParameterConfig):
220
+ """
221
+ Abstract class for date and date range parameter configs
222
+ """
223
+
224
+ @abstractmethod
225
+ def __init__(
226
+ self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *, is_hidden: bool = False,
227
+ user_attribute: Optional[str] = None, parent_name: Optional[str] = None, **kwargs
228
+ ) -> None:
229
+ super().__init__(widget_type, name, label, all_options, is_hidden=is_hidden, user_attribute=user_attribute,
230
+ parent_name=parent_name)
231
+
232
+
233
+ @dataclass
234
+ class DateParameterConfig(_DateTypeParameterConfig):
235
+ """
236
+ Class to define configurations for single-select parameter widgets.
237
+ """
238
+
239
+ def __init__(
240
+ self, name: str, label: str, all_options: Sequence[Union[po.DateParameterOption, dict]], *, is_hidden: bool = False,
241
+ user_attribute: Optional[str] = None, parent_name: Optional[str] = None, **kwargs
242
+ ) -> None:
243
+ super().__init__("date", name, label, all_options, is_hidden=is_hidden, user_attribute=user_attribute,
244
+ parent_name=parent_name)
245
+
246
+ @staticmethod
247
+ def ParameterOption(*args, **kwargs):
248
+ return po.DateParameterOption(*args, **kwargs)
249
+
250
+ @staticmethod
251
+ def DataSource(*args, **kwargs):
252
+ return d.DateDataSource(*args, **kwargs)
253
+
254
+ def with_selection(
255
+ self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
256
+ *, request_version: Optional[int] = None
257
+ ) -> p.DateParameter:
258
+ curr_option: po.DateParameterOption = next(self._get_options_iterator(user, parent_param))
259
+ selected_date = curr_option._default_date if selection is None else selection
260
+ return p.DateParameter(self, curr_option, selected_date)
261
+
262
+
263
+ @dataclass
264
+ class DateRangeParameterConfig(_DateTypeParameterConfig):
265
+ """
266
+ Class to define configurations for single-select parameter widgets.
267
+ """
268
+
269
+ def __init__(
270
+ self, name: str, label: str, all_options: Sequence[Union[po.DateRangeParameterOption, dict]], *, is_hidden: bool = False,
271
+ user_attribute: Optional[str] = None, parent_name: Optional[str] = None, **kwargs
272
+ ) -> None:
273
+ super().__init__("date_range", name, label, all_options, is_hidden=is_hidden, user_attribute=user_attribute,
274
+ parent_name=parent_name)
275
+
276
+ @staticmethod
277
+ def ParameterOption(*args, **kwargs):
278
+ return po.DateRangeParameterOption(*args, **kwargs)
279
+
280
+ @staticmethod
281
+ def DataSource(*args, **kwargs):
282
+ return d.DateRangeDataSource(*args, **kwargs)
283
+
284
+ def with_selection(
285
+ self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
286
+ *, request_version: Optional[int] = None
287
+ ) -> p.DateParameter:
288
+ curr_option: po.DateRangeParameterOption = next(self._get_options_iterator(user, parent_param))
289
+ if selection is None:
290
+ selected_start_date = curr_option._default_start_date
291
+ selected_end_date = curr_option._default_end_date
292
+ else:
293
+ try:
294
+ selected_start_date, selected_end_date = u.load_json_or_comma_delimited_str_as_list(selection)
295
+ except ValueError as e:
296
+ self._raise_invalid_input_error(selection, "Date range parameter selection must be two dates joined by comma.", e)
297
+ return p.DateRangeParameter(self, curr_option, selected_start_date, selected_end_date)
298
+
299
+
300
+ @dataclass
301
+ class _NumericParameterConfig(ParameterConfig):
302
+ """
303
+ Abstract class for number and number range parameter configs
304
+ """
305
+
306
+ @abstractmethod
307
+ def __init__(
308
+ self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *, is_hidden: bool = False,
309
+ user_attribute: Optional[str] = None, parent_name: Optional[str] = None, **kwargs
310
+ ) -> None:
311
+ super().__init__(widget_type, name, label, all_options, is_hidden=is_hidden, user_attribute=user_attribute,
312
+ parent_name=parent_name)
313
+
314
+
315
+ @dataclass
316
+ class NumberParameterConfig(_NumericParameterConfig):
317
+ """
318
+ Class to define configurations for single-select parameter widgets.
319
+ """
320
+
321
+ def __init__(
322
+ self, name: str, label: str, all_options: Sequence[Union[po.NumberParameterOption, dict]], *, is_hidden: bool = False,
323
+ user_attribute: Optional[str] = None, parent_name: Optional[str] = None, **kwargs
324
+ ) -> None:
325
+ super().__init__("number", name, label, all_options, is_hidden=is_hidden, user_attribute=user_attribute,
326
+ parent_name=parent_name)
327
+
328
+ @staticmethod
329
+ def ParameterOption(*args, **kwargs):
330
+ return po.NumberParameterOption(*args, **kwargs)
331
+
332
+ @staticmethod
333
+ def DataSource(*args, **kwargs):
334
+ return d.NumberDataSource(*args, **kwargs)
335
+
336
+ def with_selection(
337
+ self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
338
+ *, request_version: Optional[int] = None
339
+ ) -> p.NumberParameter:
340
+ curr_option: po.NumberParameterOption = next(self._get_options_iterator(user, parent_param))
341
+ selected_value = curr_option._default_value if selection is None else selection
342
+ return p.NumberParameter(self, curr_option, selected_value)
343
+
344
+
345
+ @dataclass
346
+ class NumberRangeParameterConfig(_NumericParameterConfig):
347
+ """
348
+ Class to define configurations for single-select parameter widgets.
349
+ """
350
+
351
+ def __init__(
352
+ self, name: str, label: str, all_options: Sequence[Union[po.NumberRangeParameterOption, dict]], *, is_hidden: bool = False,
353
+ user_attribute: Optional[str] = None, parent_name: Optional[str] = None, **kwargs
354
+ ) -> None:
355
+ super().__init__("number_range", name, label, all_options, is_hidden=is_hidden, user_attribute=user_attribute,
356
+ parent_name=parent_name)
357
+
358
+ @staticmethod
359
+ def ParameterOption(*args, **kwargs):
360
+ return po.NumberRangeParameterOption(*args, **kwargs)
361
+
362
+ @staticmethod
363
+ def DataSource(*args, **kwargs):
364
+ return d.NumberRangeDataSource(*args, **kwargs)
365
+
366
+ def with_selection(
367
+ self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
368
+ *, request_version: Optional[int] = None
369
+ ) -> p.NumberRangeParameter:
370
+ curr_option: po.NumberRangeParameterOption = next(self._get_options_iterator(user, parent_param))
371
+ if selection is None:
372
+ selected_lower_value = curr_option._default_lower_value
373
+ selected_upper_value = curr_option._default_upper_value
374
+ else:
375
+ try:
376
+ selected_lower_value, selected_upper_value = u.load_json_or_comma_delimited_str_as_list(selection)
377
+ except ValueError as e:
378
+ self._raise_invalid_input_error(selection, "Number range parameter selection must be two numbers joined by comma.", e)
379
+ return p.NumberRangeParameter(self, curr_option, selected_lower_value, selected_upper_value)
380
+
381
+
382
+ @dataclass
383
+ class DataSourceParameterConfig(ParameterConfigBase):
384
+ """
385
+ Class to define configurations for parameter widgets whose options come from lookup tables
386
+ """
387
+ parameter_type: Type[ParameterConfig]
388
+ data_source: d.DataSource
389
+
390
+ def __init__(
391
+ self, parameter_type: Type[ParameterConfig], name: str, label: str, data_source: Union[d.DataSource, dict], *,
392
+ is_hidden: bool = False, user_attribute: Optional[str] = None, parent_name: Optional[str] = None, **kwargs
393
+ ) -> None:
394
+ super().__init__("data_source", name, label, is_hidden=is_hidden, user_attribute=user_attribute, parent_name=parent_name)
395
+ self.parameter_type = parameter_type
396
+ if isinstance(data_source, dict):
397
+ data_source = parameter_type.DataSource(**data_source)
398
+ self.data_source = data_source
399
+
400
+ def convert(self, df: pd.DataFrame) -> ParameterConfig:
401
+ return self.data_source._convert(self, df)
@@ -0,0 +1,188 @@
1
+ from __future__ import annotations
2
+ from typing import Optional, Sequence
3
+ from dataclasses import dataclass, field
4
+ from collections import OrderedDict
5
+ import concurrent.futures, pandas as pd
6
+
7
+ from . import _utils as u, _constants as c, parameters as p, _parameter_configs as pc, _py_module as pm
8
+ from .arguments.init_time_args import ParametersArgs
9
+ from ._manifest import ManifestIO, ParametersConfig
10
+ from ._connection_set import ConnectionSetIO
11
+ from ._authenticator import User
12
+ from ._timer import timer, time
13
+
14
+
15
+ @dataclass
16
+ class ParameterSet:
17
+ """
18
+ A wrapper class for a sequence of parameters with the selections applied as well
19
+ """
20
+ _parameters_dict: OrderedDict[str, p.Parameter]
21
+
22
+ def get_parameters_as_dict(self) -> dict[str, p.Parameter]:
23
+ return self._parameters_dict.copy()
24
+
25
+ def to_json_dict0(self, *, debug: bool = False) -> dict:
26
+ parameters = []
27
+ for x in self._parameters_dict.values():
28
+ if not x._config.is_hidden or debug:
29
+ parameters.append(x.to_json_dict0())
30
+ return {"parameters": parameters}
31
+
32
+
33
+ @dataclass
34
+ class _ParameterConfigsSet:
35
+ """
36
+ Pool of parameter configs, can create multiple for unit testing purposes
37
+ """
38
+ _data: dict[str, pc.ParameterConfig] = field(default_factory=OrderedDict)
39
+ _data_source_params: dict[str, pc.DataSourceParameterConfig] = field(default_factory=dict)
40
+
41
+ def get(self, name: Optional[str]) -> Optional[pc.ParameterConfig]:
42
+ try:
43
+ return self._data[name] if name is not None else None
44
+ except KeyError as e:
45
+ raise u.ConfigurationError(f'Unable to find parameter named "{name}"') from e
46
+
47
+ def add(self, param_config: pc.ParameterConfigBase) -> None:
48
+ self._data[param_config.name] = param_config
49
+ if isinstance(param_config, pc.DataSourceParameterConfig):
50
+ self._data_source_params[param_config.name] = param_config
51
+
52
+ def _get_all_ds_param_configs(self) -> Sequence[pc.DataSourceParameterConfig]:
53
+ return list(self._data_source_params.values())
54
+
55
+ def __convert_datasource_params(self, df_dict: dict[str, pd.DataFrame]) -> None:
56
+ done = set()
57
+ for curr_name in self._data_source_params:
58
+ stack = [curr_name] # Note: parents must be converted first before children
59
+ while stack:
60
+ name = stack[-1]
61
+ if name not in done:
62
+ param = self._data_source_params.get(name, self.get(name))
63
+ parent_name = param.parent_name
64
+ if parent_name is not None and parent_name not in done:
65
+ stack.append(parent_name)
66
+ continue
67
+ if isinstance(param, pc.DataSourceParameterConfig):
68
+ if name not in df_dict:
69
+ raise u.ConfigurationError(f'No reference data found for parameter "{name}"')
70
+ self._data[name] = param.convert(df_dict[name])
71
+ done.add(name)
72
+ stack.pop()
73
+
74
+ def __validate_param_relationships(self) -> None:
75
+ for param_config in self._data.values():
76
+ assert isinstance(param_config, pc.ParameterConfig)
77
+ parent_name = param_config.parent_name
78
+ parent = self.get(parent_name)
79
+ if parent:
80
+ if not isinstance(param_config, pc.SelectionParameterConfig):
81
+ if not isinstance(parent, pc.SingleSelectParameterConfig):
82
+ raise u.ConfigurationError(f'Only single-select parameters can be parents of non-select parameters. ' +
83
+ f'Parameter "{parent_name}" is the parent of non-select parameter ' +
84
+ f'"{param_config.name}" but "{parent_name}" is not a single-select parameter.')
85
+ seen = set()
86
+ for option in param_config.all_options:
87
+ lookup_keys = option._parent_option_ids
88
+ if len(option._user_groups) > 0:
89
+ lookup_keys = set((x, y) for x in option._parent_option_ids for y in option._user_groups)
90
+ if not seen.isdisjoint(lookup_keys):
91
+ raise u.ConfigurationError(f'Each distinct value of "parent option id" can only appear once (per user group)' +
92
+ f'among the options of non-select parameter "{param_config.name}".')
93
+ seen.update(lookup_keys)
94
+
95
+ if not isinstance(parent, pc.SelectionParameterConfig):
96
+ raise u.ConfigurationError(f'Only selection parameters can be parents. Parameter "{parent_name}" is the parent of ' +
97
+ f'"{param_config.name}" but "{parent_name}" is not a selection parameter.')
98
+
99
+ parent._add_child_mutate(param_config)
100
+
101
+ def _post_process_params(self, df_dict: dict[str, pd.DataFrame]) -> None:
102
+ self.__convert_datasource_params(df_dict)
103
+ self.__validate_param_relationships()
104
+
105
+ def apply_selections(
106
+ self, dataset_params: Optional[Sequence[str]], selections: dict[str, str], user: Optional[User],
107
+ *, updates_only: bool = False, request_version: Optional[int] = None
108
+ ) -> ParameterSet:
109
+ if dataset_params is None:
110
+ dataset_params = self._data.keys()
111
+
112
+ parameters_by_name: dict[str, p.Parameter] = {}
113
+ params_to_process = selections.keys() if selections and updates_only else dataset_params
114
+ params_to_process_set = set(params_to_process)
115
+ for some_name in params_to_process:
116
+ stack = [some_name] # Note: process parent selections first (if applicable) before children
117
+ while stack:
118
+ curr_name = stack[-1]
119
+ children = []
120
+ if curr_name not in parameters_by_name:
121
+ param_conf = self.get(curr_name)
122
+ parent_name = param_conf.parent_name
123
+ if parent_name is None:
124
+ parent = None
125
+ elif parent_name in params_to_process_set and parent_name not in parameters_by_name:
126
+ stack.append(parent_name)
127
+ continue
128
+ else:
129
+ parent = parameters_by_name.get(parent_name)
130
+ param = param_conf.with_selection(selections.get(curr_name), user, parent)
131
+ parameters_by_name[curr_name] = param
132
+ if isinstance(param_conf, pc.SelectionParameterConfig):
133
+ children = list(param_conf.children.keys())
134
+ stack.pop()
135
+ stack.extend(children)
136
+
137
+ ordered_parameters = OrderedDict((key, parameters_by_name[key]) for key in dataset_params if key in parameters_by_name)
138
+ return ParameterSet(ordered_parameters)
139
+
140
+
141
+ class ParameterConfigsSetIO:
142
+ """
143
+ Static class for the singleton object of __ParameterConfigsPoolData
144
+ """
145
+ args: ParametersArgs
146
+ obj: _ParameterConfigsSet
147
+
148
+ @classmethod
149
+ def _GetDfDict(cls) -> dict[str, pd.DataFrame]:
150
+ def get_dataframe_from_query(ds_param_config: pc.DataSourceParameterConfig) -> pd.DataFrame:
151
+ key, datasource = ds_param_config.name, ds_param_config.data_source
152
+ try:
153
+ query, conn_name = datasource._get_query(), datasource._get_connection_name()
154
+ df = ConnectionSetIO.obj.run_sql_query_from_conn_name(query, conn_name)
155
+ except RuntimeError as e:
156
+ raise u.ConfigurationError(f'Error executing query for datasource parameter "{key}"') from e
157
+ return key, df
158
+
159
+ ds_param_configs = cls.obj._get_all_ds_param_configs()
160
+ with concurrent.futures.ThreadPoolExecutor() as executor:
161
+ df_dict = dict(executor.map(get_dataframe_from_query, ds_param_configs))
162
+
163
+ return df_dict
164
+
165
+ @classmethod
166
+ def _AddFromDict(cls, param_config: ParametersConfig) -> None:
167
+ param_config.arguments["name"] = param_config.name
168
+ ptype = getattr(p, param_config.type)
169
+ factory = getattr(ptype, param_config.factory)
170
+ factory(**param_config.arguments)
171
+
172
+ @classmethod
173
+ def LoadFromFile(cls) -> None:
174
+ start = time.time()
175
+ cls.obj = _ParameterConfigsSet()
176
+
177
+ parameters_from_manifest = ManifestIO.obj.parameters
178
+ for param_as_dict in parameters_from_manifest:
179
+ cls._AddFromDict(param_as_dict)
180
+
181
+ conn_args = ConnectionSetIO.args
182
+ cls.args = ParametersArgs(conn_args.proj_vars, conn_args.env_vars)
183
+ pm.run_pyconfig_main(c.PARAMETERS_FILE, {"sqrl": cls.args})
184
+
185
+ df_dict = cls._GetDfDict()
186
+ cls.obj._post_process_params(df_dict)
187
+
188
+ timer.add_activity_time("loading parameters", start)
@@ -0,0 +1,60 @@
1
+ from typing import Type, Optional, Any
2
+ from types import ModuleType
3
+ from importlib.machinery import SourceFileLoader
4
+
5
+ from . import _constants as c, _utils as u
6
+
7
+
8
+ class PyModule:
9
+ def __init__(self, filepath: u.FilePath, *, default_class: Optional[Type] = None, is_required: bool = False) -> None:
10
+ """
11
+ Constructor for PyModule, an abstract module for a file that may or may not exist
12
+
13
+ Parameters:
14
+ filepath (str | pathlib.Path): The file path to the python module
15
+ is_required: If true, throw an error if the file path doesn't exist
16
+ """
17
+ self.filepath = str(filepath)
18
+ try:
19
+ self.module: Optional[ModuleType] = SourceFileLoader(self.filepath, self.filepath).load_module()
20
+ except FileNotFoundError as e:
21
+ if is_required:
22
+ raise u.ConfigurationError(f"Required file not found: '{self.filepath}'") from e
23
+ self.module: Optional[ModuleType] = default_class
24
+
25
+ def get_func_or_class(self, attr_name: str, *, default_attr: Any = None, is_required: bool = True) -> Any:
26
+ """
27
+ Get an attribute of the module. Usually a python function or class.
28
+
29
+ Parameters:
30
+ attr_name: The attribute name
31
+ default_attr: The default function or class to use if the attribute cannot be found
32
+ is_required: If true, throw an error if the attribute cannot be found, unless default_attr is not None
33
+
34
+ Returns:
35
+ The attribute of the module
36
+ """
37
+ func_or_class = default_attr
38
+ if self.module is not None and hasattr(self.module, attr_name):
39
+ func_or_class = getattr(self.module, attr_name)
40
+ if func_or_class is None and is_required:
41
+ raise u.ConfigurationError(f"Module '{self.filepath}' missing required attribute '{attr_name}'")
42
+ return func_or_class
43
+
44
+
45
+ def run_pyconfig_main(filename: str, kwargs: dict[str, Any] = {}) -> None:
46
+ """
47
+ Given a python file in the 'pyconfigs' folder, run its main function
48
+
49
+ Parameters:
50
+ filename: The name of the file to run main function
51
+ kwargs: Dictionary of the main function arguments
52
+ """
53
+ filepath = u.join_paths(c.PYCONFIG_FOLDER, filename)
54
+ module = PyModule(filepath)
55
+ main_function = module.get_func_or_class(c.MAIN_FUNC, is_required=False)
56
+ if main_function:
57
+ try:
58
+ main_function(**kwargs)
59
+ except Exception as e:
60
+ raise u.FileExecutionError(f'Failed to run python file "{filepath}"', e)
squirrels/_timer.py ADDED
@@ -0,0 +1,36 @@
1
+ from datetime import datetime
2
+ import time
3
+
4
+
5
+ class Timer:
6
+ def __init__(self):
7
+ # self.times: dict[str, list[float]] = dict()
8
+ self.verbose = False
9
+
10
+ def _get_dt_from_timestamp(self, timestamp) -> str:
11
+ return datetime.fromtimestamp(timestamp).strftime('%H:%M:%S.%f')
12
+
13
+ def add_activity_time(self, activity: str, start_timestamp: float) -> None:
14
+ if self.verbose:
15
+ end_timestamp = time.time()
16
+ time_taken = round((end_timestamp-start_timestamp) * 10**3, 3)
17
+ # times_list = self.times.setdefault(activity, list())
18
+ # times_list.append(time_taken)
19
+ print(f'Time taken for "{activity}": {time_taken}ms')
20
+
21
+ start_datetime = self._get_dt_from_timestamp(start_timestamp)
22
+ end_datetime = self._get_dt_from_timestamp(end_timestamp)
23
+ print(f'--> start time: "{start_datetime}", end time: "{end_datetime}"')
24
+ print()
25
+
26
+ # def report_times(self):
27
+ # if self.verbose:
28
+ # for activity, times_list in self.times.items():
29
+ # total_time = sum(times_list)
30
+ # avg_time = total_time / len(times_list)
31
+ # print()
32
+ # print(f'Time statistics for "{activity}":')
33
+ # print(f' Total time: {total_time}ms')
34
+ # print(f' Average time: {avg_time}ms')
35
+
36
+ timer = Timer()