squirrels 0.3.3__py3-none-any.whl → 0.4.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.

Files changed (56) hide show
  1. squirrels/__init__.py +7 -3
  2. squirrels/_api_response_models.py +96 -72
  3. squirrels/_api_server.py +375 -201
  4. squirrels/_authenticator.py +23 -22
  5. squirrels/_command_line.py +70 -46
  6. squirrels/_connection_set.py +23 -25
  7. squirrels/_constants.py +29 -78
  8. squirrels/_dashboards_io.py +61 -0
  9. squirrels/_environcfg.py +53 -50
  10. squirrels/_initializer.py +184 -141
  11. squirrels/_manifest.py +168 -195
  12. squirrels/_models.py +159 -292
  13. squirrels/_package_loader.py +7 -8
  14. squirrels/_parameter_configs.py +173 -141
  15. squirrels/_parameter_sets.py +49 -38
  16. squirrels/_py_module.py +7 -7
  17. squirrels/_seeds.py +13 -12
  18. squirrels/_utils.py +114 -54
  19. squirrels/_version.py +1 -1
  20. squirrels/arguments/init_time_args.py +16 -10
  21. squirrels/arguments/run_time_args.py +89 -24
  22. squirrels/dashboards.py +82 -0
  23. squirrels/data_sources.py +212 -232
  24. squirrels/dateutils.py +29 -26
  25. squirrels/package_data/assets/index.css +1 -1
  26. squirrels/package_data/assets/index.js +27 -18
  27. squirrels/package_data/base_project/.gitignore +2 -2
  28. squirrels/package_data/base_project/connections.yml +1 -1
  29. squirrels/package_data/base_project/dashboards/dashboard_example.py +32 -0
  30. squirrels/package_data/base_project/dashboards.yml +10 -0
  31. squirrels/package_data/base_project/docker/.dockerignore +9 -4
  32. squirrels/package_data/base_project/docker/Dockerfile +7 -6
  33. squirrels/package_data/base_project/docker/compose.yml +1 -1
  34. squirrels/package_data/base_project/env.yml +2 -2
  35. squirrels/package_data/base_project/models/dbviews/{database_view1.py → dbview_example.py} +2 -1
  36. squirrels/package_data/base_project/models/dbviews/{database_view1.sql → dbview_example.sql} +3 -2
  37. squirrels/package_data/base_project/models/federates/{dataset_example.py → federate_example.py} +6 -6
  38. squirrels/package_data/base_project/models/federates/{dataset_example.sql → federate_example.sql} +1 -1
  39. squirrels/package_data/base_project/parameters.yml +6 -4
  40. squirrels/package_data/base_project/pyconfigs/auth.py +1 -1
  41. squirrels/package_data/base_project/pyconfigs/connections.py +1 -1
  42. squirrels/package_data/base_project/pyconfigs/context.py +38 -10
  43. squirrels/package_data/base_project/pyconfigs/parameters.py +15 -7
  44. squirrels/package_data/base_project/squirrels.yml.j2 +14 -7
  45. squirrels/package_data/templates/index.html +3 -3
  46. squirrels/parameter_options.py +103 -106
  47. squirrels/parameters.py +347 -195
  48. squirrels/project.py +378 -0
  49. squirrels/user_base.py +14 -6
  50. {squirrels-0.3.3.dist-info → squirrels-0.4.0.dist-info}/METADATA +9 -21
  51. squirrels-0.4.0.dist-info/RECORD +60 -0
  52. squirrels/_timer.py +0 -23
  53. squirrels-0.3.3.dist-info/RECORD +0 -56
  54. {squirrels-0.3.3.dist-info → squirrels-0.4.0.dist-info}/LICENSE +0 -0
  55. {squirrels-0.3.3.dist-info → squirrels-0.4.0.dist-info}/WHEEL +0 -0
  56. {squirrels-0.3.3.dist-info → squirrels-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -1,18 +1,21 @@
1
1
  from __future__ import annotations
2
- from typing import Annotated, Type, Optional, Union, Sequence, Iterator, Any
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, FieldInfo
9
+ from pydantic.fields import Field
9
10
  import pandas as pd, re
10
11
 
11
- from . import parameter_options as po, parameters as p, data_sources as d, _api_response_models as arm, _utils as u, _constants as c
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: Optional[list[Any]] = None
24
- pattern: Optional[str] = None
25
- min_length: Optional[int] = None
26
- max_length: Optional[int] = None
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: Optional[str] # = field(default=None, kw_only=True)
52
- parent_name: Optional[str] # = field(default=None, kw_only=True)
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: Optional[str] = None,
57
- parent_name: Optional[str] = None
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: Optional[User]) -> Any:
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 u.ConfigurationError(f"Non-authenticated users (only allowed for public datasets) cannot use parameter " +
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
- all_options: Sequence[po.ParameterOption] = field(repr=False)
88
+ _all_options: Sequence[ParamOptionType] = field(repr=False)
86
89
 
87
90
  @abstractmethod
88
91
  def __init__(
89
- self, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *,
90
- description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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
- parent_name=parent_name)
94
- self.all_options = tuple(self.__to_param_option(x) for x in all_options)
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
- def __to_param_option(self, option: Union[po.ParameterOption, dict]) -> po.ParameterOption:
97
- return self.__class__.ParameterOption(**option) if isinstance(option, dict) else option
105
+ @staticmethod
106
+ @abstractmethod
107
+ def widget_type() -> str:
108
+ pass
98
109
 
99
110
  @staticmethod
100
111
  @abstractmethod
101
- def ParameterOption(*args, **kwargs) -> po.ParameterOption:
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 _raise_invalid_input_error(self, selection: str, more_details: str = '', e: Exception = None) -> None:
110
- raise u.InvalidInputError(f'Selected value "{selection}" is not valid for parameter "{self.name}". ' + more_details) from e
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: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
115
- *, request_version: Optional[int] = None
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(self, user: Optional[User], parent_param: Optional[p._SelectionParameter]) -> Iterator[po.ParameterOption]:
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 self.all_options if x._is_valid(user_group, selected_parent_option_ids))
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, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.SelectParameterOption, dict]], *,
141
- description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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__(widget_type, name, label, all_options, description=description, user_attribute=user_attribute,
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 po.SelectParameterOption(*args, **kwargs)
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: Optional[User], parent_param: Optional[p._SelectionParameter]) -> Sequence[po.SelectParameterOption]:
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[po.SelectParameterOption]) -> Iterator[str]:
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) -> SelectionParameterConfig:
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[Union[po.SelectParameterOption, dict]], *, description: str = "",
179
- user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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__("single_select", name, label, all_options, description=description, user_attribute=user_attribute,
182
- parent_name=parent_name)
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: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
190
- *, request_version: Optional[int] = None
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[Union[po.SelectParameterOption, dict]], *, description: str = "",
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: Optional[str] = None, parent_name: Optional[str] = None
234
+ user_attribute: str | None = None, parent_name: str | None = None
221
235
  ) -> None:
