squirrels 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl

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

Potentially problematic release.


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

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