squirrels 0.5.0rc0__py3-none-any.whl → 0.5.1__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (108) 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 +10 -12
  6. squirrels/_api_routes/__init__.py +5 -0
  7. squirrels/_api_routes/auth.py +271 -0
  8. squirrels/_api_routes/base.py +171 -0
  9. squirrels/_api_routes/dashboards.py +158 -0
  10. squirrels/_api_routes/data_management.py +148 -0
  11. squirrels/_api_routes/datasets.py +265 -0
  12. squirrels/_api_routes/oauth2.py +298 -0
  13. squirrels/_api_routes/project.py +252 -0
  14. squirrels/_api_server.py +245 -781
  15. squirrels/_arguments/__init__.py +0 -0
  16. squirrels/{arguments → _arguments}/init_time_args.py +7 -2
  17. squirrels/{arguments → _arguments}/run_time_args.py +13 -35
  18. squirrels/_auth.py +720 -212
  19. squirrels/_command_line.py +81 -41
  20. squirrels/_compile_prompts.py +147 -0
  21. squirrels/_connection_set.py +16 -7
  22. squirrels/_constants.py +29 -9
  23. squirrels/{_dashboards_io.py → _dashboards.py} +87 -6
  24. squirrels/_data_sources.py +570 -0
  25. squirrels/{dataset_result.py → _dataset_types.py} +2 -4
  26. squirrels/_exceptions.py +9 -37
  27. squirrels/_initializer.py +83 -59
  28. squirrels/_logging.py +117 -0
  29. squirrels/_manifest.py +129 -62
  30. squirrels/_model_builder.py +10 -52
  31. squirrels/_model_configs.py +3 -3
  32. squirrels/_model_queries.py +1 -1
  33. squirrels/_models.py +249 -118
  34. squirrels/{package_data → _package_data}/base_project/.env +16 -4
  35. squirrels/{package_data → _package_data}/base_project/.env.example +15 -3
  36. squirrels/{package_data → _package_data}/base_project/connections.yml +4 -3
  37. squirrels/{package_data → _package_data}/base_project/dashboards/dashboard_example.py +4 -4
  38. squirrels/_package_data/base_project/dashboards/dashboard_example.yml +22 -0
  39. squirrels/{package_data → _package_data}/base_project/duckdb_init.sql +1 -0
  40. squirrels/_package_data/base_project/macros/macros_example.sql +17 -0
  41. squirrels/{package_data → _package_data}/base_project/models/builds/build_example.py +2 -2
  42. squirrels/{package_data → _package_data}/base_project/models/builds/build_example.sql +1 -1
  43. squirrels/{package_data → _package_data}/base_project/models/builds/build_example.yml +2 -0
  44. squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +17 -0
  45. squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +32 -0
  46. squirrels/_package_data/base_project/models/federates/federate_example.py +48 -0
  47. squirrels/_package_data/base_project/models/federates/federate_example.sql +21 -0
  48. squirrels/{package_data → _package_data}/base_project/models/federates/federate_example.yml +7 -7
  49. squirrels/{package_data → _package_data}/base_project/models/sources.yml +5 -6
  50. squirrels/{package_data → _package_data}/base_project/parameters.yml +32 -45
  51. squirrels/_package_data/base_project/pyconfigs/connections.py +18 -0
  52. squirrels/{package_data → _package_data}/base_project/pyconfigs/context.py +31 -22
  53. squirrels/_package_data/base_project/pyconfigs/parameters.py +141 -0
  54. squirrels/_package_data/base_project/pyconfigs/user.py +44 -0
  55. squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.yml +1 -1
  56. squirrels/{package_data → _package_data}/base_project/seeds/seed_subcategories.yml +1 -1
  57. squirrels/_package_data/base_project/squirrels.yml.j2 +61 -0
  58. squirrels/_package_data/templates/dataset_results.html +112 -0
  59. squirrels/_package_data/templates/oauth_login.html +271 -0
  60. squirrels/_package_data/templates/squirrels_studio.html +20 -0
  61. squirrels/_parameter_configs.py +76 -55
  62. squirrels/_parameter_options.py +348 -0
  63. squirrels/_parameter_sets.py +53 -45
  64. squirrels/_parameters.py +1664 -0
  65. squirrels/_project.py +403 -242
  66. squirrels/_py_module.py +3 -2
  67. squirrels/_request_context.py +33 -0
  68. squirrels/_schemas/__init__.py +0 -0
  69. squirrels/_schemas/auth_models.py +167 -0
  70. squirrels/_schemas/query_param_models.py +75 -0
  71. squirrels/{_api_response_models.py → _schemas/response_models.py} +48 -18
  72. squirrels/_seeds.py +1 -1
  73. squirrels/_sources.py +23 -19
  74. squirrels/_utils.py +121 -39
  75. squirrels/_version.py +1 -1
  76. squirrels/arguments.py +7 -0
  77. squirrels/auth.py +4 -0
  78. squirrels/connections.py +3 -0
  79. squirrels/dashboards.py +2 -81
  80. squirrels/data_sources.py +14 -563
  81. squirrels/parameter_options.py +13 -348
  82. squirrels/parameters.py +14 -1266
  83. squirrels/types.py +16 -0
  84. {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/METADATA +42 -30
  85. squirrels-0.5.1.dist-info/RECORD +98 -0
  86. squirrels/package_data/base_project/dashboards/dashboard_example.yml +0 -22
  87. squirrels/package_data/base_project/macros/macros_example.sql +0 -15
  88. squirrels/package_data/base_project/models/dbviews/dbview_example.sql +0 -12
  89. squirrels/package_data/base_project/models/dbviews/dbview_example.yml +0 -26
  90. squirrels/package_data/base_project/models/federates/federate_example.py +0 -44
  91. squirrels/package_data/base_project/models/federates/federate_example.sql +0 -17
  92. squirrels/package_data/base_project/pyconfigs/connections.py +0 -14
  93. squirrels/package_data/base_project/pyconfigs/parameters.py +0 -93
  94. squirrels/package_data/base_project/pyconfigs/user.py +0 -23
  95. squirrels/package_data/base_project/squirrels.yml.j2 +0 -71
  96. squirrels-0.5.0rc0.dist-info/RECORD +0 -70
  97. /squirrels/{package_data → _package_data}/base_project/assets/expenses.db +0 -0
  98. /squirrels/{package_data → _package_data}/base_project/assets/weather.db +0 -0
  99. /squirrels/{package_data → _package_data}/base_project/docker/.dockerignore +0 -0
  100. /squirrels/{package_data → _package_data}/base_project/docker/Dockerfile +0 -0
  101. /squirrels/{package_data → _package_data}/base_project/docker/compose.yml +0 -0
  102. /squirrels/{package_data/base_project/.gitignore → _package_data/base_project/gitignore} +0 -0
  103. /squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.csv +0 -0
  104. /squirrels/{package_data → _package_data}/base_project/seeds/seed_subcategories.csv +0 -0
  105. /squirrels/{package_data → _package_data}/base_project/tmp/.gitignore +0 -0
  106. {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/WHEEL +0 -0
  107. {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/entry_points.txt +0 -0
  108. {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1664 @@
1
+ from __future__ import annotations
2
+ from typing import Callable, Type, TypeVar, Sequence, Generic, Any
3
+ from dataclasses import dataclass
4
+ from datetime import datetime, date
5
+ from decimal import Decimal
6
+ from abc import ABCMeta, abstractmethod
7
+
8
+ from ._arguments.init_time_args import ParametersArgs
9
+ from ._schemas import response_models as rm
10
+ from . import _data_sources as d, _parameter_configs as pc, _parameter_options as po, _parameter_sets as ps
11
+ from . import _utils as u
12
+
13
+ IntOrFloat = TypeVar("IntOrFloat", int, float)
14
+
15
+ PC = TypeVar("PC", bound=pc.ParameterConfig)
16
+ PO = TypeVar("PO", bound=po.ParameterOption)
17
+ DS = TypeVar("DS", bound=d.DataSource)
18
+
19
+ @dataclass
20
+ class Parameter(Generic[PC, PO, DS], metaclass=ABCMeta):
21
+ """
22
+ Abstract class for all parameter widgets
23
+ """
24
+ _config: PC
25
+
26
+ @abstractmethod
27
+ def is_enabled(self) -> bool:
28
+ return True
29
+
30
+ @staticmethod
31
+ @abstractmethod
32
+ def _ParameterConfigType() -> Type[PC]: # Gets the actual type of the ParameterConfig TypeVar at runtime
33
+ pass
34
+
35
+ @staticmethod
36
+ @abstractmethod
37
+ def _ParameterOptionType() -> Type[PO]: # Gets the actual type of the ParameterOption TypeVar at runtime
38
+ pass
39
+
40
+ @staticmethod
41
+ @abstractmethod
42
+ def _DataSourceType() -> Type[DS]: # Gets the actual type of the DataSource TypeVar at runtime
43
+ pass
44
+
45
+ @classmethod
46
+ def CreateWithOptions(
47
+ cls, name: str, label: str, all_options: Sequence[PO | dict], *, description: str = "",
48
+ user_attribute: str | None = None, parent_name: str | None = None, **kwargs
49
+ ) -> PC:
50
+ """
51
+ Method for creating the configurations for a Parameter that may include user attribute or parent
52
+
53
+ Arguments:
54
+ name: The name of the parameter
55
+ label: The display label for the parameter
56
+ all_options: All options associated to this parameter regardless of the user group or parent parameter option they depend on
57
+ description: Explains the meaning of the parameter
58
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
59
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
60
+ """
61
+ param_option_type = cls._ParameterOptionType()
62
+ if not isinstance(all_options, Sequence) or not all(isinstance(x, (param_option_type, dict)) for x in all_options):
63
+ raise u.ConfigurationError(f"The parameter must take a sequence of {param_option_type.__name__} objects")
64
+
65
+ param_config_type = cls._ParameterConfigType()
66
+ param_config = param_config_type(
67
+ name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name, **kwargs
68
+ )
69
+ return param_config
70
+
71
+ @classmethod
72
+ def create_with_options(
73
+ cls, name: str, label: str, *, description: str = "", user_attribute: str | None = None, parent_name: str | None = None
74
+ ):
75
+ """
76
+ Python decorator for creating the configurations for a Parameter that may include user attribute or parent.
77
+
78
+ The decorated function must return a list of ParameterOption objects.
79
+
80
+ Arguments:
81
+ name: The name of the parameter
82
+ label: The display label for the parameter
83
+ description: Explains the meaning of the parameter
84
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
85
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
86
+ """
87
+ def decorator(func: Callable[..., Sequence[PO]]):
88
+ def wrapper(sqrl: ParametersArgs):
89
+ options = u.call_func(func, sqrl=sqrl)
90
+ return cls.CreateWithOptions(
91
+ name, label, options, description=description,
92
+ user_attribute=user_attribute, parent_name=parent_name
93
+ )
94
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
95
+ return wrapper
96
+ return decorator
97
+
98
+ @classmethod
99
+ @abstractmethod
100
+ def CreateSimple(cls, name: str, label: str, *args, description: str = "", **kwargs) -> None:
101
+ pass
102
+
103
+ @classmethod
104
+ @abstractmethod
105
+ def create_simple(cls, name: str, label: str, *args, description: str = "", **kwargs) -> None:
106
+ pass
107
+
108
+ @classmethod
109
+ def _CreateFromSourceHelper(
110
+ cls, name: str, label: str, data_source: DS | dict, *, extra_args: dict = {}, description: str = "",
111
+ user_attribute: str | None = None, parent_name: str | None = None
112
+ ):
113
+ data_source_type = cls._DataSourceType()
114
+ if not isinstance(data_source, (data_source_type, dict)):
115
+ raise u.ConfigurationError(f"The data source must be a {data_source_type.__name__} object")
116
+
117
+ param_config = pc.DataSourceParameterConfig(
118
+ cls._ParameterConfigType(), name, label, data_source, description=description, user_attribute=user_attribute,
119
+ parent_name=parent_name, extra_args=extra_args
120
+ )
121
+ return param_config
122
+
123
+ @classmethod
124
+ def CreateFromSource(
125
+ cls, name: str, label: str, data_source: DS | dict, *, description: str = "",
126
+ user_attribute: str | None = None, parent_name: str | None = None, **kwargs
127
+ ):
128
+ """
129
+ Method for creating the configurations for any Parameter that uses a DataSource to receive the options
130
+
131
+ Arguments:
132
+ name: The name of the parameter
133
+ label: The display label for the parameter
134
+ data_source: The lookup table to use for this parameter
135
+ description: Explains the meaning of the parameter
136
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
137
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
138
+ """
139
+ return cls._CreateFromSourceHelper(name, label, data_source, description=description, user_attribute=user_attribute, parent_name=parent_name)
140
+
141
+ @classmethod
142
+ def create_from_source(
143
+ cls, name: str, label: str, *, description: str = "", user_attribute: str | None = None, parent_name: str | None = None
144
+ ):
145
+ """
146
+ Python decorator for creating the configurations for a Parameter that uses a DataSource to receive the options from a lookup table
147
+
148
+ The decorated function must return a DataSource object.
149
+
150
+ Arguments:
151
+ name: The name of the parameter
152
+ label: The display label for the parameter
153
+ description: Explains the meaning of the parameter
154
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
155
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
156
+ """
157
+ def decorator(func: Callable[..., DS]):
158
+ def wrapper(sqrl: ParametersArgs):
159
+ data_source = u.call_func(func, sqrl=sqrl)
160
+ return cls.CreateFromSource(
161
+ name, label, data_source, description=description,
162
+ user_attribute=user_attribute, parent_name=parent_name
163
+ )
164
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
165
+ return wrapper
166
+ return decorator
167
+
168
+ def _enquote(self, value: str) -> str:
169
+ return "'" + value.replace("'", "''") + "'"
170
+
171
+ def _validate_input_date(self, input_date: date | str, curr_option: po._DateTypeParameterOption) -> date:
172
+ if isinstance(input_date, str):
173
+ try:
174
+ input_date = datetime.strptime(input_date.strip(), "%Y-%m-%d").date()
175
+ except ValueError:
176
+ raise self._config._invalid_input_error(str(input_date), "Must be a date in YYYY-MM-DD format.")
177
+
178
+ try:
179
+ return curr_option._validate_date(input_date)
180
+ except u.ConfigurationError as e:
181
+ raise self._config._invalid_input_error(str(input_date), str(e))
182
+
183
+ def _validate_number(self, input_number: po.Number, curr_option: po._NumericParameterOption) -> Decimal:
184
+ try:
185
+ return curr_option._validate_value(input_number)
186
+ except u.ConfigurationError as e:
187
+ raise self._config._invalid_input_error(str(input_number), str(e))
188
+
189
+ @abstractmethod
190
+ def _to_json_dict0(self) -> dict:
191
+ """
192
+ Helper method to convert the derived Parameter class into a JSON dictionary
193
+ """
194
+ output = {
195
+ "widget_type": self._config.widget_type(), "name": self._config.name,
196
+ "label": self._config.label, "description": self._config.description
197
+ }
198
+ if not self.is_enabled():
199
+ output["widget_type"] = "disabled"
200
+ return output
201
+
202
+ @abstractmethod
203
+ def _get_response_model0(self) -> type[rm.ParameterModelBase]:
204
+ pass
205
+
206
+ def _to_api_response_model0(self) -> rm.ParameterModelBase:
207
+ return self._get_response_model0().model_validate(self._to_json_dict0())
208
+
209
+
210
+ SelectionPC = TypeVar("SelectionPC", bound=pc.SelectionParameterConfig)
211
+
212
+ @dataclass
213
+ class _SelectionParameter(Parameter[SelectionPC, po.SelectParameterOption, d.SelectDataSource], Generic[SelectionPC]):
214
+ _options: Sequence[po.SelectParameterOption]
215
+
216
+ def __post_init__(self):
217
+ self._options = tuple(self._options)
218
+
219
+ def is_enabled(self) -> bool:
220
+ return len(self._options) > 0
221
+
222
+ @abstractmethod
223
+ def _get_selected_ids_as_list(self) -> Sequence[str]:
224
+ pass
225
+
226
+ def _validate_selected_id_in_options(self, selected_id):
227
+ if selected_id not in (x._identifier for x in self._options):
228
+ raise self._config._invalid_input_error(selected_id, f"The selected id {selected_id} does not exist in available options.")
229
+
230
+ @abstractmethod
231
+ def _to_json_dict0(self) -> dict:
232
+ """
233
+ Helper method to convert the derived selection parameter class into a JSON object
234
+ """
235
+ output = super()._to_json_dict0()
236
+ output['trigger_refresh'] = self._config.trigger_refresh
237
+ output['options'] = [x._to_json_dict() for x in self._options]
238
+ return output
239
+
240
+
241
+ @dataclass
242
+ class SingleSelectParameter(_SelectionParameter[pc.SingleSelectParameterConfig]):
243
+ """
244
+ Class for single-select parameter widgets.
245
+
246
+ Attributes:
247
+ config: The config for this widget parameter (for immutable attributes like name, label, all possible options, etc)
248
+ options: The parameter options that are currently selectable
249
+ selected_id: The ID of the selected option
250
+ """
251
+ _selected_id: str | None
252
+
253
+ def __post_init__(self):
254
+ super().__post_init__()
255
+ if len(self._options) > 0:
256
+ assert self._selected_id != None
257
+ self._validate_selected_id_in_options(self._selected_id)
258
+ else:
259
+ self._selected_id = None
260
+
261
+ @staticmethod
262
+ def _ParameterConfigType():
263
+ return pc.SingleSelectParameterConfig
264
+
265
+ @staticmethod
266
+ def _ParameterOptionType():
267
+ return po.SelectParameterOption
268
+
269
+ @staticmethod
270
+ def _DataSourceType():
271
+ return d.SelectDataSource
272
+
273
+ @classmethod
274
+ def CreateSimple(
275
+ cls, name: str, label: str, all_options: Sequence[po.SelectParameterOption | dict], *, description: str = "", **kwargs
276
+ ):
277
+ """
278
+ Method for creating the configurations for a SingleSelectParameter that doesn't involve user attributes or parent parameters
279
+
280
+ Arguments:
281
+ name: The name of the parameter
282
+ label: The display label for the parameter
283
+ all_options: All options associated to this parameter regardless of the user group or parent parameter option they depend on
284
+ description: Explains the meaning of the parameter
285
+ """
286
+ return cls.CreateWithOptions(name, label, all_options, description=description)
287
+
288
+ @classmethod
289
+ def create_simple(cls, name: str, label: str, *, description: str = ""):
290
+ """
291
+ Python decorator for creating the configurations for a SingleSelectParameter that doesn't involve user attributes or parent parameters
292
+
293
+ The decorated function must return a list of SelectParameterOption objects.
294
+
295
+ Arguments:
296
+ name: The name of the parameter
297
+ label: The display label for the parameter
298
+ description: Explains the meaning of the parameter
299
+ """
300
+ def decorator(func: Callable[..., Sequence[po.SelectParameterOption]]):
301
+ def wrapper(sqrl: ParametersArgs):
302
+ options = u.call_func(func, sqrl=sqrl)
303
+ return cls.CreateSimple(name, label, options, description=description)
304
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
305
+ return wrapper
306
+ return decorator
307
+
308
+ def get_selected(
309
+ self, field: str | None = None, *, default_field: str | None = None, default: Any = None, **kwargs
310
+ ) -> po.SelectParameterOption | Any | None:
311
+ """
312
+ Gets the selected single-select option or selected custom field
313
+
314
+ Arguments:
315
+ field: If field is not None, the method gets this field from the "custom_fields" attribute of the selected option.
316
+ Otherwise, returns the class object of the selected option
317
+ default_field: If field does not exist for a parameter option and default_field is not None, the default_field is used
318
+ as the "field" instead. Does nothing if field is None
319
+ default: If field does not exist for a parameter option, default_field is None, but default is not None, then the default
320
+ is returned as the selected field. Does nothing if field is None or default_field is not None
321
+
322
+ Returns:
323
+ A SelectParameterOption class object if no field is provided, or the type of the custom field
324
+ """
325
+ def get_selected_from_id(identifier: str):
326
+ selected = next(x for x in self._options if x._identifier == identifier)
327
+ if field is not None:
328
+ selected = selected.get_custom_field(field, default_field=default_field, default=default)
329
+ return selected
330
+ return u.process_if_not_none(self._selected_id, get_selected_from_id)
331
+
332
+ def get_selected_quoted(self, field: str, *, default_field: str | None = None, default: str | None = None, **kwargs) -> str | None:
333
+ """
334
+ Gets the selected single-select option surrounded by single quotes
335
+
336
+ Arguments:
337
+ field: The "custom_fields" attribute of the selected option.
338
+ default_field: If field does not exist for a parameter option and default_field is not None, the default_field is used
339
+ as the "field" instead.
340
+ default: If field does not exist for a parameter option, default_field is None, but default is not None, then the default
341
+ is returned as the selected field. Does nothing if default_field is not None
342
+
343
+ Returns:
344
+ A string surrounded by single quotes
345
+ """
346
+ selected_value = self.get_selected(field, default_field=default_field, default=default)
347
+
348
+ def _enquote(x: Any) -> str:
349
+ if not isinstance(selected_value, str):
350
+ raise u.ConfigurationError(
351
+ f"Method 'get_selected_quoted' can only be used on fields with only string values"
352
+ )
353
+ return self._enquote(x)
354
+
355
+ return u.process_if_not_none(selected_value, _enquote)
356
+
357
+ def get_selected_id(self, **kwargs) -> str | None:
358
+ """
359
+ Gets the ID of the selected option
360
+
361
+ Returns:
362
+ A string ID or None if there are no selectable options
363
+ """
364
+ def get_id(x: po.SelectParameterOption):
365
+ return x._identifier
366
+ return u.process_if_not_none(self.get_selected(), get_id)
367
+
368
+ def get_selected_id_quoted(self, **kwargs) -> str | None:
369
+ """
370
+ Gets the ID of the selected option surrounded by single quotes
371
+
372
+ Returns:
373
+ A string or None if there are no selectable options
374
+ """
375
+ return u.process_if_not_none(self.get_selected_id(), self._enquote)
376
+
377
+ def get_selected_label(self, **kwargs) -> str | None:
378
+ """
379
+ Gets the label of the selected option
380
+
381
+ Returns:
382
+ A string or None if there are no selectable options
383
+ """
384
+ def get_label(x: po.SelectParameterOption): return x._label
385
+ return u.process_if_not_none(self.get_selected(), get_label)
386
+
387
+ def get_selected_label_quoted(self, **kwargs) -> str | None:
388
+ """
389
+ Gets the label of the selected option surrounded by single quotes
390
+
391
+ Returns:
392
+ A string or None if there are no selectable options
393
+ """
394
+ return u.process_if_not_none(self.get_selected_label(), self._enquote)
395
+
396
+ def _get_selected_ids_as_list(self) -> Sequence[str]:
397
+ selected_id = self.get_selected_id()
398
+ if selected_id is not None:
399
+ return (selected_id,)
400
+ else:
401
+ return tuple()
402
+
403
+ def _to_json_dict0(self) -> dict:
404
+ """
405
+ Converts this parameter as a JSON object for the parameters API response
406
+
407
+ Returns:
408
+ A dictionary for the JSON object
409
+ """
410
+ output = super()._to_json_dict0()
411
+ output['selected_id'] = self._selected_id
412
+ return output
413
+
414
+ def _get_response_model0(self):
415
+ return rm.SingleSelectParameterModel if self.is_enabled() else rm.NoneParameterModel
416
+
417
+
418
+ @dataclass
419
+ class MultiSelectParameter(_SelectionParameter[pc.MultiSelectParameterConfig]):
420
+ """
421
+ Class for multi-select parameter widgets.
422
+
423
+ Attributes:
424
+ config: The config for this widget parameter (for immutable attributes like name, label, all possible options, etc)
425
+ options: The parameter options that are currently selectable
426
+ selected_ids: A sequence of IDs of the selected options
427
+ """
428
+ _selected_ids: Sequence[str]
429
+
430
+ def __post_init__(self):
431
+ super().__post_init__()
432
+ self._selected_ids = tuple(self._selected_ids)
433
+ for selected_id in self._selected_ids:
434
+ self._validate_selected_id_in_options(selected_id)
435
+
436
+ @staticmethod
437
+ def _ParameterConfigType():
438
+ return pc.MultiSelectParameterConfig
439
+
440
+ @staticmethod
441
+ def _ParameterOptionType():
442
+ return po.SelectParameterOption
443
+
444
+ @staticmethod
445
+ def _DataSourceType():
446
+ return d.SelectDataSource
447
+
448
+ @classmethod
449
+ def CreateWithOptions(
450
+ cls, name: str, label: str, all_options: Sequence[po.SelectParameterOption | dict], *, description: str = "",
451
+ show_select_all: bool = True, order_matters: bool = False, none_is_all: bool = True,
452
+ user_attribute: str | None = None, parent_name: str | None = None, **kwargs
453
+ ):
454
+ """
455
+ Method for creating the configurations for a MultiSelectParameter that may include user attribute or parent
456
+
457
+ Arguments:
458
+ name: The name of the parameter
459
+ label: The display label for the parameter
460
+ all_options: All options associated to this parameter regardless of the user group or parent parameter option they depend on
461
+ description: Explains the meaning of the parameter
462
+ show_select_all: Communicate to front-end whether to include a "select all" option
463
+ order_matters: Communicate to front-end whether the order of the selections made matter
464
+ none_is_all: Whether having no options selected is equivalent to all selectable options selected
465
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
466
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
467
+ """
468
+ return super().CreateWithOptions(
469
+ name, label, all_options, description=description, user_attribute=user_attribute, parent_name=parent_name,
470
+ show_select_all=show_select_all, order_matters=order_matters, none_is_all=none_is_all
471
+ )
472
+
473
+ @classmethod
474
+ def create_with_options(
475
+ cls, name: str, label: str, *, description: str = "", show_select_all: bool = True, order_matters: bool = False,
476
+ none_is_all: bool = True, user_attribute: str | None = None, parent_name: str | None = None
477
+ ):
478
+ """
479
+ Python decorator for creating the configurations for a MultiSelectParameter that may include user attribute or parent
480
+
481
+ The decorated function must return a list of SelectParameterOption objects.
482
+
483
+ Arguments:
484
+ name: The name of the parameter
485
+ label: The display label for the parameter
486
+ description: Explains the meaning of the parameter
487
+ show_select_all: Communicate to front-end whether to include a "select all" option
488
+ order_matters: Communicate to front-end whether the order of the selections made matter
489
+ none_is_all: Whether having no options selected is equivalent to all selectable options selected
490
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
491
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
492
+ """
493
+ def decorator(func: Callable[..., Sequence[po.SelectParameterOption]]):
494
+ def wrapper(sqrl: ParametersArgs):
495
+ options = u.call_func(func, sqrl=sqrl)
496
+ return cls.CreateWithOptions(
497
+ name, label, options, description=description, user_attribute=user_attribute, parent_name=parent_name,
498
+ show_select_all=show_select_all, order_matters=order_matters, none_is_all=none_is_all
499
+ )
500
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
501
+ return wrapper
502
+ return decorator
503
+
504
+ @classmethod
505
+ def CreateSimple(
506
+ cls, name: str, label: str, all_options: Sequence[po.SelectParameterOption], *, description: str = "",
507
+ show_select_all: bool = True, order_matters: bool = False, none_is_all: bool = True, **kwargs
508
+ ):
509
+ """
510
+ Method for creating the configurations for a MultiSelectParameter that doesn't involve user attributes or parent parameters
511
+
512
+ Arguments:
513
+ name: The name of the parameter
514
+ label: The display label for the parameter
515
+ all_options: All options associated to this parameter regardless of the user group or parent parameter option they depend on
516
+ description: Explains the meaning of the parameter
517
+ show_select_all: Communicate to front-end whether to include a "select all" option
518
+ order_matters: Communicate to front-end whether the order of the selections made matter
519
+ none_is_all: Whether having no options selected is equivalent to all selectable options selected
520
+ """
521
+ return cls.CreateWithOptions(
522
+ name, label, all_options, description=description,
523
+ show_select_all=show_select_all, order_matters=order_matters, none_is_all=none_is_all
524
+ )
525
+
526
+ @classmethod
527
+ def create_simple(
528
+ cls, name: str, label: str, *, description: str = "",
529
+ show_select_all: bool = True, order_matters: bool = False, none_is_all: bool = True
530
+ ):
531
+ """
532
+ Python decorator for creating the configurations for a MultiSelectParameter that doesn't involve user attributes or parent parameters
533
+
534
+ The decorated function must return a list of SelectParameterOption objects.
535
+
536
+ Arguments:
537
+ name: The name of the parameter
538
+ label: The display label for the parameter
539
+ description: Explains the meaning of the parameter
540
+ show_select_all: Communicate to front-end whether to include a "select all" option
541
+ order_matters: Communicate to front-end whether the order of the selections made matter
542
+ none_is_all: Whether having no options selected is equivalent to all selectable options selected
543
+ """
544
+ def decorator(func: Callable[..., Sequence[po.SelectParameterOption]]):
545
+ def wrapper(sqrl: ParametersArgs):
546
+ options = u.call_func(func, sqrl=sqrl)
547
+ return cls.CreateSimple(
548
+ name, label, options, description=description,
549
+ show_select_all=show_select_all, order_matters=order_matters, none_is_all=none_is_all
550
+ )
551
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
552
+ return wrapper
553
+ return decorator
554
+
555
+ @classmethod
556
+ def CreateFromSource(
557
+ cls, name: str, label: str, data_source: d.SelectDataSource | dict, *, description: str = "",
558
+ show_select_all: bool = True, order_matters: bool = False, none_is_all: bool = True,
559
+ user_attribute: str | None = None, parent_name: str | None = None, **kwargs
560
+ ):
561
+ """
562
+ Method for creating the configurations for a MultiSelectParameter that uses a SelectDataSource to receive the options
563
+
564
+ Arguments:
565
+ name: The name of the parameter
566
+ label: The display label for the parameter
567
+ data_source: The lookup table to use for this parameter
568
+ description: Explains the meaning of the parameter
569
+ show_select_all: Communicate to front-end whether to include a "select all" option
570
+ order_matters: Communicate to front-end whether the order of the selections made matter
571
+ none_is_all: Whether having no options selected is equivalent to all selectable options selected
572
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
573
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
574
+ """
575
+ extra_args = {
576
+ "show_select_all": show_select_all, "order_matters": order_matters, "none_is_all": none_is_all
577
+ }
578
+ return cls._CreateFromSourceHelper(
579
+ name, label, data_source, extra_args=extra_args, description=description,
580
+ user_attribute=user_attribute, parent_name=parent_name
581
+ )
582
+
583
+ @classmethod
584
+ def create_from_source(
585
+ cls, name: str, label: str, *, description: str = "",
586
+ show_select_all: bool = True, order_matters: bool = False, none_is_all: bool = True,
587
+ user_attribute: str | None = None, parent_name: str | None = None
588
+ ):
589
+ """
590
+ Python decorator for creating the configurations for a MultiSelectParameter that uses a SelectDataSource to receive the options from a lookup table
591
+
592
+ The decorated function must return a SelectDataSource object.
593
+
594
+ Arguments:
595
+ name: The name of the parameter
596
+ label: The display label for the parameter
597
+ data_source: The lookup table to use for this parameter
598
+ description: Explains the meaning of the parameter
599
+ show_select_all: Communicate to front-end whether to include a "select all" option
600
+ order_matters: Communicate to front-end whether the order of the selections made matter
601
+ none_is_all: Whether having no options selected is equivalent to all selectable options selected
602
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
603
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
604
+ """
605
+ def decorator(func: Callable[..., d.SelectDataSource]):
606
+ def wrapper(sqrl: ParametersArgs):
607
+ data_source = u.call_func(func, sqrl=sqrl)
608
+ return cls.CreateFromSource(
609
+ name, label, data_source, description=description,
610
+ show_select_all=show_select_all, order_matters=order_matters, none_is_all=none_is_all,
611
+ user_attribute=user_attribute, parent_name=parent_name
612
+ )
613
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
614
+ return wrapper
615
+ return decorator
616
+
617
+ def has_non_empty_selection(self) -> bool:
618
+ """
619
+ Returns True if more than zero options were selected. False otherwise.
620
+
621
+ Note that even when this returns False, all "get_selected" functions would
622
+ return the full list of options if "include_all" is set to True
623
+
624
+ Returns:
625
+ A boolean
626
+ """
627
+ return len(self._selected_ids) > 0
628
+
629
+ def get_selected_list(
630
+ self, field: str | None = None, *, default_field: str | None = None, default: Any = None, **kwargs
631
+ ) -> Sequence[po.SelectParameterOption | Any]:
632
+ """
633
+ Gets the sequence of the selected option(s) or a sequence of selected custom fields
634
+
635
+ Arguments:
636
+ field: If field is not None, the method gets this field from the "custom_fields" attribute of the selected options.
637
+ Otherwise, returns the class objects of the selected options
638
+ default_field: If field does not exist for a parameter option and default_field is not None, the default_field is used
639
+ as the "field" instead. Does nothing if field is None
640
+ default: If field does not exist for a parameter option, default_field is None, but default is not None, the default
641
+ is returned as the selected field. Does nothing if field is None or default_field is not None
642
+
643
+ Returns:
644
+ A sequence of SelectParameterOption class objects or sequence of type of custom field
645
+ """
646
+ if not self.has_non_empty_selection() and self._config.none_is_all:
647
+ selected_list = self._options
648
+ else:
649
+ selected_list = (x for x in self._options if x._identifier in self._selected_ids)
650
+
651
+ if field is not None:
652
+ selected_list = [selected.get_custom_field(field, default_field=default_field, default=default) for selected in selected_list]
653
+
654
+ return tuple(selected_list)
655
+
656
+ def _get_selected_list_of_strings(
657
+ self, method: str, field: str, default_field: str | None, default: str | None, **kwargs
658
+ ) -> list[str]:
659
+ selected_list = self.get_selected_list(field, default_field=default_field, default=default)
660
+ list_of_strings: list[str] = []
661
+ for selected in selected_list:
662
+ if not isinstance(selected, str):
663
+ raise u.ConfigurationError(
664
+ f"Method '{method}' can only be used on fields with only string values"
665
+ )
666
+ list_of_strings.append(selected)
667
+ return list_of_strings
668
+
669
+ def get_selected_list_joined(self, field: str, *, default_field: str | None = None, default: str | None = None, **kwargs) -> str:
670
+ """
671
+ Gets the selected custom fields joined by comma
672
+
673
+ Arguments:
674
+ field: The "custom_fields" attribute of the selected options.
675
+ default_field: If field does not exist for a parameter option and default_field is not None, the default_field is used
676
+ as the "field" instead.
677
+ default: If field does not exist for a parameter option, default_field is None, but default is not None, the default
678
+ is returned as the selected field. Does nothing if default_field is not None
679
+
680
+ Returns:
681
+ A string
682
+ """
683
+ list_of_strings = self._get_selected_list_of_strings("get_selected_list_joined", field, default_field, default)
684
+ return ','.join(list_of_strings)
685
+
686
+ def get_selected_list_quoted(self, field: str, *, default_field: str | None = None, default: str | None = None, **kwargs) -> tuple[str, ...]:
687
+ """
688
+ Gets the selected custom fields surrounded by single quotes
689
+
690
+ Arguments:
691
+ field: The "custom_fields" attribute of the selected options.
692
+ default_field: If field does not exist for a parameter option and default_field is not None, the default_field is used
693
+ as the "field" instead.
694
+ default: If field does not exist for a parameter option, default_field is None, but default is not None, the default
695
+ is returned as the selected field. Does nothing if default_field is not None
696
+
697
+ Returns:
698
+ A tuple of strings
699
+ """
700
+ list_of_strings = self._get_selected_list_of_strings("get_selected_list_quoted", field, default_field, default)
701
+ return tuple(self._enquote(x) for x in list_of_strings)
702
+
703
+ def get_selected_list_quoted_joined(self, field: str, *, default_field: str | None = None, default: str | None = None, **kwargs) -> str:
704
+ """
705
+ Gets the selected custom fields surrounded by single quotes and joined by comma
706
+
707
+ Arguments:
708
+ field: The "custom_fields" attribute of the selected options.
709
+ default_field: If field does not exist for a parameter option and default_field is not None, the default_field is used
710
+ as the "field" instead.
711
+ default: If field does not exist for a parameter option, default_field is None, but default is not None, the default
712
+ is returned as the selected field. Does nothing if default_field is not None
713
+
714
+ Returns:
715
+ A string
716
+ """
717
+ list_of_strings = self._get_selected_list_of_strings("get_selected_list_quoted_joined", field, default_field, default)
718
+ return ','.join(self._enquote(x) for x in list_of_strings)
719
+
720
+ def get_selected_ids_as_list(self, **kwargs) -> Sequence[str]:
721
+ """
722
+ Gets the sequence of ID(s) of the selected option(s)
723
+
724
+ Returns:
725
+ A sequence of strings
726
+ """
727
+ return tuple(x._identifier for x in self.get_selected_list())
728
+
729
+ def get_selected_ids_joined(self, **kwargs) -> str:
730
+ """
731
+ Gets the ID(s) of the selected option(s) joined by comma
732
+
733
+ Returns:
734
+ A string
735
+ """
736
+ return ','.join(self.get_selected_ids_as_list())
737
+
738
+ def get_selected_ids_quoted_as_list(self, **kwargs) -> Sequence[str]:
739
+ """
740
+ Gets the sequence of ID(s) of the selected option(s) surrounded by single quotes
741
+
742
+ Returns:
743
+ A sequence of strings
744
+ """
745
+ return tuple(self._enquote(x) for x in self.get_selected_ids_as_list())
746
+
747
+ def get_selected_ids_quoted_joined(self, **kwargs) -> str:
748
+ """
749
+ Gets the ID(s) of the selected option(s) surrounded by single quotes and joined by comma
750
+
751
+ Returns:
752
+ A string
753
+ """
754
+ return ','.join(self.get_selected_ids_quoted_as_list())
755
+
756
+ def get_selected_labels_as_list(self, **kwargs) -> Sequence[str]:
757
+ """
758
+ Gets the sequence of label(s) of the selected option(s)
759
+
760
+ Returns:
761
+ A sequence of strings
762
+ """
763
+ return tuple(x._label for x in self.get_selected_list())
764
+
765
+ def get_selected_labels_joined(self, **kwargs) -> str:
766
+ """
767
+ Gets the label(s) of the selected option(s) joined by comma
768
+
769
+ Returns:
770
+ A string
771
+ """
772
+ return ','.join(self.get_selected_labels_as_list())
773
+
774
+ def get_selected_labels_quoted_as_list(self, **kwargs) -> Sequence[str]:
775
+ """
776
+ Gets the sequence of label(s) of the selected option(s) surrounded by single quotes
777
+
778
+ Returns:
779
+ A sequence of strings
780
+ """
781
+ return tuple(self._enquote(x) for x in self.get_selected_labels_as_list())
782
+
783
+ def get_selected_labels_quoted_joined(self, **kwargs) -> str:
784
+ """
785
+ Gets the label(s) of the selected option(s) surrounded by single quotes and joined by comma
786
+
787
+ Returns:
788
+ A string
789
+ """
790
+ return ','.join(self.get_selected_labels_quoted_as_list())
791
+
792
+ def _get_selected_ids_as_list(self, **kwargs) -> Sequence[str]:
793
+ return self.get_selected_ids_as_list()
794
+
795
+ def _to_json_dict0(self):
796
+ """
797
+ Converts this parameter as a JSON object for the parameters API response
798
+
799
+ Returns:
800
+ A dictionary for the JSON object
801
+ """
802
+ output = super()._to_json_dict0()
803
+ output['show_select_all'] = self._config.show_select_all
804
+ output['order_matters'] = self._config.order_matters
805
+ output['selected_ids'] = list(self._selected_ids)
806
+ return output
807
+
808
+ def _get_response_model0(self):
809
+ return rm.MultiSelectParameterModel if self.is_enabled() else rm.NoneParameterModel
810
+
811
+
812
+ DatePO = TypeVar("DatePO", bound=po._DateTypeParameterOption)
813
+
814
+ @dataclass
815
+ class _DateTypeParameter(Parameter[PC, DatePO, DS], Generic[PC, DatePO, DS]):
816
+ _curr_option: DatePO | None
817
+
818
+ def is_enabled(self) -> bool:
819
+ return self._curr_option is not None
820
+
821
+ def _cast_optional_date_to_str(self, date: date | None) -> str | None:
822
+ return None if date is None else date.strftime("%Y-%m-%d")
823
+
824
+ def _to_json_dict0(self):
825
+ output = super()._to_json_dict0()
826
+ if self._curr_option is not None:
827
+ output["min_date"] = self._cast_optional_date_to_str(self._curr_option._min_date)
828
+ output["max_date"] = self._cast_optional_date_to_str(self._curr_option._max_date)
829
+ return output
830
+
831
+
832
+ @dataclass
833
+ class DateParameter(_DateTypeParameter[pc.DateParameterConfig, po.DateParameterOption, d.DateDataSource]):
834
+ """
835
+ Class for date parameter widgets.
836
+
837
+ Attributes:
838
+ config: The config for this widget parameter (for immutable attributes like name, label, all possible options, etc)
839
+ curr_option: The current option showing for defaults based on user attribute and selection of parent
840
+ selected_date: The selected date
841
+ """
842
+ _selected_date: date | str | None
843
+
844
+ def __post_init__(self):
845
+ if self._curr_option is not None and self._selected_date is not None:
846
+ self._selected_date = self._validate_input_date(self._selected_date, self._curr_option)
847
+
848
+ def is_enabled(self) -> bool:
849
+ return self._curr_option is not None
850
+
851
+ @staticmethod
852
+ def _ParameterConfigType():
853
+ return pc.DateParameterConfig
854
+
855
+ @staticmethod
856
+ def _ParameterOptionType():
857
+ return po.DateParameterOption
858
+
859
+ @staticmethod
860
+ def _DataSourceType():
861
+ return d.DateDataSource
862
+
863
+ @classmethod
864
+ def CreateSimple(
865
+ cls, name: str, label: str, default_date: str | date, *, description: str = "",
866
+ min_date: str | date | None = None, max_date: str | date | None = None, date_format: str = '%Y-%m-%d', **kwargs
867
+ ):
868
+ """
869
+ Method for creating the configurations for a DateParameter that doesn't involve user attributes or parent parameters
870
+
871
+ Arguments:
872
+ name: The name of the parameter
873
+ label: The display label for the parameter
874
+ default_date: Default date for this option
875
+ description: Explains the meaning of the parameter
876
+ min_date: Minimum selectable date
877
+ max_date: Maximum selectable date
878
+ date_format: Format of the default date, default is '%Y-%m-%d'
879
+ """
880
+ single_param_option = po.DateParameterOption(default_date, min_date=min_date, max_date=max_date, date_format=date_format)
881
+ return cls.CreateWithOptions(name, label, (single_param_option,), description=description)
882
+
883
+ @classmethod
884
+ def create_simple(
885
+ cls, name: str, label: str, default_date: str | date, *, description: str = "",
886
+ min_date: str | date | None = None, max_date: str | date | None = None, date_format: str = '%Y-%m-%d'
887
+ ):
888
+ """
889
+ Python decorator for creating the configurations for a DateParameter that doesn't involve user attributes or parent parameters
890
+
891
+ Arguments:
892
+ name: The name of the parameter
893
+ label: The display label for the parameter
894
+ default_date: Default date for this option
895
+ description: Explains the meaning of the parameter
896
+ min_date: Minimum selectable date
897
+ max_date: Maximum selectable date
898
+ date_format: Format of the default date, default is '%Y-%m-%d'
899
+ """
900
+ def decorator(func: Callable[..., Any]):
901
+ def wrapper(sqrl: ParametersArgs):
902
+ return cls.CreateSimple(
903
+ name, label, default_date, description=description,
904
+ min_date=min_date, max_date=max_date, date_format=date_format
905
+ )
906
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
907
+ return wrapper
908
+ return decorator
909
+
910
+ def get_selected_date(self, *, date_format: str | None = None, **kwargs) -> str:
911
+ """
912
+ Gets selected date as string
913
+
914
+ Arguments:
915
+ date_format: The date format (see Python's datetime formats). If not specified, self.date_format is used
916
+
917
+ Returns:
918
+ A string
919
+ """
920
+ assert self._curr_option is not None and isinstance(self._selected_date, date), "Parameter is not enabled"
921
+ date_format = self._curr_option._date_format if date_format is None else date_format
922
+ return self._selected_date.strftime(date_format)
923
+
924
+ def get_selected_date_quoted(self, *, date_format: str | None = None, **kwargs) -> str:
925
+ """
926
+ Gets selected date as string surrounded by single quotes
927
+
928
+ Arguments:
929
+ date_format: The date format (see Python's datetime formats). If not specified, self.date_format is used
930
+
931
+ Returns:
932
+ A string
933
+ """
934
+ return self._enquote(self.get_selected_date(date_format=date_format))
935
+
936
+ def _to_json_dict0(self):
937
+ """
938
+ Converts this parameter as a JSON object for the parameters API response
939
+
940
+ The "selected_date" field will always be in yyyy-mm-dd format
941
+
942
+ Returns:
943
+ A dictionary for the JSON object
944
+ """
945
+ output = super()._to_json_dict0()
946
+ if self.is_enabled():
947
+ output["selected_date"] = self.get_selected_date(date_format="%Y-%m-%d")
948
+ else:
949
+ output["selected_date"] = ""
950
+ return output
951
+
952
+ def _get_response_model0(self):
953
+ return rm.DateParameterModel if self.is_enabled() else rm.NoneParameterModel
954
+
955
+
956
+ @dataclass
957
+ class DateRangeParameter(_DateTypeParameter[pc.DateRangeParameterConfig, po.DateRangeParameterOption, d.DateRangeDataSource]):
958
+ """
959
+ Class for date range parameter widgets.
960
+
961
+ Attributes:
962
+ config: The config for this widget parameter (for immutable attributes like name, label, all possible options, etc)
963
+ curr_option: The current option showing for defaults based on user attribute and selection of parent
964
+ selected_start_date: The selected start date
965
+ selected_end_date: The selected end date
966
+ """
967
+ _selected_start_date: date | str | None
968
+ _selected_end_date: date | str | None
969
+
970
+ def __post_init__(self):
971
+ if self._curr_option is not None:
972
+ if self._selected_start_date is not None:
973
+ self._selected_start_date = self._validate_input_date(self._selected_start_date, self._curr_option)
974
+ if self._selected_end_date is not None:
975
+ self._selected_end_date = self._validate_input_date(self._selected_end_date, self._curr_option)
976
+
977
+ def is_enabled(self) -> bool:
978
+ return self._curr_option is not None
979
+
980
+ @staticmethod
981
+ def _ParameterConfigType():
982
+ return pc.DateRangeParameterConfig
983
+
984
+ @staticmethod
985
+ def _ParameterOptionType():
986
+ return po.DateRangeParameterOption
987
+
988
+ @staticmethod
989
+ def _DataSourceType():
990
+ return d.DateRangeDataSource
991
+
992
+ @classmethod
993
+ def CreateSimple(
994
+ cls, name: str, label: str, default_start_date: str | date, default_end_date: str | date, *, description: str = "",
995
+ min_date: str | date | None = None, max_date: str | date | None = None, date_format: str = '%Y-%m-%d', **kwargs
996
+ ):
997
+ """
998
+ Method for creating the configurations for a DateRangeParameter that doesn't involve user attributes or parent parameters
999
+
1000
+ Arguments:
1001
+ name: The name of the parameter
1002
+ label: The display label for the parameter
1003
+ default_start_date: Default start date for this option
1004
+ default_end_date: Default end date for this option
1005
+ description: Explains the meaning of the parameter
1006
+ min_date: Minimum selectable date
1007
+ max_date: Maximum selectable date
1008
+ date_format: Format of the default date, default is '%Y-%m-%d'
1009
+ """
1010
+ single_param_option = po.DateRangeParameterOption(
1011
+ default_start_date, default_end_date, min_date=min_date, max_date=max_date, date_format=date_format
1012
+ )
1013
+ return cls.CreateWithOptions(name, label, (single_param_option,), description=description)
1014
+
1015
+ @classmethod
1016
+ def create_simple(
1017
+ cls, name: str, label: str, default_start_date: str | date, default_end_date: str | date, *, description: str = "",
1018
+ min_date: str | date | None = None, max_date: str | date | None = None, date_format: str = '%Y-%m-%d'
1019
+ ):
1020
+ """
1021
+ Python decorator for creating the configurations for a DateRangeParameter that doesn't involve user attributes or parent parameters
1022
+
1023
+ Arguments:
1024
+ name: The name of the parameter
1025
+ label: The display label for the parameter
1026
+ default_start_date: Default start date for this option
1027
+ default_end_date: Default end date for this option
1028
+ description: Explains the meaning of the parameter
1029
+ min_date: Minimum selectable date
1030
+ max_date: Maximum selectable date
1031
+ date_format: Format of the default date, default is '%Y-%m-%d'
1032
+ """
1033
+ def decorator(func: Callable[..., Any]):
1034
+ def wrapper(sqrl: ParametersArgs):
1035
+ return cls.CreateSimple(
1036
+ name, label, default_start_date, default_end_date, description=description,
1037
+ min_date=min_date, max_date=max_date, date_format=date_format
1038
+ )
1039
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
1040
+ return wrapper
1041
+ return decorator
1042
+
1043
+ def get_selected_start_date(self, *, date_format: str | None = None, **kwargs) -> str:
1044
+ """
1045
+ Gets selected start date as string
1046
+
1047
+ Arguments:
1048
+ date_format: The date format (see Python's datetime formats). If not specified, self.date_format is used
1049
+
1050
+ Returns:
1051
+ A string
1052
+ """
1053
+ assert self._curr_option is not None and isinstance(self._selected_start_date, date), "Parameter is not enabled"
1054
+ date_format = self._curr_option._date_format if date_format is None else date_format
1055
+ return self._selected_start_date.strftime(date_format)
1056
+
1057
+ def get_selected_start_date_quoted(self, *, date_format: str | None = None, **kwargs) -> str:
1058
+ """
1059
+ Gets selected start date as string surrounded by single quotes
1060
+
1061
+ Arguments:
1062
+ date_format: The date format (see Python's datetime formats). If not specified, self.date_format is used
1063
+
1064
+ Returns:
1065
+ A string
1066
+ """
1067
+ return self._enquote(self.get_selected_start_date(date_format=date_format))
1068
+
1069
+ def get_selected_end_date(self, *, date_format: str | None = None, **kwargs) -> str:
1070
+ """
1071
+ Gets selected end date as string
1072
+
1073
+ Arguments:
1074
+ date_format: The date format (see Python's datetime formats). If not specified, self.date_format is used
1075
+
1076
+ Returns:
1077
+ A string
1078
+ """
1079
+ assert self._curr_option is not None and isinstance(self._selected_end_date, date), "Parameter is not enabled"
1080
+ date_format = self._curr_option._date_format if date_format is None else date_format
1081
+ return self._selected_end_date.strftime(date_format)
1082
+
1083
+ def get_selected_end_date_quoted(self, *, date_format: str | None = None, **kwargs) -> str:
1084
+ """
1085
+ Gets selected end date as string surrounded by single quotes
1086
+
1087
+ Arguments:
1088
+ date_format: The date format (see Python's datetime formats). If not specified, self.date_format is used
1089
+
1090
+ Returns:
1091
+ A string
1092
+ """
1093
+ return self._enquote(self.get_selected_end_date(date_format=date_format))
1094
+
1095
+ def _to_json_dict0(self):
1096
+ """
1097
+ Converts this parameter as a JSON object for the parameters API response
1098
+
1099
+ The "selected_date" field will always be in yyyy-mm-dd format
1100
+
1101
+ Returns:
1102
+ A dictionary for the JSON object
1103
+ """
1104
+ output = super()._to_json_dict0()
1105
+ if self.is_enabled():
1106
+ output["selected_start_date"] = self.get_selected_start_date(date_format="%Y-%m-%d")
1107
+ output["selected_end_date"] = self.get_selected_end_date(date_format="%Y-%m-%d")
1108
+ return output
1109
+
1110
+ def _get_response_model0(self):
1111
+ return rm.DateRangeParameterModel if self.is_enabled() else rm.NoneParameterModel
1112
+
1113
+
1114
+ NumericPO = TypeVar("NumericPO", bound=po._NumericParameterOption)
1115
+
1116
+ @dataclass
1117
+ class _NumberTypeParameter(Parameter[PC, NumericPO, DS], Generic[PC, NumericPO, DS]):
1118
+ _curr_option: NumericPO | None
1119
+
1120
+ def is_enabled(self) -> bool:
1121
+ return self._curr_option is not None
1122
+
1123
+ def _to_json_dict0(self):
1124
+ output = super()._to_json_dict0()
1125
+ if self._curr_option is not None:
1126
+ output["min_value"] = float(self._curr_option._min_value)
1127
+ output["max_value"] = float(self._curr_option._max_value)
1128
+ output["increment"] = float(self._curr_option._increment)
1129
+ return output
1130
+
1131
+
1132
+ @dataclass
1133
+ class NumberParameter(_NumberTypeParameter[pc.NumberParameterConfig, po.NumberParameterOption, d.NumberDataSource]):
1134
+ """
1135
+ Class for number parameter widgets.
1136
+
1137
+ Attributes:
1138
+ config: The config for this widget parameter (for immutable attributes like name, label, all possible options, etc)
1139
+ curr_option: The current option showing for defaults based on user attribute and selection of parent
1140
+ selected_value: The selected integer or decimal number
1141
+ """
1142
+ _selected_value: po.Number | None
1143
+
1144
+ def __post_init__(self):
1145
+ if self._curr_option is not None and self._selected_value is not None:
1146
+ self._selected_value = self._validate_number(self._selected_value, self._curr_option)
1147
+
1148
+ @staticmethod
1149
+ def _ParameterConfigType():
1150
+ return pc.NumberParameterConfig
1151
+
1152
+ @staticmethod
1153
+ def _ParameterOptionType():
1154
+ return po.NumberParameterOption
1155
+
1156
+ @staticmethod
1157
+ def _DataSourceType():
1158
+ return d.NumberDataSource
1159
+
1160
+ @classmethod
1161
+ def CreateSimple(
1162
+ cls, name: str, label: str, min_value: po.Number, max_value: po.Number, *, description: str = "",
1163
+ increment: po.Number = 1, default_value: po.Number | None = None, **kwargs
1164
+ ):
1165
+ """
1166
+ Method for creating the configurations for a NumberParameter that doesn't involve user attributes or parent parameters
1167
+
1168
+ * Note that the "Number" type denotes an int, a Decimal (from decimal module), or a string that can be parsed to Decimal
1169
+
1170
+ Arguments:
1171
+ name: The name of the parameter
1172
+ label: The display label for the parameter
1173
+ min_value: Minimum selectable value
1174
+ max_value: Maximum selectable value
1175
+ description: Explains the meaning of the parameter
1176
+ increment: Increment of selectable values, and must fit evenly between min_value and max_value
1177
+ default_value: Default value for this option, and must be selectable based on min_value, max_value, and increment
1178
+ """
1179
+ single_param_option = po.NumberParameterOption(min_value, max_value, increment=increment, default_value=default_value)
1180
+ return cls.CreateWithOptions(name, label, (single_param_option,), description=description)
1181
+
1182
+ @classmethod
1183
+ def create_simple(
1184
+ cls, name: str, label: str, min_value: po.Number, max_value: po.Number, *, description: str = "",
1185
+ increment: po.Number = 1, default_value: po.Number | None = None
1186
+ ):
1187
+ """
1188
+ Python decorator for creating the configurations for a NumberParameter that doesn't involve user attributes or parent parameters
1189
+
1190
+ * Note that the "Number" type denotes an int, a Decimal (from decimal module), or a string that can be parsed to Decimal
1191
+
1192
+ Arguments:
1193
+ name: The name of the parameter
1194
+ label: The display label for the parameter
1195
+ min_value: Minimum selectable value
1196
+ max_value: Maximum selectable value
1197
+ description: Explains the meaning of the parameter
1198
+ increment: Increment of selectable values, and must fit evenly between min_value and max_value
1199
+ default_value: Default value for the parameter
1200
+ """
1201
+ def decorator(func: Callable[..., Any]):
1202
+ def wrapper(sqrl: ParametersArgs):
1203
+ return cls.CreateSimple(
1204
+ name, label, min_value, max_value, description=description, increment=increment, default_value=default_value
1205
+ )
1206
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
1207
+ return wrapper
1208
+ return decorator
1209
+
1210
+ def get_selected_value(self, **kwargs) -> float:
1211
+ """
1212
+ Get the selected number (converted from Decimal to float)
1213
+
1214
+ Returns:
1215
+ float
1216
+ """
1217
+ assert self._selected_value is not None, "Parameter is not enabled"
1218
+ return float(self._selected_value)
1219
+
1220
+ def _to_json_dict0(self):
1221
+ """
1222
+ Converts this parameter as a JSON object for the parameters API response
1223
+
1224
+ Returns:
1225
+ A dictionary for the JSON object
1226
+ """
1227
+ output = super()._to_json_dict0()
1228
+ if self.is_enabled():
1229
+ output["selected_value"] = self.get_selected_value()
1230
+ return output
1231
+
1232
+ def _get_response_model0(self):
1233
+ return rm.NumberParameterModel if self.is_enabled() else rm.NoneParameterModel
1234
+
1235
+
1236
+ @dataclass
1237
+ class NumberRangeParameter(_NumberTypeParameter[pc.NumberRangeParameterConfig, po.NumberRangeParameterOption, d.NumberRangeDataSource]):
1238
+ """
1239
+ Class for number range parameter widgets.
1240
+
1241
+ Attributes:
1242
+ config: The config for this widget parameter (for immutable attributes like name, label, all possible options, etc)
1243
+ curr_option: The current option showing for defaults based on user attribute and selection of parent
1244
+ selected_lower_value: The selected lower integer or decimal number
1245
+ selected_upper_value: The selected upper integer or decimal number
1246
+ """
1247
+ _selected_lower_value: po.Number | None
1248
+ _selected_upper_value: po.Number | None
1249
+
1250
+ def __post_init__(self):
1251
+ if self._curr_option is not None:
1252
+ if self._selected_lower_value is not None:
1253
+ self._selected_lower_value = self._validate_number(self._selected_lower_value, self._curr_option)
1254
+ if self._selected_upper_value is not None:
1255
+ self._selected_upper_value = self._validate_number(self._selected_upper_value, self._curr_option)
1256
+
1257
+ @staticmethod
1258
+ def _ParameterConfigType():
1259
+ return pc.NumberRangeParameterConfig
1260
+
1261
+ @staticmethod
1262
+ def _ParameterOptionType():
1263
+ return po.NumberRangeParameterOption
1264
+
1265
+ @staticmethod
1266
+ def _DataSourceType():
1267
+ return d.NumberRangeDataSource
1268
+
1269
+ @classmethod
1270
+ def CreateSimple(
1271
+ cls, name: str, label: str, min_value: po.Number, max_value: po.Number, *, description: str = "",
1272
+ increment: po.Number = 1, default_lower_value: po.Number | None = None, default_upper_value: po.Number | None = None,**kwargs
1273
+ ):
1274
+ """
1275
+ Method for creating the configurations for a NumberRangeParameter that doesn't involve user attributes or parent parameters
1276
+
1277
+ * Note that the "Number" type denotes an int, a Decimal (from decimal module), or a string that can be parsed to Decimal
1278
+
1279
+ Arguments:
1280
+ name: The name of the parameter
1281
+ label: The display label for the parameter
1282
+ min_value: Minimum selectable value
1283
+ max_value: Maximum selectable value
1284
+ description: Explains the meaning of the parameter
1285
+ increment: Increment of selectable values, and must fit evenly between min_value and max_value
1286
+ default_lower_value: Default lower value for this option, and must be selectable based on min_value, max_value, and increment
1287
+ default_upper_value: Default upper value for this option, and must be selectable based on min_value, max_value, and increment.
1288
+ Must also be greater than default_lower_value
1289
+ """
1290
+ single_param_option = po.NumberRangeParameterOption(
1291
+ min_value, max_value, increment=increment, default_lower_value=default_lower_value, default_upper_value=default_upper_value
1292
+ )
1293
+ return cls.CreateWithOptions(name, label, (single_param_option,), description=description)
1294
+
1295
+ @classmethod
1296
+ def create_simple(
1297
+ cls, name: str, label: str, min_value: po.Number, max_value: po.Number, *, description: str = "",
1298
+ increment: po.Number = 1, default_lower_value: po.Number | None = None, default_upper_value: po.Number | None = None
1299
+ ):
1300
+ """
1301
+ Python decorator for creating the configurations for a NumberRangeParameter that doesn't involve user attributes or parent parameters
1302
+
1303
+ * Note that the "Number" type denotes an int, a Decimal (from decimal module), or a string that can be parsed to Decimal
1304
+
1305
+ Arguments:
1306
+ name: The name of the parameter
1307
+ label: The display label for the parameter
1308
+ min_value: Minimum selectable value
1309
+ max_value: Maximum selectable value
1310
+ description: Explains the meaning of the parameter
1311
+ increment: Increment of selectable values, and must fit evenly between min_value and max_value
1312
+ default_lower_value: Default lower value for this option, and must be selectable based on min_value, max_value, and increment
1313
+ default_upper_value: Default upper value for this option, and must be selectable based on min_value, max_value, and increment.
1314
+ Must also be greater than default_lower_value
1315
+ """
1316
+ def decorator(func: Callable[..., Any]):
1317
+ def wrapper(sqrl: ParametersArgs):
1318
+ return cls.CreateSimple(
1319
+ name, label, min_value, max_value, description=description, increment=increment,
1320
+ default_lower_value=default_lower_value, default_upper_value=default_upper_value
1321
+ )
1322
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
1323
+ return wrapper
1324
+ return decorator
1325
+
1326
+ def get_selected_lower_value(self, **kwargs) -> float:
1327
+ """
1328
+ Get the selected lower value number (converted from Decimal to float)
1329
+
1330
+ Returns:
1331
+ float
1332
+ """
1333
+ assert self._selected_lower_value is not None, "Parameter is not enabled"
1334
+ return float(self._selected_lower_value)
1335
+
1336
+ def get_selected_upper_value(self, **kwargs) -> float:
1337
+ """
1338
+ Get the selected upper value number (converted from Decimal to float)
1339
+
1340
+ Returns:
1341
+ float
1342
+ """
1343
+ assert self._selected_upper_value is not None, "Parameter is not enabled"
1344
+ return float(self._selected_upper_value)
1345
+
1346
+ def _to_json_dict0(self):
1347
+ """
1348
+ Converts this parameter as a JSON object for the parameters API response
1349
+
1350
+ Returns:
1351
+ A dictionary for the JSON object
1352
+ """
1353
+ output = super()._to_json_dict0()
1354
+ if self.is_enabled():
1355
+ output['selected_lower_value'] = self.get_selected_lower_value()
1356
+ output['selected_upper_value'] = self.get_selected_upper_value()
1357
+ return output
1358
+
1359
+ def _get_response_model0(self):
1360
+ return rm.NumberRangeParameterModel if self.is_enabled() else rm.NoneParameterModel
1361
+
1362
+
1363
+ @dataclass
1364
+ class TextValue:
1365
+ _value_do_not_touch: str
1366
+
1367
+ def __repr__(self):
1368
+ raise u.ConfigurationError(
1369
+ "Cannot convert TextValue directly to string (to avoid SQL injection). Try using it through placeholders instead"
1370
+ )
1371
+
1372
+ def apply(self, str_to_str_function: Callable[[str], str]) -> TextValue:
1373
+ """
1374
+ Transforms the entered text with a function that takes a string and returns a string.
1375
+
1376
+ This method returns a new object and leaves the original the same.
1377
+
1378
+ Arguments:
1379
+ str_to_str_function: A function that accepts a string and returns a string
1380
+
1381
+ Returns:
1382
+ A new TextValue with the transformed entered text
1383
+ """
1384
+ new_value = str_to_str_function(self._value_do_not_touch)
1385
+ if not isinstance(new_value, str):
1386
+ raise u.ConfigurationError("Function provided must return string")
1387
+ return TextValue(new_value)
1388
+
1389
+ def apply_percent_wrap(self) -> TextValue:
1390
+ """
1391
+ Adds percent signs before and after the entered text, and returns a new object, leaving the original the same.
1392
+
1393
+ Returns:
1394
+ A new TextValue with the transformed entered text
1395
+ """
1396
+ return self.apply(lambda x: "%"+x+"%")
1397
+
1398
+ def apply_as_bool(self, str_to_bool_function: Callable[[str], bool]) -> bool:
1399
+ """
1400
+ Transforms the entered text with a function that takes a string and returns a boolean.
1401
+
1402
+ Arguments:
1403
+ str_to_bool_function: A function that accepts a string and returns a boolean.
1404
+
1405
+ Returns:
1406
+ A boolean for the transformed value
1407
+ """
1408
+ new_value = str_to_bool_function(self._value_do_not_touch)
1409
+ if not isinstance(new_value, bool):
1410
+ raise u.ConfigurationError("Function provided must return bool")
1411
+ return new_value
1412
+
1413
+ def apply_as_number(self, str_to_num_function: Callable[[str], IntOrFloat]) -> IntOrFloat:
1414
+ """
1415
+ Transforms the entered text with a function that takes a string and returns an int or float.
1416
+
1417
+ Arguments:
1418
+ str_to_num_function: A function that accepts a string and returns an int or float.
1419
+
1420
+ Returns:
1421
+ An int or float for the transformed value
1422
+ """
1423
+ new_value = str_to_num_function(self._value_do_not_touch)
1424
+ if not isinstance(new_value, (int, float)):
1425
+ raise u.ConfigurationError("Function provided must return a number")
1426
+ return new_value
1427
+
1428
+ def apply_as_datetime(self, str_to_datetime_function: Callable[[str], datetime]) -> datetime:
1429
+ """
1430
+ Transforms the entered text with a function that takes a string and returns a datetime object.
1431
+
1432
+ Arguments:
1433
+ str_to_datetime_function: A function that accepts a string and returns a datetime object.
1434
+
1435
+ Returns:
1436
+ A datetime object for the transformed value
1437
+ """
1438
+ new_value = str_to_datetime_function(self._value_do_not_touch)
1439
+ if not isinstance(new_value, datetime):
1440
+ raise u.ConfigurationError("Function provided must return datetime")
1441
+ return new_value
1442
+
1443
+
1444
+ @dataclass
1445
+ class TextParameter(Parameter[pc.TextParameterConfig, po.TextParameterOption, d.TextDataSource]):
1446
+ """
1447
+ Class for text parameter widgets.
1448
+ """
1449
+ _curr_option: po.TextParameterOption | None
1450
+ _entered_text: str | None
1451
+
1452
+ def __post_init__(self):
1453
+ if self.is_enabled() and isinstance(self._entered_text, str):
1454
+ try:
1455
+ self._entered_text = self._config.validate_entered_text(self._entered_text)
1456
+ except u.ConfigurationError as e:
1457
+ raise self._config._invalid_input_error(self._entered_text, str(e))
1458
+
1459
+ def is_enabled(self) -> bool:
1460
+ return self._curr_option is not None
1461
+
1462
+ @staticmethod
1463
+ def _ParameterConfigType():
1464
+ return pc.TextParameterConfig
1465
+
1466
+ @staticmethod
1467
+ def _ParameterOptionType():
1468
+ return po.TextParameterOption
1469
+
1470
+ @staticmethod
1471
+ def _DataSourceType():
1472
+ return d.TextDataSource
1473
+
1474
+ @classmethod
1475
+ def CreateWithOptions(
1476
+ cls, name: str, label: str, all_options: Sequence[po.TextParameterOption | dict], *, description: str = "",
1477
+ input_type: str = "text", user_attribute: str | None = None, parent_name: str | None = None, **kwargs
1478
+ ):
1479
+ """
1480
+ Method for creating the configurations for a TextParameter that doesn't involve user attribute or parent
1481
+
1482
+ Arguments:
1483
+ name: The name of the parameter
1484
+ label: The display label for the parameter
1485
+ all_options: All options associated to this parameter regardless of the user group or parent parameter option they depend on
1486
+ description: Explains the meaning of the parameter
1487
+ input_type: The type of input field to use. Must be one of "text", "textarea", "number", "color", "date", "datetime-local", "month", "time", and "password". Optional, default is "text". More information on input types other than "textarea" can be found at https://www.w3schools.com/html/html_form_input_types.asp. More information on "textarea" can be found at https://www.w3schools.com/tags/tag_textarea.asp
1488
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
1489
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
1490
+ """
1491
+ return super().CreateWithOptions(
1492
+ name, label, all_options, description=description, input_type=input_type,
1493
+ user_attribute=user_attribute, parent_name=parent_name
1494
+ )
1495
+
1496
+ @classmethod
1497
+ def create_with_options(
1498
+ cls, name: str, label: str, *, description: str = "",
1499
+ input_type: str = "text", user_attribute: str | None = None, parent_name: str | None = None
1500
+ ):
1501
+ """
1502
+ Python decorator for creating the configurations for a TextParameter that may include user attribute or parent
1503
+
1504
+ The decorated function must return a list of TextParameterOption objects.
1505
+
1506
+ Arguments:
1507
+ name: The name of the parameter
1508
+ label: The display label for the parameter
1509
+ description: Explains the meaning of the parameter
1510
+ input_type: The type of input field to use. Must be one of "text", "textarea", "number", "color", "date", "datetime-local", "month", "time", and "password". Optional, default is "text". More information on input types other than "textarea" can be found at https://www.w3schools.com/html/html_form_input_types.asp. More information on "textarea" can be found at https://www.w3schools.com/tags/tag_textarea.asp
1511
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
1512
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
1513
+ """
1514
+ def decorator(func: Callable[..., Sequence[po.TextParameterOption]]):
1515
+ def wrapper(sqrl: ParametersArgs):
1516
+ options = u.call_func(func, sqrl=sqrl)
1517
+ return cls.CreateWithOptions(
1518
+ name, label, options, description=description, input_type=input_type,
1519
+ user_attribute=user_attribute, parent_name=parent_name
1520
+ )
1521
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
1522
+ return wrapper
1523
+ return decorator
1524
+
1525
+ @classmethod
1526
+ def CreateSimple(
1527
+ cls, name: str, label: str, *, description: str = "", default_text: str = "", input_type: str = "text", **kwargs
1528
+ ):
1529
+ """
1530
+ Method for creating the configurations for a TextParameter that doesn't involve user attributes or parent parameters
1531
+
1532
+ Arguments:
1533
+ name: The name of the parameter
1534
+ label: The display label for the parameter
1535
+ description: Explains the meaning of the parameter
1536
+ default_text: Default input text for this option. Optional, default is empty string.
1537
+ input_type: The type of input field to use. Must be one of "text", "textarea", "number", "color", "date", "datetime-local", "month", "time", and "password". Optional, default is "text". More information on input types other than "textarea" can be found at https://www.w3schools.com/html/html_form_input_types.asp. More information on "textarea" can be found at https://www.w3schools.com/tags/tag_textarea.asp
1538
+ """
1539
+ single_param_option = po.TextParameterOption(default_text=default_text)
1540
+ return cls.CreateWithOptions(name, label, (single_param_option,), description=description, input_type=input_type)
1541
+
1542
+ @classmethod
1543
+ def create_simple(cls, name: str, label: str, *, description: str = "", default_text: str = "", input_type: str = "text"):
1544
+ """
1545
+ Python decorator for creating the configurations for a TextParameter that doesn't involve user attributes or parent parameters
1546
+
1547
+ Arguments:
1548
+ name: The name of the parameter
1549
+ label: The display label for the parameter
1550
+ description: Explains the meaning of the parameter
1551
+ default_text: Default input text for this option. Optional, default is empty string.
1552
+ input_type: The type of input field to use. Must be one of "text", "textarea", "number", "color", "date", "datetime-local", "month", "time", and "password". Optional, default is "text". More information on input types other than "textarea" can be found at https://www.w3schools.com/html/html_form_input_types.asp. More information on "textarea" can be found at https://www.w3schools.com/tags/tag_textarea.asp
1553
+ """
1554
+ def decorator(func: Callable[..., Any]):
1555
+ def wrapper(sqrl: ParametersArgs):
1556
+ return cls.CreateSimple(name, label, description=description, default_text=default_text, input_type=input_type)
1557
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
1558
+ return wrapper
1559
+ return decorator
1560
+
1561
+ @classmethod
1562
+ def CreateFromSource(
1563
+ cls, name: str, label: str, data_source: d.TextDataSource | dict, *, description: str = "",
1564
+ input_type: str = "text", user_attribute: str | None = None, parent_name: str | None = None, **kwargs
1565
+ ):
1566
+ """
1567
+ Method for creating the configurations for a TextParameter that uses a TextDataSource to receive the options
1568
+
1569
+ Arguments:
1570
+ name: The name of the parameter
1571
+ label: The display label for the parameter
1572
+ data_source: The lookup table to use for this parameter
1573
+ description: Explains the meaning of the parameter
1574
+ input_type: The type of input field to use. Options are one of "text", "textarea", "number", "color", "date", "datetime-local", "month", "time", and "password". Optional, default is "text". More information on input types other than "textarea" can be found at https://www.w3schools.com/html/html_form_input_types.asp. More information on "textarea" can be found at https://www.w3schools.com/tags/tag_textarea.asp
1575
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
1576
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
1577
+ """
1578
+ extra_args = {
1579
+ "input_type": input_type
1580
+ }
1581
+ return cls._CreateFromSourceHelper(
1582
+ name, label, data_source, extra_args=extra_args, description=description, user_attribute=user_attribute, parent_name=parent_name
1583
+ )
1584
+
1585
+ @classmethod
1586
+ def create_from_source(
1587
+ cls, name: str, label: str, data_source: d.TextDataSource | dict, *, description: str = "",
1588
+ input_type: str = "text", user_attribute: str | None = None, parent_name: str | None = None
1589
+ ):
1590
+ """
1591
+ Python decorator for creating the configurations for a TextParameter that uses a TextDataSource to receive the options from a lookup table
1592
+
1593
+ The decorated function must return a TextDataSource object.
1594
+
1595
+ Arguments:
1596
+ name: The name of the parameter
1597
+ label: The display label for the parameter
1598
+ data_source: The lookup table to use for this parameter
1599
+ description: Explains the meaning of the parameter
1600
+ input_type: The type of input field to use. Options are one of "text", "textarea", "number", "color", "date", "datetime-local", "month", "time", and "password". Optional, default is "text". More information on input types other than "textarea" can be found at https://www.w3schools.com/html/html_form_input_types.asp. More information on "textarea" can be found at https://www.w3schools.com/tags/tag_textarea.asp
1601
+ user_attribute: The user attribute that may cascade the options for this parameter. Default is None
1602
+ parent_name: Name of parent parameter that may cascade the options for this parameter. Default is None (no parent)
1603
+ """
1604
+ def decorator(func: Callable[..., d.TextDataSource]):
1605
+ def wrapper(sqrl: ParametersArgs):
1606
+ data_source = u.call_func(func, sqrl=sqrl)
1607
+ return cls.CreateFromSource(
1608
+ name, label, data_source, description=description,
1609
+ input_type=input_type, user_attribute=user_attribute, parent_name=parent_name
1610
+ )
1611
+ ps.ParameterConfigsSetIO.param_factories.append(wrapper)
1612
+ return wrapper
1613
+ return decorator
1614
+
1615
+ def get_entered_text(self, **kwargs) -> TextValue:
1616
+ """
1617
+ Get the entered text. Returns a TextValue object that cannot be converted to string except through placeholders.
1618
+
1619
+ Returns:
1620
+ A TextValue object
1621
+ """
1622
+ assert isinstance(self._entered_text, str), "Parameter is not enabled"
1623
+ return TextValue(self._entered_text)
1624
+
1625
+ def get_entered_int(self, **kwargs) -> int:
1626
+ """
1627
+ Get the entered integer. The TextParameter must be a "number" input type
1628
+
1629
+ Returns: int
1630
+ """
1631
+ if self._config.input_type != "number":
1632
+ raise u.ConfigurationError("Method 'get_entered_int' requires TextParameter to have input type 'number'")
1633
+ text = self.get_entered_text()
1634
+ return text.apply_as_number(int)
1635
+
1636
+ def get_entered_datetime(self, **kwargs) -> datetime:
1637
+ """
1638
+ Get the entered datetime. The TextParameter input type must be one of ["date", "datetime-local", "month", "time"]
1639
+
1640
+ Returns: datetime
1641
+ """
1642
+ applicable_input_types = ["date", "datetime-local", "month", "time"]
1643
+ if self._config.input_type not in applicable_input_types:
1644
+ raise u.ConfigurationError(f"Method 'get_entered_datetime' requires TextParameter to have one of these input types: {applicable_input_types}")
1645
+ text = self.get_entered_text()
1646
+
1647
+ date_formats = { "date": "%Y-%m-%d", "datetime-local": "%Y-%m-%dT%H:%M", "month": "%Y-%m", "time": "%H:%M" }
1648
+ return text.apply_as_datetime(lambda x: datetime.strptime(x, date_formats[self._config.input_type]))
1649
+
1650
+ def _to_json_dict0(self):
1651
+ """
1652
+ Converts this parameter as a JSON object for the parameters API response
1653
+
1654
+ Returns:
1655
+ A dictionary for the JSON object
1656
+ """
1657
+ output = super()._to_json_dict0()
1658
+ output['input_type'] = self._config.input_type
1659
+ if self.is_enabled():
1660
+ output['entered_text'] = self._entered_text
1661
+ return output
1662
+
1663
+ def _get_response_model0(self):
1664
+ return rm.TextParameterModel if self.is_enabled() else rm.NoneParameterModel