squirrels 0.2.2__py3-none-any.whl → 0.3.0__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.
- squirrels/__init__.py +11 -4
- squirrels/_api_response_models.py +118 -0
- squirrels/_api_server.py +140 -75
- squirrels/_authenticator.py +10 -8
- squirrels/_command_line.py +17 -11
- squirrels/_connection_set.py +2 -2
- squirrels/_constants.py +13 -5
- squirrels/_initializer.py +23 -13
- squirrels/_manifest.py +20 -10
- squirrels/_models.py +295 -142
- squirrels/_parameter_configs.py +195 -57
- squirrels/_parameter_sets.py +14 -17
- squirrels/_py_module.py +2 -4
- squirrels/_seeds.py +38 -0
- squirrels/_utils.py +41 -33
- squirrels/arguments/run_time_args.py +76 -34
- squirrels/data_sources.py +172 -51
- squirrels/dateutils.py +3 -3
- squirrels/package_data/assets/index.js +14 -14
- squirrels/package_data/base_project/connections.yml +1 -1
- squirrels/package_data/base_project/database/expenses.db +0 -0
- squirrels/package_data/base_project/docker/Dockerfile +1 -1
- squirrels/package_data/base_project/environcfg.yml +7 -7
- squirrels/package_data/base_project/models/dbviews/database_view1.py +25 -14
- squirrels/package_data/base_project/models/dbviews/database_view1.sql +21 -14
- squirrels/package_data/base_project/models/federates/dataset_example.py +6 -5
- squirrels/package_data/base_project/models/federates/dataset_example.sql +1 -1
- squirrels/package_data/base_project/parameters.yml +57 -28
- squirrels/package_data/base_project/pyconfigs/auth.py +11 -10
- squirrels/package_data/base_project/pyconfigs/connections.py +6 -8
- squirrels/package_data/base_project/pyconfigs/context.py +49 -33
- squirrels/package_data/base_project/pyconfigs/parameters.py +62 -30
- squirrels/package_data/base_project/seeds/seed_categories.csv +6 -0
- squirrels/package_data/base_project/seeds/seed_subcategories.csv +15 -0
- squirrels/package_data/base_project/squirrels.yml.j2 +37 -20
- squirrels/parameter_options.py +30 -10
- squirrels/parameters.py +300 -70
- squirrels/user_base.py +3 -13
- squirrels-0.3.0.dist-info/LICENSE +201 -0
- {squirrels-0.2.2.dist-info → squirrels-0.3.0.dist-info}/METADATA +15 -15
- squirrels-0.3.0.dist-info/RECORD +56 -0
- squirrels/package_data/base_project/seeds/mocks/category.csv +0 -3
- squirrels/package_data/base_project/seeds/mocks/max_filter.csv +0 -2
- squirrels/package_data/base_project/seeds/mocks/subcategory.csv +0 -6
- squirrels-0.2.2.dist-info/LICENSE +0 -22
- squirrels-0.2.2.dist-info/RECORD +0 -55
- {squirrels-0.2.2.dist-info → squirrels-0.3.0.dist-info}/WHEEL +0 -0
- {squirrels-0.2.2.dist-info → squirrels-0.3.0.dist-info}/entry_points.txt +0 -0
squirrels/_parameter_configs.py
CHANGED
|
@@ -1,12 +1,42 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import Type, Optional, Union, Sequence, Iterator, Any
|
|
2
|
+
from typing import Annotated, Type, Optional, Union, Sequence, Iterator, Any
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from abc import ABCMeta, abstractmethod
|
|
5
5
|
from copy import copy
|
|
6
|
+
from fastapi import Query
|
|
7
|
+
from pydantic.fields import Field, FieldInfo
|
|
6
8
|
import pandas as pd
|
|
7
9
|
|
|
8
|
-
from . import parameter_options as po, parameters as p, data_sources as d, _utils as u
|
|
10
|
+
from . import parameter_options as po, parameters as p, data_sources as d, _api_response_models as arm, _utils as u, _constants as c
|
|
9
11
|
from .user_base import User
|
|
12
|
+
from ._connection_set import ConnectionSet
|
|
13
|
+
from ._seeds import Seeds
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class APIParamFieldInfo:
|
|
18
|
+
name: str
|
|
19
|
+
type: type
|
|
20
|
+
title: str = ""
|
|
21
|
+
description: str = ""
|
|
22
|
+
examples: Optional[list[Any]] = None
|
|
23
|
+
pattern: Optional[str] = None
|
|
24
|
+
min_length: Optional[int] = None
|
|
25
|
+
max_length: Optional[int] = None
|
|
26
|
+
|
|
27
|
+
def as_query_info(self):
|
|
28
|
+
query_info = Query(
|
|
29
|
+
title=self.title, description=self.description, examples=self.examples, pattern=self.pattern,
|
|
30
|
+
min_length=self.min_length, max_length=self.max_length
|
|
31
|
+
)
|
|
32
|
+
return (self.name, Annotated[self.type, query_info], None)
|
|
33
|
+
|
|
34
|
+
def as_body_info(self):
|
|
35
|
+
field_info = Field(None,
|
|
36
|
+
title=self.title, description=self.description, examples=self.examples, pattern=self.pattern,
|
|
37
|
+
min_length=self.min_length, max_length=self.max_length
|
|
38
|
+
)
|
|
39
|
+
return (self.type, field_info)
|
|
10
40
|
|
|
11
41
|
|
|
12
42
|
@dataclass
|
|
@@ -16,26 +46,26 @@ class ParameterConfigBase(metaclass=ABCMeta):
|
|
|
16
46
|
"""
|
|
17
47
|
name: str
|
|
18
48
|
label: str
|
|
19
|
-
|
|
49
|
+
description: str # = field(default="", kw_only=True)
|
|
20
50
|
user_attribute: Optional[str] # = field(default=None, kw_only=True)
|
|
21
51
|
parent_name: Optional[str] # = field(default=None, kw_only=True)
|
|
22
52
|
|
|
23
53
|
@abstractmethod
|
|
24
54
|
def __init__(
|
|
25
|
-
self, widget_type: str, name: str, label: str, *,
|
|
55
|
+
self, widget_type: str, name: str, label: str, *, description: str = "", user_attribute: Optional[str] = None,
|
|
26
56
|
parent_name: Optional[str] = None
|
|
27
57
|
) -> None:
|
|
28
58
|
self.widget_type = widget_type
|
|
29
59
|
self.name = name
|
|
30
60
|
self.label = label
|
|
31
|
-
self.
|
|
61
|
+
self.description = description
|
|
32
62
|
self.user_attribute = user_attribute
|
|
33
63
|
self.parent_name = parent_name
|
|
34
64
|
|
|
35
65
|
def _get_user_group(self, user: Optional[User]) -> Any:
|
|
36
66
|
if self.user_attribute is not None:
|
|
37
67
|
if user is None:
|
|
38
|
-
raise u.ConfigurationError(f"
|
|
68
|
+
raise u.ConfigurationError(f"Non-authenticated users (only allowed for public datasets) cannot use parameter " +
|
|
39
69
|
f"'{self.name}' because 'user_attribute' is defined on this parameter.")
|
|
40
70
|
return getattr(user, self.user_attribute)
|
|
41
71
|
|
|
@@ -45,11 +75,9 @@ class ParameterConfigBase(metaclass=ABCMeta):
|
|
|
45
75
|
"""
|
|
46
76
|
return copy(self)
|
|
47
77
|
|
|
48
|
-
def to_json_dict0(self) ->
|
|
78
|
+
def to_json_dict0(self) -> arm.ParameterModelBase:
|
|
49
79
|
return {
|
|
50
|
-
|
|
51
|
-
'name': self.name,
|
|
52
|
-
'label': self.label
|
|
80
|
+
"widget_type": self.widget_type, "name": self.name, "label": self.label, "description": self.description
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
|
|
@@ -62,10 +90,10 @@ class ParameterConfig(ParameterConfigBase):
|
|
|
62
90
|
|
|
63
91
|
@abstractmethod
|
|
64
92
|
def __init__(
|
|
65
|
-
self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *,
|
|
66
|
-
user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
93
|
+
self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *,
|
|
94
|
+
description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
67
95
|
) -> None:
|
|
68
|
-
super().__init__(widget_type, name, label,
|
|
96
|
+
super().__init__(widget_type, name, label, description=description, user_attribute=user_attribute,
|
|
69
97
|
parent_name=parent_name)
|
|
70
98
|
self.all_options = tuple(self.__to_param_option(x) for x in all_options)
|
|
71
99
|
|
|
@@ -96,6 +124,10 @@ class ParameterConfig(ParameterConfigBase):
|
|
|
96
124
|
user_group = self._get_user_group(user)
|
|
97
125
|
selected_parent_option_ids = frozenset(parent_param._get_selected_ids_as_list()) if parent_param else None
|
|
98
126
|
return (x for x in self.all_options if x._is_valid(user_group, selected_parent_option_ids))
|
|
127
|
+
|
|
128
|
+
@abstractmethod
|
|
129
|
+
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
130
|
+
pass
|
|
99
131
|
|
|
100
132
|
|
|
101
133
|
@dataclass
|
|
@@ -103,15 +135,16 @@ class SelectionParameterConfig(ParameterConfig):
|
|
|
103
135
|
"""
|
|
104
136
|
Abstract class for select parameter classes (single-select, multi-select, etc)
|
|
105
137
|
"""
|
|
138
|
+
all_options: Sequence[po.SelectParameterOption] = field(repr=False)
|
|
106
139
|
children: dict[str, ParameterConfigBase] = field(default_factory=dict, init=False, repr=False)
|
|
107
140
|
trigger_refresh: bool = field(default=False, init=False)
|
|
108
141
|
|
|
109
142
|
@abstractmethod
|
|
110
143
|
def __init__(
|
|
111
|
-
self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.SelectParameterOption, dict]], *,
|
|
112
|
-
user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
144
|
+
self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.SelectParameterOption, dict]], *,
|
|
145
|
+
description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
113
146
|
) -> None:
|
|
114
|
-
super().__init__(widget_type, name, label, all_options,
|
|
147
|
+
super().__init__(widget_type, name, label, all_options, description=description, user_attribute=user_attribute,
|
|
115
148
|
parent_name=parent_name)
|
|
116
149
|
self.children: dict[str, ParameterConfigBase] = dict()
|
|
117
150
|
self.trigger_refresh = False
|
|
@@ -151,15 +184,15 @@ class SingleSelectParameterConfig(SelectionParameterConfig):
|
|
|
151
184
|
"""
|
|
152
185
|
|
|
153
186
|
def __init__(
|
|
154
|
-
self, name: str, label: str, all_options: Sequence[Union[po.SelectParameterOption, dict]], *,
|
|
187
|
+
self, name: str, label: str, all_options: Sequence[Union[po.SelectParameterOption, dict]], *, description: str = "",
|
|
155
188
|
user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
156
189
|
) -> None:
|
|
157
|
-
super().__init__("single_select", name, label, all_options,
|
|
190
|
+
super().__init__("single_select", name, label, all_options, description=description, user_attribute=user_attribute,
|
|
158
191
|
parent_name=parent_name)
|
|
159
192
|
|
|
160
193
|
@staticmethod
|
|
161
194
|
def DataSource(*args, **kwargs):
|
|
162
|
-
return d.
|
|
195
|
+
return d.SelectDataSource(*args, **kwargs)
|
|
163
196
|
|
|
164
197
|
def with_selection(
|
|
165
198
|
self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
|
|
@@ -173,6 +206,12 @@ class SingleSelectParameterConfig(SelectionParameterConfig):
|
|
|
173
206
|
else:
|
|
174
207
|
selected_id = selection
|
|
175
208
|
return p.SingleSelectParameter(self, options, selected_id)
|
|
209
|
+
|
|
210
|
+
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
211
|
+
examples = [x._identifier for x in self.all_options]
|
|
212
|
+
return APIParamFieldInfo(
|
|
213
|
+
self.name, str, title=self.label, description=self.description, examples=examples
|
|
214
|
+
)
|
|
176
215
|
|
|
177
216
|
|
|
178
217
|
@dataclass
|
|
@@ -186,11 +225,11 @@ class MultiSelectParameterConfig(SelectionParameterConfig):
|
|
|
186
225
|
none_is_all: bool # = field(default=True, kw_only=True)
|
|
187
226
|
|
|
188
227
|
def __init__(
|
|
189
|
-
self, name: str, label: str, all_options: Sequence[Union[po.SelectParameterOption, dict]], *,
|
|
190
|
-
|
|
228
|
+
self, name: str, label: str, all_options: Sequence[Union[po.SelectParameterOption, dict]], *, description: str = "",
|
|
229
|
+
show_select_all: bool = True, is_dropdown: bool = True, order_matters: bool = False, none_is_all: bool = True,
|
|
191
230
|
user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
192
231
|
) -> None:
|
|
193
|
-
super().__init__("multi_select", name, label, all_options,
|
|
232
|
+
super().__init__("multi_select", name, label, all_options, description=description, user_attribute=user_attribute,
|
|
194
233
|
parent_name=parent_name)
|
|
195
234
|
self.show_select_all = show_select_all
|
|
196
235
|
self.is_dropdown = is_dropdown
|
|
@@ -199,7 +238,7 @@ class MultiSelectParameterConfig(SelectionParameterConfig):
|
|
|
199
238
|
|
|
200
239
|
@staticmethod
|
|
201
240
|
def DataSource(*args, **kwargs):
|
|
202
|
-
return d.
|
|
241
|
+
return d.SelectDataSource(*args, **kwargs)
|
|
203
242
|
|
|
204
243
|
def with_selection(
|
|
205
244
|
self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
|
|
@@ -218,6 +257,12 @@ class MultiSelectParameterConfig(SelectionParameterConfig):
|
|
|
218
257
|
output['is_dropdown'] = self.is_dropdown
|
|
219
258
|
output['order_matters'] = self.order_matters
|
|
220
259
|
return output
|
|
260
|
+
|
|
261
|
+
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
262
|
+
identifiers = [x._identifier for x in self.all_options]
|
|
263
|
+
return APIParamFieldInfo(
|
|
264
|
+
self.name, list[str], title=self.label, description=self.description, examples=[identifiers]
|
|
265
|
+
)
|
|
221
266
|
|
|
222
267
|
|
|
223
268
|
@dataclass
|
|
@@ -228,24 +273,25 @@ class _DateTypeParameterConfig(ParameterConfig):
|
|
|
228
273
|
|
|
229
274
|
@abstractmethod
|
|
230
275
|
def __init__(
|
|
231
|
-
self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *,
|
|
232
|
-
user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
276
|
+
self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *,
|
|
277
|
+
description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
233
278
|
) -> None:
|
|
234
|
-
super().__init__(widget_type, name, label, all_options,
|
|
279
|
+
super().__init__(widget_type, name, label, all_options, description=description, user_attribute=user_attribute,
|
|
235
280
|
parent_name=parent_name)
|
|
236
281
|
|
|
237
282
|
|
|
238
283
|
@dataclass
|
|
239
284
|
class DateParameterConfig(_DateTypeParameterConfig):
|
|
240
285
|
"""
|
|
241
|
-
Class to define configurations for
|
|
286
|
+
Class to define configurations for date parameter widgets.
|
|
242
287
|
"""
|
|
288
|
+
all_options: Sequence[po.DateParameterOption] = field(repr=False)
|
|
243
289
|
|
|
244
290
|
def __init__(
|
|
245
|
-
self, name: str, label: str, all_options: Sequence[Union[po.DateParameterOption, dict]], *,
|
|
246
|
-
user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
291
|
+
self, name: str, label: str, all_options: Sequence[Union[po.DateParameterOption, dict]], *,
|
|
292
|
+
description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
247
293
|
) -> None:
|
|
248
|
-
super().__init__("date", name, label, all_options,
|
|
294
|
+
super().__init__("date", name, label, all_options, description=description, user_attribute=user_attribute,
|
|
249
295
|
parent_name=parent_name)
|
|
250
296
|
|
|
251
297
|
@staticmethod
|
|
@@ -260,22 +306,29 @@ class DateParameterConfig(_DateTypeParameterConfig):
|
|
|
260
306
|
self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
|
|
261
307
|
*, request_version: Optional[int] = None
|
|
262
308
|
) -> p.DateParameter:
|
|
263
|
-
curr_option: po.DateParameterOption = next(self._get_options_iterator(user, parent_param))
|
|
264
|
-
selected_date = curr_option._default_date if selection is None else selection
|
|
309
|
+
curr_option: po.DateParameterOption = next(self._get_options_iterator(user, parent_param), None)
|
|
310
|
+
selected_date = curr_option._default_date if selection is None and curr_option is not None else selection
|
|
265
311
|
return p.DateParameter(self, curr_option, selected_date)
|
|
312
|
+
|
|
313
|
+
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
314
|
+
examples = [str(x._default_date) for x in self.all_options]
|
|
315
|
+
return APIParamFieldInfo(
|
|
316
|
+
self.name, str, title=self.label, description=self.description, examples=examples, pattern=c.date_regex
|
|
317
|
+
)
|
|
266
318
|
|
|
267
319
|
|
|
268
320
|
@dataclass
|
|
269
321
|
class DateRangeParameterConfig(_DateTypeParameterConfig):
|
|
270
322
|
"""
|
|
271
|
-
Class to define configurations for
|
|
323
|
+
Class to define configurations for date range parameter widgets.
|
|
272
324
|
"""
|
|
325
|
+
all_options: Sequence[po.DateRangeParameterOption] = field(repr=False)
|
|
273
326
|
|
|
274
327
|
def __init__(
|
|
275
|
-
self, name: str, label: str, all_options: Sequence[Union[po.DateRangeParameterOption, dict]], *,
|
|
276
|
-
user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
328
|
+
self, name: str, label: str, all_options: Sequence[Union[po.DateRangeParameterOption, dict]], *,
|
|
329
|
+
description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
277
330
|
) -> None:
|
|
278
|
-
super().__init__("date_range", name, label, all_options,
|
|
331
|
+
super().__init__("date_range", name, label, all_options, description=description, user_attribute=user_attribute,
|
|
279
332
|
parent_name=parent_name)
|
|
280
333
|
|
|
281
334
|
@staticmethod
|
|
@@ -290,16 +343,25 @@ class DateRangeParameterConfig(_DateTypeParameterConfig):
|
|
|
290
343
|
self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
|
|
291
344
|
*, request_version: Optional[int] = None
|
|
292
345
|
) -> p.DateParameter:
|
|
293
|
-
curr_option: po.DateRangeParameterOption = next(self._get_options_iterator(user, parent_param))
|
|
346
|
+
curr_option: po.DateRangeParameterOption = next(self._get_options_iterator(user, parent_param), None)
|
|
294
347
|
if selection is None:
|
|
295
|
-
|
|
296
|
-
|
|
348
|
+
if curr_option is not None:
|
|
349
|
+
selected_start_date = curr_option._default_start_date
|
|
350
|
+
selected_end_date = curr_option._default_end_date
|
|
351
|
+
else:
|
|
352
|
+
selected_start_date, selected_end_date = None, None
|
|
297
353
|
else:
|
|
298
354
|
try:
|
|
299
355
|
selected_start_date, selected_end_date = u.load_json_or_comma_delimited_str_as_list(selection)
|
|
300
356
|
except ValueError as e:
|
|
301
357
|
self._raise_invalid_input_error(selection, "Date range parameter selection must be two dates joined by comma.", e)
|
|
302
358
|
return p.DateRangeParameter(self, curr_option, selected_start_date, selected_end_date)
|
|
359
|
+
|
|
360
|
+
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
361
|
+
examples = [[str(x._default_start_date), str(x._default_end_date)] for x in self.all_options]
|
|
362
|
+
return APIParamFieldInfo(
|
|
363
|
+
self.name, list[str], title=self.label, description=self.description, examples=examples, max_length=2
|
|
364
|
+
)
|
|
303
365
|
|
|
304
366
|
|
|
305
367
|
@dataclass
|
|
@@ -310,24 +372,25 @@ class _NumericParameterConfig(ParameterConfig):
|
|
|
310
372
|
|
|
311
373
|
@abstractmethod
|
|
312
374
|
def __init__(
|
|
313
|
-
self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *,
|
|
314
|
-
user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
375
|
+
self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *,
|
|
376
|
+
description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
315
377
|
) -> None:
|
|
316
|
-
super().__init__(widget_type, name, label, all_options,
|
|
378
|
+
super().__init__(widget_type, name, label, all_options, description=description, user_attribute=user_attribute,
|
|
317
379
|
parent_name=parent_name)
|
|
318
380
|
|
|
319
381
|
|
|
320
382
|
@dataclass
|
|
321
383
|
class NumberParameterConfig(_NumericParameterConfig):
|
|
322
384
|
"""
|
|
323
|
-
Class to define configurations for
|
|
385
|
+
Class to define configurations for number parameter widgets.
|
|
324
386
|
"""
|
|
387
|
+
all_options: Sequence[po.NumberParameterOption] = field(repr=False)
|
|
325
388
|
|
|
326
389
|
def __init__(
|
|
327
|
-
self, name: str, label: str, all_options: Sequence[Union[po.NumberParameterOption, dict]], *,
|
|
328
|
-
user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
390
|
+
self, name: str, label: str, all_options: Sequence[Union[po.NumberParameterOption, dict]], *,
|
|
391
|
+
description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
329
392
|
) -> None:
|
|
330
|
-
super().__init__("number", name, label, all_options,
|
|
393
|
+
super().__init__("number", name, label, all_options, description=description, user_attribute=user_attribute,
|
|
331
394
|
parent_name=parent_name)
|
|
332
395
|
|
|
333
396
|
@staticmethod
|
|
@@ -342,22 +405,29 @@ class NumberParameterConfig(_NumericParameterConfig):
|
|
|
342
405
|
self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
|
|
343
406
|
*, request_version: Optional[int] = None
|
|
344
407
|
) -> p.NumberParameter:
|
|
345
|
-
curr_option: po.NumberParameterOption = next(self._get_options_iterator(user, parent_param))
|
|
346
|
-
selected_value = curr_option._default_value if selection is None else selection
|
|
408
|
+
curr_option: po.NumberParameterOption = next(self._get_options_iterator(user, parent_param), None)
|
|
409
|
+
selected_value = curr_option._default_value if selection is None and curr_option is not None else selection
|
|
347
410
|
return p.NumberParameter(self, curr_option, selected_value)
|
|
411
|
+
|
|
412
|
+
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
413
|
+
examples = [x._default_value for x in self.all_options]
|
|
414
|
+
return APIParamFieldInfo(
|
|
415
|
+
self.name, float, title=self.label, description=self.description, examples=examples
|
|
416
|
+
)
|
|
348
417
|
|
|
349
418
|
|
|
350
419
|
@dataclass
|
|
351
420
|
class NumberRangeParameterConfig(_NumericParameterConfig):
|
|
352
421
|
"""
|
|
353
|
-
Class to define configurations for
|
|
422
|
+
Class to define configurations for number range parameter widgets.
|
|
354
423
|
"""
|
|
424
|
+
all_options: Sequence[po.NumberRangeParameterOption] = field(repr=False)
|
|
355
425
|
|
|
356
426
|
def __init__(
|
|
357
|
-
self, name: str, label: str, all_options: Sequence[Union[po.NumberRangeParameterOption, dict]], *,
|
|
358
|
-
user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
427
|
+
self, name: str, label: str, all_options: Sequence[Union[po.NumberRangeParameterOption, dict]], *,
|
|
428
|
+
description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
359
429
|
) -> None:
|
|
360
|
-
super().__init__("number_range", name, label, all_options,
|
|
430
|
+
super().__init__("number_range", name, label, all_options, description=description, user_attribute=user_attribute,
|
|
361
431
|
parent_name=parent_name)
|
|
362
432
|
|
|
363
433
|
@staticmethod
|
|
@@ -372,16 +442,70 @@ class NumberRangeParameterConfig(_NumericParameterConfig):
|
|
|
372
442
|
self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
|
|
373
443
|
*, request_version: Optional[int] = None
|
|
374
444
|
) -> p.NumberRangeParameter:
|
|
375
|
-
curr_option: po.NumberRangeParameterOption = next(self._get_options_iterator(user, parent_param))
|
|
445
|
+
curr_option: po.NumberRangeParameterOption = next(self._get_options_iterator(user, parent_param), None)
|
|
376
446
|
if selection is None:
|
|
377
|
-
|
|
378
|
-
|
|
447
|
+
if curr_option is not None:
|
|
448
|
+
selected_lower_value = curr_option._default_lower_value
|
|
449
|
+
selected_upper_value = curr_option._default_upper_value
|
|
450
|
+
else:
|
|
451
|
+
selected_lower_value, selected_upper_value = None, None
|
|
379
452
|
else:
|
|
380
453
|
try:
|
|
381
454
|
selected_lower_value, selected_upper_value = u.load_json_or_comma_delimited_str_as_list(selection)
|
|
382
455
|
except ValueError as e:
|
|
383
456
|
self._raise_invalid_input_error(selection, "Number range parameter selection must be two numbers joined by comma.", e)
|
|
384
457
|
return p.NumberRangeParameter(self, curr_option, selected_lower_value, selected_upper_value)
|
|
458
|
+
|
|
459
|
+
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
460
|
+
examples = [[x._default_lower_value, x._default_upper_value] for x in self.all_options]
|
|
461
|
+
return APIParamFieldInfo(
|
|
462
|
+
self.name, list[str], title=self.label, description=self.description, examples=examples, max_length=2
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
@dataclass
|
|
467
|
+
class TextParameterConfig(ParameterConfig):
|
|
468
|
+
"""
|
|
469
|
+
Class to define configurations for text parameter widgets.
|
|
470
|
+
"""
|
|
471
|
+
all_options: Sequence[po.TextParameterOption] = field(repr=False)
|
|
472
|
+
is_textarea: bool
|
|
473
|
+
|
|
474
|
+
def __init__(
|
|
475
|
+
self, name: str, label: str, all_options: Sequence[Union[po.TextParameterOption, dict]], *,
|
|
476
|
+
description: str = "", is_textarea: bool = False, user_attribute: Optional[str] = None,
|
|
477
|
+
parent_name: Optional[str] = None
|
|
478
|
+
) -> None:
|
|
479
|
+
super().__init__("text", name, label, all_options, description=description, user_attribute=user_attribute,
|
|
480
|
+
parent_name=parent_name)
|
|
481
|
+
self.is_textarea = is_textarea
|
|
482
|
+
|
|
483
|
+
@staticmethod
|
|
484
|
+
def ParameterOption(*args, **kwargs):
|
|
485
|
+
return po.TextParameterOption(*args, **kwargs)
|
|
486
|
+
|
|
487
|
+
@staticmethod
|
|
488
|
+
def DataSource(*args, **kwargs):
|
|
489
|
+
return d.TextDataSource(*args, **kwargs)
|
|
490
|
+
|
|
491
|
+
def with_selection(
|
|
492
|
+
self, selection: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
|
|
493
|
+
*, request_version: Optional[int] = None
|
|
494
|
+
) -> p.TextParameter:
|
|
495
|
+
curr_option: po.TextParameterOption = next(self._get_options_iterator(user, parent_param), None)
|
|
496
|
+
entered_text = curr_option._default_text if selection is None and curr_option is not None else selection
|
|
497
|
+
return p.TextParameter(self, curr_option, entered_text)
|
|
498
|
+
|
|
499
|
+
def to_json_dict0(self) -> dict:
|
|
500
|
+
output = super().to_json_dict0()
|
|
501
|
+
output['is_textarea'] = self.is_textarea
|
|
502
|
+
return output
|
|
503
|
+
|
|
504
|
+
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
505
|
+
examples = [x._default_text for x in self.all_options]
|
|
506
|
+
return APIParamFieldInfo(
|
|
507
|
+
self.name, str, title=self.label, description=self.description, examples=examples
|
|
508
|
+
)
|
|
385
509
|
|
|
386
510
|
|
|
387
511
|
@dataclass
|
|
@@ -394,13 +518,27 @@ class DataSourceParameterConfig(ParameterConfigBase):
|
|
|
394
518
|
|
|
395
519
|
def __init__(
|
|
396
520
|
self, parameter_type: Type[ParameterConfig], name: str, label: str, data_source: Union[d.DataSource, dict], *,
|
|
397
|
-
|
|
521
|
+
extra_args: dict = {}, description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
|
|
398
522
|
) -> None:
|
|
399
|
-
super().__init__("data_source", name, label,
|
|
523
|
+
super().__init__("data_source", name, label, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
400
524
|
self.parameter_type = parameter_type
|
|
401
525
|
if isinstance(data_source, dict):
|
|
402
526
|
data_source = parameter_type.DataSource(**data_source)
|
|
403
527
|
self.data_source = data_source
|
|
528
|
+
self.extra_args = extra_args
|
|
404
529
|
|
|
405
530
|
def convert(self, df: pd.DataFrame) -> ParameterConfig:
|
|
406
531
|
return self.data_source._convert(self, df)
|
|
532
|
+
|
|
533
|
+
def get_dataframe(self, conn_set: ConnectionSet, seeds: Seeds) -> pd.DataFrame:
|
|
534
|
+
datasource = self.data_source
|
|
535
|
+
query = datasource._get_query()
|
|
536
|
+
if datasource.is_from_seed():
|
|
537
|
+
df = seeds.run_query(query)
|
|
538
|
+
else:
|
|
539
|
+
try:
|
|
540
|
+
conn_name = datasource._get_connection_name()
|
|
541
|
+
df = conn_set.run_sql_query_from_conn_name(query, conn_name)
|
|
542
|
+
except RuntimeError as e:
|
|
543
|
+
raise u.ConfigurationError(f'Error executing query for datasource parameter "{self.name}"') from e
|
|
544
|
+
return df
|
squirrels/_parameter_sets.py
CHANGED
|
@@ -4,10 +4,11 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from collections import OrderedDict
|
|
5
5
|
import concurrent.futures, pandas as pd
|
|
6
6
|
|
|
7
|
-
from . import _utils as u, _constants as c, parameters as p, _parameter_configs as pc, _py_module as pm
|
|
7
|
+
from . import _utils as u, _constants as c, parameters as p, _parameter_configs as pc, _py_module as pm, _api_response_models as arm
|
|
8
8
|
from .arguments.init_time_args import ParametersArgs
|
|
9
9
|
from ._manifest import ManifestIO, ParametersConfig
|
|
10
10
|
from ._connection_set import ConnectionSetIO
|
|
11
|
+
from ._seeds import SeedsIO
|
|
11
12
|
from .user_base import User
|
|
12
13
|
from ._timer import timer, time
|
|
13
14
|
|
|
@@ -22,12 +23,11 @@ class ParameterSet:
|
|
|
22
23
|
def get_parameters_as_dict(self) -> dict[str, p.Parameter]:
|
|
23
24
|
return self._parameters_dict.copy()
|
|
24
25
|
|
|
25
|
-
def
|
|
26
|
+
def to_api_response_model0(self) -> arm.ParametersModel:
|
|
26
27
|
parameters = []
|
|
27
28
|
for x in self._parameters_dict.values():
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return {"parameters": parameters}
|
|
29
|
+
parameters.append(x._to_api_response_model0())
|
|
30
|
+
return arm.ParametersModel(parameters=parameters)
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
@dataclass
|
|
@@ -130,12 +130,15 @@ class _ParameterConfigsSet:
|
|
|
130
130
|
param = param_conf.with_selection(selections.get(curr_name), user, parent)
|
|
131
131
|
parameters_by_name[curr_name] = param
|
|
132
132
|
if isinstance(param_conf, pc.SelectionParameterConfig):
|
|
133
|
-
children = list(param_conf.children.keys())
|
|
133
|
+
children = list(x for x in param_conf.children.keys() if x in dataset_params)
|
|
134
134
|
stack.pop()
|
|
135
135
|
stack.extend(children)
|
|
136
136
|
|
|
137
137
|
ordered_parameters = OrderedDict((key, parameters_by_name[key]) for key in dataset_params if key in parameters_by_name)
|
|
138
138
|
return ParameterSet(ordered_parameters)
|
|
139
|
+
|
|
140
|
+
def get_all_api_field_info(self) -> dict[str, pc.APIParamFieldInfo]:
|
|
141
|
+
return {param: config.get_api_field_info() for param, config in self._data.items()}
|
|
139
142
|
|
|
140
143
|
|
|
141
144
|
class ParameterConfigsSetIO:
|
|
@@ -146,19 +149,13 @@ class ParameterConfigsSetIO:
|
|
|
146
149
|
obj: _ParameterConfigsSet
|
|
147
150
|
|
|
148
151
|
@classmethod
|
|
149
|
-
def
|
|
150
|
-
def
|
|
151
|
-
|
|
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
|
|
152
|
+
def _GetDfDictFromDataSources(cls) -> dict[str, pd.DataFrame]:
|
|
153
|
+
def get_dataframe(ds_param_config: pc.DataSourceParameterConfig) -> tuple[str, pd.DataFrame]:
|
|
154
|
+
return ds_param_config.name, ds_param_config.get_dataframe(ConnectionSetIO.obj, SeedsIO.obj)
|
|
158
155
|
|
|
159
156
|
ds_param_configs = cls.obj._get_all_ds_param_configs()
|
|
160
157
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
161
|
-
df_dict = dict(executor.map(
|
|
158
|
+
df_dict = dict(executor.map(get_dataframe, ds_param_configs))
|
|
162
159
|
|
|
163
160
|
return df_dict
|
|
164
161
|
|
|
@@ -180,7 +177,7 @@ class ParameterConfigsSetIO:
|
|
|
180
177
|
cls.args = ParametersArgs(conn_args.proj_vars, conn_args.env_vars)
|
|
181
178
|
pm.run_pyconfig_main(c.PARAMETERS_FILE, {"sqrl": cls.args})
|
|
182
179
|
|
|
183
|
-
df_dict = cls.
|
|
180
|
+
df_dict = cls._GetDfDictFromDataSources()
|
|
184
181
|
cls.obj._post_process_params(df_dict)
|
|
185
182
|
|
|
186
183
|
timer.add_activity_time("loading parameters", start)
|
squirrels/_py_module.py
CHANGED
|
@@ -36,9 +36,7 @@ class PyModule:
|
|
|
36
36
|
Returns:
|
|
37
37
|
The attribute of the module
|
|
38
38
|
"""
|
|
39
|
-
func_or_class = default_attr
|
|
40
|
-
if self.module is not None and hasattr(self.module, attr_name):
|
|
41
|
-
func_or_class = getattr(self.module, attr_name)
|
|
39
|
+
func_or_class = getattr(self.module, attr_name, default_attr)
|
|
42
40
|
if func_or_class is None and is_required:
|
|
43
41
|
raise u.ConfigurationError(f"Module '{self.filepath}' missing required attribute '{attr_name}'")
|
|
44
42
|
return func_or_class
|
|
@@ -52,7 +50,7 @@ def run_pyconfig_main(filename: str, kwargs: dict[str, Any] = {}) -> None:
|
|
|
52
50
|
filename: The name of the file to run main function
|
|
53
51
|
kwargs: Dictionary of the main function arguments
|
|
54
52
|
"""
|
|
55
|
-
filepath = u.join_paths(c.
|
|
53
|
+
filepath = u.join_paths(c.PYCONFIGS_FOLDER, filename)
|
|
56
54
|
module = PyModule(filepath)
|
|
57
55
|
main_function = module.get_func_or_class(c.MAIN_FUNC, is_required=False)
|
|
58
56
|
if main_function:
|
squirrels/_seeds.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import os, glob, pandas as pd
|
|
3
|
+
|
|
4
|
+
from ._timer import timer, time
|
|
5
|
+
from ._manifest import ManifestIO
|
|
6
|
+
from . import _utils as u, _constants as c
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Seeds:
|
|
11
|
+
_data: dict[str, pd.DataFrame]
|
|
12
|
+
|
|
13
|
+
def run_query(self, sql_query: str) -> pd.DataFrame:
|
|
14
|
+
return u.run_sql_on_dataframes(sql_query, self._data)
|
|
15
|
+
|
|
16
|
+
def get_dataframes(self) -> dict[str, pd.DataFrame]:
|
|
17
|
+
return self._data.copy()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SeedsIO:
|
|
21
|
+
obj: Seeds
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def LoadFiles(cls) -> None:
|
|
25
|
+
start = time.time()
|
|
26
|
+
infer_schema: bool = ManifestIO.obj.settings.get(c.SEEDS_INFER_SCHEMA_SETTING, True)
|
|
27
|
+
na_values: list[str] = ManifestIO.obj.settings.get(c.SEEDS_NA_VALUES_SETTING, ["NA"])
|
|
28
|
+
csv_dtype = None if infer_schema else str
|
|
29
|
+
|
|
30
|
+
seeds_dict = {}
|
|
31
|
+
csv_files = glob.glob(os.path.join(c.SEEDS_FOLDER, '**/*.csv'), recursive=True)
|
|
32
|
+
for csv_file in csv_files:
|
|
33
|
+
file_stem = os.path.splitext(os.path.basename(csv_file))[0]
|
|
34
|
+
df = pd.read_csv(csv_file, dtype=csv_dtype, keep_default_na=False, na_values=na_values)
|
|
35
|
+
seeds_dict[file_stem] = df
|
|
36
|
+
|
|
37
|
+
cls.obj = Seeds(seeds_dict)
|
|
38
|
+
timer.add_activity_time("loading seed files", start)
|