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