squirrels 0.3.3__py3-none-any.whl → 0.4.1__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 +7 -3
- squirrels/_api_response_models.py +96 -72
- squirrels/_api_server.py +375 -201
- squirrels/_authenticator.py +23 -22
- squirrels/_command_line.py +70 -46
- squirrels/_connection_set.py +23 -25
- squirrels/_constants.py +29 -78
- squirrels/_dashboards_io.py +61 -0
- squirrels/_environcfg.py +53 -50
- squirrels/_initializer.py +184 -141
- squirrels/_manifest.py +168 -195
- squirrels/_models.py +159 -292
- squirrels/_package_loader.py +7 -8
- squirrels/_parameter_configs.py +173 -141
- squirrels/_parameter_sets.py +49 -38
- squirrels/_py_module.py +7 -7
- squirrels/_seeds.py +13 -12
- squirrels/_utils.py +114 -54
- squirrels/_version.py +1 -1
- squirrels/arguments/init_time_args.py +16 -10
- squirrels/arguments/run_time_args.py +89 -24
- squirrels/dashboards.py +82 -0
- squirrels/data_sources.py +212 -232
- squirrels/dateutils.py +29 -26
- squirrels/package_data/assets/index.css +1 -1
- squirrels/package_data/assets/index.js +27 -18
- squirrels/package_data/base_project/.gitignore +2 -2
- squirrels/package_data/base_project/connections.yml +1 -1
- squirrels/package_data/base_project/dashboards/dashboard_example.py +32 -0
- squirrels/package_data/base_project/dashboards.yml +10 -0
- squirrels/package_data/base_project/docker/.dockerignore +9 -4
- squirrels/package_data/base_project/docker/Dockerfile +7 -6
- squirrels/package_data/base_project/docker/compose.yml +1 -1
- squirrels/package_data/base_project/env.yml +2 -2
- squirrels/package_data/base_project/models/dbviews/{database_view1.py → dbview_example.py} +2 -1
- squirrels/package_data/base_project/models/dbviews/{database_view1.sql → dbview_example.sql} +3 -2
- squirrels/package_data/base_project/models/federates/{dataset_example.py → federate_example.py} +6 -6
- squirrels/package_data/base_project/models/federates/{dataset_example.sql → federate_example.sql} +1 -1
- squirrels/package_data/base_project/parameters.yml +6 -4
- squirrels/package_data/base_project/pyconfigs/auth.py +1 -1
- squirrels/package_data/base_project/pyconfigs/connections.py +1 -1
- squirrels/package_data/base_project/pyconfigs/context.py +38 -10
- squirrels/package_data/base_project/pyconfigs/parameters.py +15 -7
- squirrels/package_data/base_project/squirrels.yml.j2 +14 -7
- squirrels/package_data/templates/index.html +3 -3
- squirrels/parameter_options.py +103 -106
- squirrels/parameters.py +347 -195
- squirrels/project.py +378 -0
- squirrels/user_base.py +14 -6
- {squirrels-0.3.3.dist-info → squirrels-0.4.1.dist-info}/METADATA +9 -21
- squirrels-0.4.1.dist-info/RECORD +60 -0
- squirrels/_timer.py +0 -23
- squirrels-0.3.3.dist-info/RECORD +0 -56
- {squirrels-0.3.3.dist-info → squirrels-0.4.1.dist-info}/LICENSE +0 -0
- {squirrels-0.3.3.dist-info → squirrels-0.4.1.dist-info}/WHEEL +0 -0
- {squirrels-0.3.3.dist-info → squirrels-0.4.1.dist-info}/entry_points.txt +0 -0
squirrels/_parameter_configs.py
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Generic, TypeVar, Annotated, Type, Sequence, Iterator, Any
|
|
3
|
+
from typing_extensions import Self
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
from abc import ABCMeta, abstractmethod
|
|
6
7
|
from copy import copy
|
|
7
8
|
from fastapi import Query
|
|
8
|
-
from pydantic.fields import Field
|
|
9
|
+
from pydantic.fields import Field
|
|
9
10
|
import pandas as pd, re
|
|
10
11
|
|
|
11
|
-
from . import parameter_options as
|
|
12
|
+
from . import parameter_options as _po, parameters as p, data_sources as d, _utils as _u, _constants as c
|
|
12
13
|
from .user_base import User
|
|
13
14
|
from ._connection_set import ConnectionSet
|
|
14
15
|
from ._seeds import Seeds
|
|
15
16
|
|
|
17
|
+
ParamOptionType = TypeVar("ParamOptionType", bound=_po.ParameterOption)
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
@dataclass
|
|
18
21
|
class APIParamFieldInfo:
|
|
@@ -20,10 +23,10 @@ class APIParamFieldInfo:
|
|
|
20
23
|
type: type
|
|
21
24
|
title: str = ""
|
|
22
25
|
description: str = ""
|
|
23
|
-
examples:
|
|
24
|
-
pattern:
|
|
25
|
-
min_length:
|
|
26
|
-
max_length:
|
|
26
|
+
examples: list[Any] | None = None
|
|
27
|
+
pattern: str | None = None
|
|
28
|
+
min_length: int | None = None
|
|
29
|
+
max_length: int | None = None
|
|
27
30
|
|
|
28
31
|
def as_query_info(self):
|
|
29
32
|
query_info = Query(
|
|
@@ -48,13 +51,13 @@ class ParameterConfigBase(metaclass=ABCMeta):
|
|
|
48
51
|
name: str
|
|
49
52
|
label: str
|
|
50
53
|
description: str # = field(default="", kw_only=True)
|
|
51
|
-
user_attribute:
|
|
52
|
-
parent_name:
|
|
54
|
+
user_attribute: str | None # = field(default=None, kw_only=True)
|
|
55
|
+
parent_name: str | None # = field(default=None, kw_only=True)
|
|
53
56
|
|
|
54
57
|
@abstractmethod
|
|
55
58
|
def __init__(
|
|
56
|
-
self, widget_type: str, name: str, label: str, *, description: str = "", user_attribute:
|
|
57
|
-
parent_name:
|
|
59
|
+
self, widget_type: str, name: str, label: str, *, description: str = "", user_attribute: str | None = None,
|
|
60
|
+
parent_name: str | None = None
|
|
58
61
|
) -> None:
|
|
59
62
|
self.widget_type = widget_type
|
|
60
63
|
self.name = name
|
|
@@ -63,10 +66,10 @@ class ParameterConfigBase(metaclass=ABCMeta):
|
|
|
63
66
|
self.user_attribute = user_attribute
|
|
64
67
|
self.parent_name = parent_name
|
|
65
68
|
|
|
66
|
-
def _get_user_group(self, user:
|
|
69
|
+
def _get_user_group(self, user: User | None) -> Any:
|
|
67
70
|
if self.user_attribute is not None:
|
|
68
71
|
if user is None:
|
|
69
|
-
raise
|
|
72
|
+
raise _u.ConfigurationError(f"Non-authenticated users (only allowed for public datasets) cannot use parameter " +
|
|
70
73
|
f"'{self.name}' because 'user_attribute' is defined on this parameter.")
|
|
71
74
|
return getattr(user, self.user_attribute)
|
|
72
75
|
|
|
@@ -78,27 +81,35 @@ class ParameterConfigBase(metaclass=ABCMeta):
|
|
|
78
81
|
|
|
79
82
|
|
|
80
83
|
@dataclass
|
|
81
|
-
class ParameterConfig(ParameterConfigBase):
|
|
84
|
+
class ParameterConfig(Generic[ParamOptionType], ParameterConfigBase):
|
|
82
85
|
"""
|
|
83
86
|
Abstract class for all parameter classes (except DataSourceParameters)
|
|
84
87
|
"""
|
|
85
|
-
|
|
88
|
+
_all_options: Sequence[ParamOptionType] = field(repr=False)
|
|
86
89
|
|
|
87
90
|
@abstractmethod
|
|
88
91
|
def __init__(
|
|
89
|
-
self,
|
|
90
|
-
|
|
92
|
+
self, name: str, label: str, all_options: Sequence[ParamOptionType | dict], *, description: str = "",
|
|
93
|
+
user_attribute: str | None = None, parent_name: str | None = None
|
|
91
94
|
) -> None:
|
|
92
|
-
super().__init__(widget_type, name, label, description=description, user_attribute=user_attribute,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
super().__init__(self.widget_type(), name, label, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
96
|
+
self._all_options = tuple(self._to_param_option(x) for x in all_options)
|
|
97
|
+
|
|
98
|
+
def _to_param_option(self, option: ParamOptionType | dict) -> ParamOptionType:
|
|
99
|
+
return self.ParameterOption(**option) if isinstance(option, dict) else option
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def all_options(self) -> Sequence[ParamOptionType]:
|
|
103
|
+
return self._all_options
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
@staticmethod
|
|
106
|
+
@abstractmethod
|
|
107
|
+
def widget_type() -> str:
|
|
108
|
+
pass
|
|
98
109
|
|
|
99
110
|
@staticmethod
|
|
100
111
|
@abstractmethod
|
|
101
|
-
def ParameterOption(*args, **kwargs) ->
|
|
112
|
+
def ParameterOption(*args, **kwargs) -> ParamOptionType:
|
|
102
113
|
pass
|
|
103
114
|
|
|
104
115
|
@staticmethod
|
|
@@ -106,20 +117,22 @@ class ParameterConfig(ParameterConfigBase):
|
|
|
106
117
|
def DataSource(*args, **kwargs) -> d.DataSource:
|
|
107
118
|
pass
|
|
108
119
|
|
|
109
|
-
def
|
|
110
|
-
|
|
120
|
+
def _invalid_input_error(self, selection: str, more_details: str = '') -> _u.InvalidInputError:
|
|
121
|
+
return _u.InvalidInputError(f'Selected value "{selection}" is not valid for parameter "{self.name}". ' + more_details)
|
|
111
122
|
|
|
112
123
|
@abstractmethod
|
|
113
124
|
def with_selection(
|
|
114
|
-
self, selection:
|
|
115
|
-
*, request_version:
|
|
125
|
+
self, selection: str | None, user: User | None, parent_param: p._SelectionParameter | None,
|
|
126
|
+
*, request_version: int | None = None
|
|
116
127
|
) -> p.Parameter:
|
|
117
128
|
pass
|
|
118
129
|
|
|
119
|
-
def _get_options_iterator(
|
|
130
|
+
def _get_options_iterator(
|
|
131
|
+
self, all_options: Sequence[ParamOptionType], user: User | None, parent_param: p._SelectionParameter | None
|
|
132
|
+
) -> Iterator[ParamOptionType]:
|
|
120
133
|
user_group = self._get_user_group(user)
|
|
121
134
|
selected_parent_option_ids = frozenset(parent_param._get_selected_ids_as_list()) if parent_param else None
|
|
122
|
-
return (x for x in
|
|
135
|
+
return (x for x in all_options if x._is_valid(user_group, selected_parent_option_ids))
|
|
123
136
|
|
|
124
137
|
@abstractmethod
|
|
125
138
|
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
@@ -127,39 +140,37 @@ class ParameterConfig(ParameterConfigBase):
|
|
|
127
140
|
|
|
128
141
|
|
|
129
142
|
@dataclass
|
|
130
|
-
class SelectionParameterConfig(ParameterConfig):
|
|
143
|
+
class SelectionParameterConfig(ParameterConfig[_po.SelectParameterOption]):
|
|
131
144
|
"""
|
|
132
145
|
Abstract class for select parameter classes (single-select, multi-select, etc)
|
|
133
146
|
"""
|
|
134
|
-
all_options: Sequence[po.SelectParameterOption] = field(repr=False)
|
|
135
147
|
children: dict[str, ParameterConfigBase] = field(default_factory=dict, init=False, repr=False)
|
|
136
148
|
trigger_refresh: bool = field(default=False, init=False)
|
|
137
149
|
|
|
138
150
|
@abstractmethod
|
|
139
151
|
def __init__(
|
|
140
|
-
self,
|
|
141
|
-
description: str = "", user_attribute:
|
|
152
|
+
self, name: str, label: str, all_options: Sequence[_po.SelectParameterOption | dict], *,
|
|
153
|
+
description: str = "", user_attribute: str | None = None, parent_name: str | None = None
|
|
142
154
|
) -> None:
|
|
143
|
-
super().__init__(
|
|
144
|
-
parent_name=parent_name)
|
|
155
|
+
super().__init__(name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
145
156
|
self.children: dict[str, ParameterConfigBase] = dict()
|
|
146
157
|
self.trigger_refresh = False
|
|
147
158
|
|
|
148
159
|
@staticmethod
|
|
149
160
|
def ParameterOption(*args, **kwargs):
|
|
150
|
-
return
|
|
161
|
+
return _po.SelectParameterOption(*args, **kwargs)
|
|
151
162
|
|
|
152
163
|
def _add_child_mutate(self, child: ParameterConfigBase):
|
|
153
164
|
self.children[child.name] = child
|
|
154
165
|
self.trigger_refresh = True
|
|
155
166
|
|
|
156
|
-
def _get_options(self, user:
|
|
157
|
-
return tuple(self._get_options_iterator(user, parent_param))
|
|
167
|
+
def _get_options(self, user: User | None, parent_param: p._SelectionParameter | None) -> Sequence[_po.SelectParameterOption]:
|
|
168
|
+
return tuple(self._get_options_iterator(self.all_options, user, parent_param))
|
|
158
169
|
|
|
159
|
-
def _get_default_ids_iterator(self, options: Sequence[
|
|
170
|
+
def _get_default_ids_iterator(self, options: Sequence[_po.SelectParameterOption]) -> Iterator[str]:
|
|
160
171
|
return (x._identifier for x in options if x._is_default)
|
|
161
172
|
|
|
162
|
-
def copy(self) ->
|
|
173
|
+
def copy(self) -> Self:
|
|
163
174
|
"""
|
|
164
175
|
Use for unit testing only
|
|
165
176
|
"""
|
|
@@ -175,19 +186,22 @@ class SingleSelectParameterConfig(SelectionParameterConfig):
|
|
|
175
186
|
"""
|
|
176
187
|
|
|
177
188
|
def __init__(
|
|
178
|
-
self, name: str, label: str, all_options: Sequence[
|
|
179
|
-
user_attribute:
|
|
189
|
+
self, name: str, label: str, all_options: Sequence[_po.SelectParameterOption | dict], *, description: str = "",
|
|
190
|
+
user_attribute: str | None = None, parent_name: str | None = None
|
|
180
191
|
) -> None:
|
|
181
|
-
super().__init__(
|
|
182
|
-
|
|
192
|
+
super().__init__(name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def widget_type() -> str:
|
|
196
|
+
return "single_select"
|
|
183
197
|
|
|
184
198
|
@staticmethod
|
|
185
199
|
def DataSource(*args, **kwargs):
|
|
186
200
|
return d.SelectDataSource(*args, **kwargs)
|
|
187
201
|
|
|
188
202
|
def with_selection(
|
|
189
|
-
self, selection:
|
|
190
|
-
*, request_version:
|
|
203
|
+
self, selection: str | None, user: User | None, parent_param: p._SelectionParameter | None,
|
|
204
|
+
*, request_version: int | None = None
|
|
191
205
|
) -> p.SingleSelectParameter:
|
|
192
206
|
options = self._get_options(user, parent_param)
|
|
193
207
|
if selection is None:
|
|
@@ -215,29 +229,34 @@ class MultiSelectParameterConfig(SelectionParameterConfig):
|
|
|
215
229
|
none_is_all: bool # = field(default=True, kw_only=True)
|
|
216
230
|
|
|
217
231
|
def __init__(
|
|
218
|
-
self, name: str, label: str, all_options: Sequence[
|
|
232
|
+
self, name: str, label: str, all_options: Sequence[_po.SelectParameterOption | dict], *, description: str = "",
|
|
219
233
|
show_select_all: bool = True, order_matters: bool = False, none_is_all: bool = True,
|
|
220
|
-
user_attribute:
|
|
234
|
+
user_attribute: str | None = None, parent_name: str | None = None
|
|
221
235
|
) -> None:
|
|
222
|
-
super().__init__(
|
|
223
|
-
|
|
236
|
+
super().__init__(
|
|
237
|
+
name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name
|
|
238
|
+
)
|
|
224
239
|
self.show_select_all = show_select_all
|
|
225
240
|
self.order_matters = order_matters
|
|
226
241
|
self.none_is_all = none_is_all
|
|
227
242
|
|
|
243
|
+
@staticmethod
|
|
244
|
+
def widget_type() -> str:
|
|
245
|
+
return "multi_select"
|
|
246
|
+
|
|
228
247
|
@staticmethod
|
|
229
248
|
def DataSource(*args, **kwargs):
|
|
230
249
|
return d.SelectDataSource(*args, **kwargs)
|
|
231
250
|
|
|
232
251
|
def with_selection(
|
|
233
|
-
self, selection:
|
|
234
|
-
*, request_version:
|
|
252
|
+
self, selection: str | None, user: User | None, parent_param: p._SelectionParameter | None,
|
|
253
|
+
*, request_version: int | None = None
|
|
235
254
|
) -> p.MultiSelectParameter:
|
|
236
255
|
options = self._get_options(user, parent_param)
|
|
237
256
|
if selection is None:
|
|
238
257
|
selected_ids = tuple(self._get_default_ids_iterator(options))
|
|
239
258
|
else:
|
|
240
|
-
selected_ids =
|
|
259
|
+
selected_ids = _u.load_json_or_comma_delimited_str_as_list(selection)
|
|
241
260
|
return p.MultiSelectParameter(self, options, selected_ids)
|
|
242
261
|
|
|
243
262
|
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
@@ -248,47 +267,49 @@ class MultiSelectParameterConfig(SelectionParameterConfig):
|
|
|
248
267
|
|
|
249
268
|
|
|
250
269
|
@dataclass
|
|
251
|
-
class _DateTypeParameterConfig(ParameterConfig):
|
|
270
|
+
class _DateTypeParameterConfig(ParameterConfig[ParamOptionType]):
|
|
252
271
|
"""
|
|
253
272
|
Abstract class for date and date range parameter configs
|
|
254
273
|
"""
|
|
255
274
|
|
|
256
275
|
@abstractmethod
|
|
257
276
|
def __init__(
|
|
258
|
-
self,
|
|
259
|
-
|
|
277
|
+
self, name: str, label: str, all_options: Sequence[ParamOptionType | dict], *, description: str = "",
|
|
278
|
+
user_attribute: str | None = None, parent_name: str | None = None
|
|
260
279
|
) -> None:
|
|
261
|
-
super().__init__(
|
|
262
|
-
parent_name=parent_name)
|
|
280
|
+
super().__init__(name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
263
281
|
|
|
264
282
|
|
|
265
283
|
@dataclass
|
|
266
|
-
class DateParameterConfig(_DateTypeParameterConfig):
|
|
284
|
+
class DateParameterConfig(_DateTypeParameterConfig[_po.DateParameterOption]):
|
|
267
285
|
"""
|
|
268
286
|
Class to define configurations for date parameter widgets.
|
|
269
287
|
"""
|
|
270
|
-
|
|
288
|
+
_all_options: Sequence[_po.DateParameterOption] = field(repr=False)
|
|
271
289
|
|
|
272
290
|
def __init__(
|
|
273
|
-
self, name: str, label: str, all_options: Sequence[
|
|
274
|
-
description: str = "", user_attribute:
|
|
291
|
+
self, name: str, label: str, all_options: Sequence[_po.DateParameterOption | dict], *,
|
|
292
|
+
description: str = "", user_attribute: str | None = None, parent_name: str | None = None
|
|
275
293
|
) -> None:
|
|
276
|
-
super().__init__(
|
|
277
|
-
|
|
294
|
+
super().__init__(name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
295
|
+
|
|
296
|
+
@staticmethod
|
|
297
|
+
def widget_type() -> str:
|
|
298
|
+
return "date"
|
|
278
299
|
|
|
279
300
|
@staticmethod
|
|
280
301
|
def ParameterOption(*args, **kwargs):
|
|
281
|
-
return
|
|
302
|
+
return _po.DateParameterOption(*args, **kwargs)
|
|
282
303
|
|
|
283
304
|
@staticmethod
|
|
284
305
|
def DataSource(*args, **kwargs):
|
|
285
306
|
return d.DateDataSource(*args, **kwargs)
|
|
286
307
|
|
|
287
308
|
def with_selection(
|
|
288
|
-
self, selection:
|
|
289
|
-
*, request_version:
|
|
309
|
+
self, selection: str | None, user: User | None, parent_param: p._SelectionParameter | None,
|
|
310
|
+
*, request_version: int | None = None
|
|
290
311
|
) -> p.DateParameter:
|
|
291
|
-
curr_option:
|
|
312
|
+
curr_option: _po.DateParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
|
|
292
313
|
selected_date = curr_option._default_date if selection is None and curr_option is not None else selection
|
|
293
314
|
return p.DateParameter(self, curr_option, selected_date)
|
|
294
315
|
|
|
@@ -300,32 +321,35 @@ class DateParameterConfig(_DateTypeParameterConfig):
|
|
|
300
321
|
|
|
301
322
|
|
|
302
323
|
@dataclass
|
|
303
|
-
class DateRangeParameterConfig(_DateTypeParameterConfig):
|
|
324
|
+
class DateRangeParameterConfig(_DateTypeParameterConfig[_po.DateRangeParameterOption]):
|
|
304
325
|
"""
|
|
305
326
|
Class to define configurations for date range parameter widgets.
|
|
306
327
|
"""
|
|
307
|
-
|
|
328
|
+
_all_options: Sequence[_po.DateRangeParameterOption] = field(repr=False)
|
|
308
329
|
|
|
309
330
|
def __init__(
|
|
310
|
-
self, name: str, label: str, all_options: Sequence[
|
|
311
|
-
description: str = "", user_attribute:
|
|
331
|
+
self, name: str, label: str, all_options: Sequence[_po.DateRangeParameterOption | dict], *,
|
|
332
|
+
description: str = "", user_attribute: str | None = None, parent_name: str | None = None
|
|
312
333
|
) -> None:
|
|
313
|
-
super().__init__(
|
|
314
|
-
|
|
334
|
+
super().__init__(name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
335
|
+
|
|
336
|
+
@staticmethod
|
|
337
|
+
def widget_type() -> str:
|
|
338
|
+
return "date_range"
|
|
315
339
|
|
|
316
340
|
@staticmethod
|
|
317
341
|
def ParameterOption(*args, **kwargs):
|
|
318
|
-
return
|
|
342
|
+
return _po.DateRangeParameterOption(*args, **kwargs)
|
|
319
343
|
|
|
320
344
|
@staticmethod
|
|
321
345
|
def DataSource(*args, **kwargs):
|
|
322
346
|
return d.DateRangeDataSource(*args, **kwargs)
|
|
323
347
|
|
|
324
348
|
def with_selection(
|
|
325
|
-
self, selection:
|
|
326
|
-
*, request_version:
|
|
327
|
-
) -> p.
|
|
328
|
-
curr_option:
|
|
349
|
+
self, selection: str | None, user: User | None, parent_param: p._SelectionParameter | None,
|
|
350
|
+
*, request_version: int | None = None
|
|
351
|
+
) -> p.DateRangeParameter:
|
|
352
|
+
curr_option: _po.DateRangeParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
|
|
329
353
|
if selection is None:
|
|
330
354
|
if curr_option is not None:
|
|
331
355
|
selected_start_date = curr_option._default_start_date
|
|
@@ -334,9 +358,9 @@ class DateRangeParameterConfig(_DateTypeParameterConfig):
|
|
|
334
358
|
selected_start_date, selected_end_date = None, None
|
|
335
359
|
else:
|
|
336
360
|
try:
|
|
337
|
-
selected_start_date, selected_end_date =
|
|
338
|
-
except ValueError
|
|
339
|
-
self.
|
|
361
|
+
selected_start_date, selected_end_date = _u.load_json_or_comma_delimited_str_as_list(selection)
|
|
362
|
+
except ValueError:
|
|
363
|
+
raise self._invalid_input_error(selection, "Date range parameter selection must be two dates.")
|
|
340
364
|
return p.DateRangeParameter(self, curr_option, selected_start_date, selected_end_date)
|
|
341
365
|
|
|
342
366
|
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
@@ -347,47 +371,49 @@ class DateRangeParameterConfig(_DateTypeParameterConfig):
|
|
|
347
371
|
|
|
348
372
|
|
|
349
373
|
@dataclass
|
|
350
|
-
class _NumericParameterConfig(ParameterConfig):
|
|
374
|
+
class _NumericParameterConfig(ParameterConfig[ParamOptionType]):
|
|
351
375
|
"""
|
|
352
376
|
Abstract class for number and number range parameter configs
|
|
353
377
|
"""
|
|
354
378
|
|
|
355
379
|
@abstractmethod
|
|
356
380
|
def __init__(
|
|
357
|
-
self,
|
|
358
|
-
|
|
381
|
+
self, name: str, label: str, all_options: Sequence[ParamOptionType | dict], *, description: str = "",
|
|
382
|
+
user_attribute: str | None = None, parent_name: str | None = None
|
|
359
383
|
) -> None:
|
|
360
|
-
super().__init__(
|
|
361
|
-
parent_name=parent_name)
|
|
384
|
+
super().__init__(name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
362
385
|
|
|
363
386
|
|
|
364
387
|
@dataclass
|
|
365
|
-
class NumberParameterConfig(_NumericParameterConfig):
|
|
388
|
+
class NumberParameterConfig(_NumericParameterConfig[_po.NumberParameterOption]):
|
|
366
389
|
"""
|
|
367
390
|
Class to define configurations for number parameter widgets.
|
|
368
391
|
"""
|
|
369
|
-
|
|
392
|
+
_all_options: Sequence[_po.NumberParameterOption] = field(repr=False)
|
|
370
393
|
|
|
371
394
|
def __init__(
|
|
372
|
-
self, name: str, label: str, all_options: Sequence[
|
|
373
|
-
description: str = "", user_attribute:
|
|
395
|
+
self, name: str, label: str, all_options: Sequence[_po.NumberParameterOption | dict], *,
|
|
396
|
+
description: str = "", user_attribute: str | None = None, parent_name: str | None = None
|
|
374
397
|
) -> None:
|
|
375
|
-
super().__init__(
|
|
376
|
-
|
|
377
|
-
|
|
398
|
+
super().__init__(name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
399
|
+
|
|
400
|
+
@staticmethod
|
|
401
|
+
def widget_type() -> str:
|
|
402
|
+
return "number"
|
|
403
|
+
|
|
378
404
|
@staticmethod
|
|
379
405
|
def ParameterOption(*args, **kwargs):
|
|
380
|
-
return
|
|
406
|
+
return _po.NumberParameterOption(*args, **kwargs)
|
|
381
407
|
|
|
382
408
|
@staticmethod
|
|
383
409
|
def DataSource(*args, **kwargs):
|
|
384
410
|
return d.NumberDataSource(*args, **kwargs)
|
|
385
411
|
|
|
386
412
|
def with_selection(
|
|
387
|
-
self, selection:
|
|
388
|
-
*, request_version:
|
|
413
|
+
self, selection: str | None, user: User | None, parent_param: p._SelectionParameter | None,
|
|
414
|
+
*, request_version: int | None = None
|
|
389
415
|
) -> p.NumberParameter:
|
|
390
|
-
curr_option:
|
|
416
|
+
curr_option: _po.NumberParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
|
|
391
417
|
selected_value = curr_option._default_value if selection is None and curr_option is not None else selection
|
|
392
418
|
return p.NumberParameter(self, curr_option, selected_value)
|
|
393
419
|
|
|
@@ -399,32 +425,35 @@ class NumberParameterConfig(_NumericParameterConfig):
|
|
|
399
425
|
|
|
400
426
|
|
|
401
427
|
@dataclass
|
|
402
|
-
class NumberRangeParameterConfig(_NumericParameterConfig):
|
|
428
|
+
class NumberRangeParameterConfig(_NumericParameterConfig[_po.NumberRangeParameterOption]):
|
|
403
429
|
"""
|
|
404
430
|
Class to define configurations for number range parameter widgets.
|
|
405
431
|
"""
|
|
406
|
-
|
|
432
|
+
_all_options: Sequence[_po.NumberRangeParameterOption] = field(repr=False)
|
|
407
433
|
|
|
408
434
|
def __init__(
|
|
409
|
-
self, name: str, label: str, all_options: Sequence[
|
|
410
|
-
description: str = "", user_attribute:
|
|
435
|
+
self, name: str, label: str, all_options: Sequence[_po.NumberRangeParameterOption | dict], *,
|
|
436
|
+
description: str = "", user_attribute: str | None = None, parent_name: str | None = None
|
|
411
437
|
) -> None:
|
|
412
|
-
super().__init__(
|
|
413
|
-
|
|
438
|
+
super().__init__(name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
439
|
+
|
|
440
|
+
@staticmethod
|
|
441
|
+
def widget_type() -> str:
|
|
442
|
+
return "number_range"
|
|
414
443
|
|
|
415
444
|
@staticmethod
|
|
416
445
|
def ParameterOption(*args, **kwargs):
|
|
417
|
-
return
|
|
446
|
+
return _po.NumberRangeParameterOption(*args, **kwargs)
|
|
418
447
|
|
|
419
448
|
@staticmethod
|
|
420
449
|
def DataSource(*args, **kwargs):
|
|
421
450
|
return d.NumberRangeDataSource(*args, **kwargs)
|
|
422
451
|
|
|
423
452
|
def with_selection(
|
|
424
|
-
self, selection:
|
|
425
|
-
*, request_version:
|
|
453
|
+
self, selection: str | None, user: User | None, parent_param: p._SelectionParameter | None,
|
|
454
|
+
*, request_version: int | None = None
|
|
426
455
|
) -> p.NumberRangeParameter:
|
|
427
|
-
curr_option:
|
|
456
|
+
curr_option: _po.NumberRangeParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
|
|
428
457
|
if selection is None:
|
|
429
458
|
if curr_option is not None:
|
|
430
459
|
selected_lower_value = curr_option._default_lower_value
|
|
@@ -433,9 +462,9 @@ class NumberRangeParameterConfig(_NumericParameterConfig):
|
|
|
433
462
|
selected_lower_value, selected_upper_value = None, None
|
|
434
463
|
else:
|
|
435
464
|
try:
|
|
436
|
-
selected_lower_value, selected_upper_value =
|
|
437
|
-
except ValueError
|
|
438
|
-
self.
|
|
465
|
+
selected_lower_value, selected_upper_value = _u.load_json_or_comma_delimited_str_as_list(selection)
|
|
466
|
+
except ValueError:
|
|
467
|
+
raise self._invalid_input_error(selection, "Number range parameter selection must be two numbers.")
|
|
439
468
|
return p.NumberRangeParameter(self, curr_option, selected_lower_value, selected_upper_value)
|
|
440
469
|
|
|
441
470
|
def get_api_field_info(self) -> APIParamFieldInfo:
|
|
@@ -446,73 +475,76 @@ class NumberRangeParameterConfig(_NumericParameterConfig):
|
|
|
446
475
|
|
|
447
476
|
|
|
448
477
|
@dataclass
|
|
449
|
-
class TextParameterConfig(ParameterConfig):
|
|
478
|
+
class TextParameterConfig(ParameterConfig[_po.TextParameterOption]):
|
|
450
479
|
"""
|
|
451
480
|
Class to define configurations for text parameter widgets.
|
|
452
481
|
"""
|
|
453
|
-
|
|
482
|
+
_all_options: Sequence[_po.TextParameterOption] = field(repr=False)
|
|
454
483
|
input_type: str
|
|
455
484
|
|
|
456
485
|
def __init__(
|
|
457
|
-
self, name: str, label: str, all_options: Sequence[
|
|
458
|
-
input_type: str = "text", user_attribute:
|
|
486
|
+
self, name: str, label: str, all_options: Sequence[_po.TextParameterOption | dict], *, description: str = "",
|
|
487
|
+
input_type: str = "text", user_attribute: str | None = None, parent_name: str | None = None
|
|
459
488
|
) -> None:
|
|
460
|
-
super().__init__(
|
|
461
|
-
parent_name=parent_name)
|
|
489
|
+
super().__init__(name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
462
490
|
|
|
463
491
|
allowed_input_types = ["text", "textarea", "number", "date", "datetime-local", "month", "time", "color", "password"]
|
|
464
492
|
if input_type not in allowed_input_types:
|
|
465
|
-
raise
|
|
493
|
+
raise _u.ConfigurationError(f"Invalid input type '{input_type}' for text parameter '{name}'. Must be one of {allowed_input_types}.")
|
|
466
494
|
|
|
467
495
|
self.input_type = input_type
|
|
468
|
-
for option in self.
|
|
496
|
+
for option in self._all_options:
|
|
469
497
|
self.validate_entered_text(option._default_text)
|
|
470
498
|
|
|
471
499
|
def validate_entered_text(self, entered_text: str) -> str:
|
|
472
500
|
if self.input_type == "number":
|
|
473
501
|
try:
|
|
474
502
|
int(entered_text)
|
|
475
|
-
except ValueError
|
|
476
|
-
raise self.
|
|
503
|
+
except ValueError:
|
|
504
|
+
raise self._invalid_input_error(entered_text, "Must be an integer (without decimals)")
|
|
477
505
|
elif self.input_type == "date":
|
|
478
506
|
try:
|
|
479
507
|
datetime.strptime(entered_text, "%Y-%m-%d")
|
|
480
|
-
except ValueError
|
|
481
|
-
raise self.
|
|
508
|
+
except ValueError:
|
|
509
|
+
raise self._invalid_input_error(entered_text, "Must be a date in YYYY-MM-DD format")
|
|
482
510
|
elif self.input_type == "datetime-local":
|
|
483
511
|
try:
|
|
484
512
|
datetime.strptime(entered_text, "%Y-%m-%dT%H:%M")
|
|
485
|
-
except ValueError
|
|
486
|
-
raise self.
|
|
513
|
+
except ValueError:
|
|
514
|
+
raise self._invalid_input_error(entered_text, "Must be a date in YYYY-MM-DDThh:mm format (e.g. 2020-01-01T07:00)")
|
|
487
515
|
elif self.input_type == "month":
|
|
488
516
|
try:
|
|
489
517
|
datetime.strptime(entered_text, "%Y-%m")
|
|
490
|
-
except ValueError
|
|
491
|
-
raise self.
|
|
518
|
+
except ValueError:
|
|
519
|
+
raise self._invalid_input_error(entered_text, "Must be a date in YYYY-MM format")
|
|
492
520
|
elif self.input_type == "time":
|
|
493
521
|
try:
|
|
494
522
|
datetime.strptime(entered_text, "%H:%M")
|
|
495
|
-
except ValueError
|
|
496
|
-
raise self.
|
|
523
|
+
except ValueError:
|
|
524
|
+
raise self._invalid_input_error(entered_text, "Must be a time in hh:mm format.")
|
|
497
525
|
elif self.input_type == "color":
|
|
498
|
-
if not re.match(
|
|
499
|
-
raise self.
|
|
526
|
+
if not re.match(c.color_regex, entered_text):
|
|
527
|
+
raise self._invalid_input_error(entered_text, "Must be a valid color hex code (e.g. #000000).")
|
|
500
528
|
|
|
501
529
|
return entered_text
|
|
530
|
+
|
|
531
|
+
@staticmethod
|
|
532
|
+
def widget_type() -> str:
|
|
533
|
+
return "text"
|
|
502
534
|
|
|
503
535
|
@staticmethod
|
|
504
536
|
def ParameterOption(*args, **kwargs):
|
|
505
|
-
return
|
|
537
|
+
return _po.TextParameterOption(*args, **kwargs)
|
|
506
538
|
|
|
507
539
|
@staticmethod
|
|
508
540
|
def DataSource(*args, **kwargs):
|
|
509
541
|
return d.TextDataSource(*args, **kwargs)
|
|
510
542
|
|
|
511
543
|
def with_selection(
|
|
512
|
-
self, selection:
|
|
513
|
-
*, request_version:
|
|
544
|
+
self, selection: str | None, user: User | None, parent_param: p._SelectionParameter | None,
|
|
545
|
+
*, request_version: int | None = None
|
|
514
546
|
) -> p.TextParameter:
|
|
515
|
-
curr_option:
|
|
547
|
+
curr_option: _po.TextParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
|
|
516
548
|
entered_text = curr_option._default_text if selection is None and curr_option is not None else selection
|
|
517
549
|
return p.TextParameter(self, curr_option, entered_text)
|
|
518
550
|
|
|
@@ -532,8 +564,8 @@ class DataSourceParameterConfig(ParameterConfigBase):
|
|
|
532
564
|
data_source: d.DataSource
|
|
533
565
|
|
|
534
566
|
def __init__(
|
|
535
|
-
self, parameter_type: Type[ParameterConfig], name: str, label: str, data_source:
|
|
536
|
-
extra_args: dict = {}, description: str = "", user_attribute:
|
|
567
|
+
self, parameter_type: Type[ParameterConfig], name: str, label: str, data_source: d.DataSource | dict, *,
|
|
568
|
+
extra_args: dict = {}, description: str = "", user_attribute: str | None = None, parent_name: str | None = None
|
|
537
569
|
) -> None:
|
|
538
570
|
super().__init__("data_source", name, label, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
539
571
|
self.parameter_type = parameter_type
|
|
@@ -545,15 +577,15 @@ class DataSourceParameterConfig(ParameterConfigBase):
|
|
|
545
577
|
def convert(self, df: pd.DataFrame) -> ParameterConfig:
|
|
546
578
|
return self.data_source._convert(self, df)
|
|
547
579
|
|
|
548
|
-
def get_dataframe(self, conn_set: ConnectionSet, seeds: Seeds) -> pd.DataFrame:
|
|
580
|
+
def get_dataframe(self, default_conn_name: str, conn_set: ConnectionSet, seeds: Seeds) -> pd.DataFrame:
|
|
549
581
|
datasource = self.data_source
|
|
550
582
|
query = datasource._get_query()
|
|
551
|
-
if datasource.
|
|
583
|
+
if datasource._is_from_seeds:
|
|
552
584
|
df = seeds.run_query(query)
|
|
553
585
|
else:
|
|
554
586
|
try:
|
|
555
|
-
conn_name = datasource._get_connection_name()
|
|
587
|
+
conn_name = datasource._get_connection_name(default_conn_name)
|
|
556
588
|
df = conn_set.run_sql_query_from_conn_name(query, conn_name)
|
|
557
589
|
except RuntimeError as e:
|
|
558
|
-
raise
|
|
590
|
+
raise _u.ConfigurationError(f'Error executing query for datasource parameter "{self.name}"') from e
|
|
559
591
|
return df
|