222
- super().__init__("multi_select", name, label, all_options, description=description, user_attribute=user_attribute,
223
- parent_name=parent_name)
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: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
234
- *, request_version: Optional[int] = None
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 = u.load_json_or_comma_delimited_str_as_list(selection)
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, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *,
259
- description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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__(widget_type, name, label, all_options, description=description, user_attribute=user_attribute,
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
- all_options: Sequence[po.DateParameterOption] = field(repr=False)
288
+ _all_options: Sequence[_po.DateParameterOption] = field(repr=False)
271
289
 
272
290
  def __init__(
273
- self, name: str, label: str, all_options: Sequence[Union[po.DateParameterOption, dict]], *,
274
- description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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__("date", name, label, all_options, description=description, user_attribute=user_attribute,
277
- parent_name=parent_name)
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 po.DateParameterOption(*args, **kwargs)
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: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
289
- *, request_version: Optional[int] = None
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: po.DateParameterOption = next(self._get_options_iterator(user, parent_param), None)
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
- all_options: Sequence[po.DateRangeParameterOption] = field(repr=False)
328
+ _all_options: Sequence[_po.DateRangeParameterOption] = field(repr=False)
308
329
 
309
330
  def __init__(
310
- self, name: str, label: str, all_options: Sequence[Union[po.DateRangeParameterOption, dict]], *,
311
- description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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__("date_range", name, label, all_options, description=description, user_attribute=user_attribute,
314
- parent_name=parent_name)
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 po.DateRangeParameterOption(*args, **kwargs)
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: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
326
- *, request_version: Optional[int] = None
327
- ) -> p.DateParameter:
328
- curr_option: po.DateRangeParameterOption = next(self._get_options_iterator(user, parent_param), None)
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 = u.load_json_or_comma_delimited_str_as_list(selection)
338
- except ValueError as e:
339
- self._raise_invalid_input_error(selection, "Date range parameter selection must be two dates.", e)
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, widget_type: str, name: str, label: str, all_options: Sequence[Union[po.ParameterOption, dict]], *,
358
- description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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__(widget_type, name, label, all_options, description=description, user_attribute=user_attribute,
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
- all_options: Sequence[po.NumberParameterOption] = field(repr=False)
392
+ _all_options: Sequence[_po.NumberParameterOption] = field(repr=False)
370
393
 
371
394
  def __init__(
372
- self, name: str, label: str, all_options: Sequence[Union[po.NumberParameterOption, dict]], *,
373
- description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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__("number", name, label, all_options, description=description, user_attribute=user_attribute,
376
- parent_name=parent_name)
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 po.NumberParameterOption(*args, **kwargs)
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: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
388
- *, request_version: Optional[int] = None
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: po.NumberParameterOption = next(self._get_options_iterator(user, parent_param), None)
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
- all_options: Sequence[po.NumberRangeParameterOption] = field(repr=False)
432
+ _all_options: Sequence[_po.NumberRangeParameterOption] = field(repr=False)
407
433
 
408
434
  def __init__(
409
- self, name: str, label: str, all_options: Sequence[Union[po.NumberRangeParameterOption, dict]], *,
410
- description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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__("number_range", name, label, all_options, description=description, user_attribute=user_attribute,
413
- parent_name=parent_name)
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 po.NumberRangeParameterOption(*args, **kwargs)
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: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
425
- *, request_version: Optional[int] = None
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: Optional[po.NumberRangeParameterOption] = next(self._get_options_iterator(user, parent_param), None)
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 = u.load_json_or_comma_delimited_str_as_list(selection)
437
- except ValueError as e:
438
- self._raise_invalid_input_error(selection, "Number range parameter selection must be two numbers.", e)
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
- all_options: Sequence[po.TextParameterOption] = field(repr=False)
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[Union[po.TextParameterOption, dict]], *, description: str = "",
458
- input_type: str = "text", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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__("text", name, label, all_options, description=description, user_attribute=user_attribute,
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 u.ConfigurationError(f"Invalid input type '{input_type}' for text parameter '{name}'. Must be one of {allowed_input_types}.")
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.all_options:
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 as e:
476
- raise self._raise_invalid_input_error(entered_text, "Must be an integer (without decimals).", e)
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 as e:
481
- raise self._raise_invalid_input_error(entered_text, "Must be a date in YYYY-MM-DD format.", e)
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 as e:
486
- raise self._raise_invalid_input_error(entered_text, "Must be a date in YYYY-MM-DDThh:mm format (e.g. 2020-01-01T07:00).", e)
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 as e:
491
- raise self._raise_invalid_input_error(entered_text, "Must be a date in YYYY-MM format.", e)
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 as e:
496
- raise self._raise_invalid_input_error(entered_text, "Must be a time in hh:mm format.", e)
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(r"^#[0-9a-fA-F]{6}$", entered_text):
499
- raise self._raise_invalid_input_error(entered_text, "Must be a valid color hex code (e.g. #000000).")
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 po.TextParameterOption(*args, **kwargs)
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: Optional[str], user: Optional[User], parent_param: Optional[p._SelectionParameter],
513
- *, request_version: Optional[int] = None
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: po.TextParameterOption = next(self._get_options_iterator(user, parent_param), None)
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: Union[d.DataSource, dict], *,
536
- extra_args: dict = {}, description: str = "", user_attribute: Optional[str] = None, parent_name: Optional[str] = None
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.is_from_seed():
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 u.ConfigurationError(f'Error executing query for datasource parameter "{self.name}"') from e
590
+ raise _u.ConfigurationError(f'Error executing query for datasource parameter "{self.name}"') from e
559
591
  return df