squirrels 0.5.0b2__py3-none-any.whl → 0.5.0b3__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.
Files changed (77) hide show
  1. dateutils/__init__.py +6 -460
  2. dateutils/_enums.py +25 -0
  3. dateutils/_implementation.py +409 -0
  4. dateutils/types.py +6 -0
  5. squirrels/__init__.py +7 -13
  6. squirrels/_api_server.py +5 -5
  7. squirrels/{arguments/init_time_args.py → _arguments/_init_time_args.py} +2 -2
  8. squirrels/{arguments/run_time_args.py → _arguments/_run_time_args.py} +4 -26
  9. squirrels/_auth.py +2 -2
  10. squirrels/_connection_set.py +5 -5
  11. squirrels/_constants.py +1 -1
  12. squirrels/_dashboard_types.py +82 -0
  13. squirrels/_dashboards_io.py +2 -2
  14. squirrels/_data_sources.py +564 -0
  15. squirrels/_exceptions.py +1 -1
  16. squirrels/_initializer.py +31 -26
  17. squirrels/_manifest.py +5 -5
  18. squirrels/_model_configs.py +2 -2
  19. squirrels/_model_queries.py +1 -1
  20. squirrels/_models.py +28 -16
  21. squirrels/{package_data → _package_data}/base_project/dashboards/dashboard_example.py +4 -4
  22. squirrels/{package_data → _package_data}/base_project/dashboards/dashboard_example.yml +2 -2
  23. squirrels/_package_data/base_project/macros/macros_example.sql +17 -0
  24. squirrels/{package_data → _package_data}/base_project/models/builds/build_example.py +2 -2
  25. squirrels/{package_data → _package_data}/base_project/models/builds/build_example.sql +1 -1
  26. squirrels/{package_data → _package_data}/base_project/models/dbviews/dbview_example.sql +1 -1
  27. squirrels/_package_data/base_project/models/federates/federate_example.py +41 -0
  28. squirrels/_package_data/base_project/models/federates/federate_example.sql +25 -0
  29. squirrels/{package_data → _package_data}/base_project/models/federates/federate_example.yml +6 -6
  30. squirrels/{package_data → _package_data}/base_project/parameters.yml +9 -8
  31. squirrels/_package_data/base_project/pyconfigs/connections.py +14 -0
  32. squirrels/{package_data → _package_data}/base_project/pyconfigs/context.py +14 -16
  33. squirrels/{package_data → _package_data}/base_project/pyconfigs/parameters.py +13 -8
  34. squirrels/{package_data → _package_data}/base_project/pyconfigs/user.py +2 -2
  35. squirrels/_parameter_configs.py +34 -34
  36. squirrels/_parameter_options.py +348 -0
  37. squirrels/_parameter_sets.py +18 -18
  38. squirrels/_parameters.py +1266 -0
  39. squirrels/_project.py +37 -12
  40. squirrels/_utils.py +4 -2
  41. squirrels/arguments.py +2 -0
  42. squirrels/connections.py +1 -0
  43. squirrels/dashboards.py +1 -82
  44. squirrels/data_sources.py +8 -563
  45. squirrels/parameter_options.py +8 -348
  46. squirrels/parameters.py +9 -1266
  47. squirrels/types.py +11 -0
  48. {squirrels-0.5.0b2.dist-info → squirrels-0.5.0b3.dist-info}/METADATA +1 -1
  49. squirrels-0.5.0b3.dist-info/RECORD +80 -0
  50. squirrels/package_data/base_project/macros/macros_example.sql +0 -15
  51. squirrels/package_data/base_project/models/federates/federate_example.py +0 -44
  52. squirrels/package_data/base_project/models/federates/federate_example.sql +0 -17
  53. squirrels/package_data/base_project/pyconfigs/connections.py +0 -14
  54. squirrels-0.5.0b2.dist-info/RECORD +0 -70
  55. /squirrels/{dataset_result.py → _dataset_types.py} +0 -0
  56. /squirrels/{package_data → _package_data}/base_project/.env +0 -0
  57. /squirrels/{package_data → _package_data}/base_project/.env.example +0 -0
  58. /squirrels/{package_data → _package_data}/base_project/assets/expenses.db +0 -0
  59. /squirrels/{package_data → _package_data}/base_project/assets/weather.db +0 -0
  60. /squirrels/{package_data → _package_data}/base_project/connections.yml +0 -0
  61. /squirrels/{package_data → _package_data}/base_project/docker/.dockerignore +0 -0
  62. /squirrels/{package_data → _package_data}/base_project/docker/Dockerfile +0 -0
  63. /squirrels/{package_data → _package_data}/base_project/docker/compose.yml +0 -0
  64. /squirrels/{package_data → _package_data}/base_project/duckdb_init.sql +0 -0
  65. /squirrels/{package_data/base_project/.gitignore → _package_data/base_project/gitignore} +0 -0
  66. /squirrels/{package_data → _package_data}/base_project/models/builds/build_example.yml +0 -0
  67. /squirrels/{package_data → _package_data}/base_project/models/dbviews/dbview_example.yml +0 -0
  68. /squirrels/{package_data → _package_data}/base_project/models/sources.yml +0 -0
  69. /squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.csv +0 -0
  70. /squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.yml +0 -0
  71. /squirrels/{package_data → _package_data}/base_project/seeds/seed_subcategories.csv +0 -0
  72. /squirrels/{package_data → _package_data}/base_project/seeds/seed_subcategories.yml +0 -0
  73. /squirrels/{package_data → _package_data}/base_project/squirrels.yml.j2 +0 -0
  74. /squirrels/{package_data → _package_data}/base_project/tmp/.gitignore +0 -0
  75. {squirrels-0.5.0b2.dist-info → squirrels-0.5.0b3.dist-info}/WHEEL +0 -0
  76. {squirrels-0.5.0b2.dist-info → squirrels-0.5.0b3.dist-info}/entry_points.txt +0 -0
  77. {squirrels-0.5.0b2.dist-info → squirrels-0.5.0b3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,82 @@
1
+ import matplotlib.figure as figure, io, abc, typing
2
+
3
+ from . import _constants as c
4
+
5
+
6
+ class Dashboard(metaclass=abc.ABCMeta):
7
+ """
8
+ Abstract parent class for all Dashboard classes.
9
+ """
10
+
11
+ @property
12
+ @abc.abstractmethod
13
+ def _content(self) -> bytes | str:
14
+ pass
15
+
16
+ @property
17
+ @abc.abstractmethod
18
+ def _format(self) -> str:
19
+ pass
20
+
21
+
22
+ class PngDashboard(Dashboard):
23
+ """
24
+ Instantiate a Dashboard in PNG format from a matplotlib figure or bytes
25
+ """
26
+
27
+ def __init__(self, content: figure.Figure | io.BytesIO | bytes) -> None:
28
+ """
29
+ Constructor for PngDashboard
30
+
31
+ Arguments:
32
+ content: The content of the dashboard as a matplotlib.figure.Figure or bytes
33
+ """
34
+ if isinstance(content, figure.Figure):
35
+ buffer = io.BytesIO()
36
+ content.savefig(buffer, format=c.PNG)
37
+ content = buffer.getvalue()
38
+
39
+ if isinstance(content, io.BytesIO):
40
+ content = content.getvalue()
41
+
42
+ self.__content = content
43
+
44
+ @property
45
+ def _content(self) -> bytes:
46
+ return self.__content
47
+
48
+ @property
49
+ def _format(self) -> typing.Literal['png']:
50
+ return c.PNG
51
+
52
+ def _repr_png_(self):
53
+ return self._content
54
+
55
+
56
+ class HtmlDashboard(Dashboard):
57
+ """
58
+ Instantiate a Dashboard from an HTML string
59
+ """
60
+
61
+ def __init__(self, content: io.StringIO | str) -> None:
62
+ """
63
+ Constructor for HtmlDashboard
64
+
65
+ Arguments:
66
+ content: The content of the dashboard as HTML string
67
+ """
68
+ if isinstance(content, io.StringIO):
69
+ content = content.getvalue()
70
+
71
+ self.__content = content
72
+
73
+ @property
74
+ def _content(self) -> str:
75
+ return self.__content
76
+
77
+ @property
78
+ def _format(self) -> typing.Literal['html']:
79
+ return c.HTML
80
+
81
+ def _repr_html_(self):
82
+ return self._content
@@ -4,11 +4,11 @@ from dataclasses import dataclass
4
4
  from pydantic import BaseModel, Field
5
5
  import os, time
6
6
 
7
- from .arguments.run_time_args import DashboardArgs
7
+ from ._arguments._run_time_args import DashboardArgs
8
8
  from ._py_module import PyModule
9
9
  from ._manifest import AnalyticsOutputConfig
10
10
  from ._exceptions import InvalidInputError, ConfigurationError, FileExecutionError
11
- from . import _constants as c, _utils as u, dashboards as d
11
+ from . import _constants as c, _dashboard_types as d, _utils as u
12
12
 
13
13
  T = TypeVar('T', bound=d.Dashboard)
14
14
 
@@ -0,0 +1,564 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ import polars as pl, typing as t, abc
4
+
5
+ from . import _parameter_configs as pc, _parameter_options as po
6
+ from ._exceptions import ConfigurationError
7
+
8
+
9
+ @dataclass
10
+ class DataSource(metaclass=abc.ABCMeta):
11
+ """
12
+ Abstract class for lookup tables coming from a database
13
+ """
14
+ _table_or_query: str
15
+ _id_col: str | None
16
+ _is_from_seeds: bool
17
+ _user_group_col: str | None
18
+ _parent_id_col: str | None
19
+ _connection: str | None
20
+
21
+ @abc.abstractmethod
22
+ def __init__(
23
+ self, table_or_query: str, *, id_col: str | None = None, from_seeds: bool = False, user_group_col: str | None = None,
24
+ parent_id_col: str | None = None, connection: str | None = None, **kwargs
25
+ ) -> None:
26
+ self._table_or_query = table_or_query
27
+ self._id_col = id_col
28
+ self._is_from_seeds = from_seeds
29
+ self._user_group_col = user_group_col
30
+ self._parent_id_col = parent_id_col
31
+ self._connection = connection
32
+
33
+ def _get_connection_name(self, default_conn_name: str) -> str:
34
+ return self._connection if self._connection is not None else default_conn_name
35
+
36
+ def _get_query(self) -> str:
37
+ """
38
+ Get the "table_or_query" attribute as a select query
39
+
40
+ Returns:
41
+ str: The converted select query
42
+ """
43
+ if self._table_or_query.strip().lower().startswith('select '):
44
+ query = self._table_or_query
45
+ else:
46
+ query = f'SELECT * FROM {self._table_or_query}'
47
+ return query
48
+
49
+ @abc.abstractmethod
50
+ def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.ParameterConfig:
51
+ """
52
+ An abstract method for converting itself into a parameter
53
+ """
54
+ pass
55
+
56
+ def _validate_parameter_type(self, ds_param: pc.DataSourceParameterConfig, target_parameter_type: t.Type[pc.ParameterConfig]) -> None:
57
+ if ds_param.parameter_type != target_parameter_type:
58
+ parameter_type_name = ds_param.parameter_type.__name__
59
+ datasource_type_name = self.__class__.__name__
60
+ raise ConfigurationError(f'Invalid widget type "{parameter_type_name}" for {datasource_type_name}')
61
+
62
+ def _get_aggregated_df(self, df: pl.DataFrame, columns_to_include: t.Iterable[str]) -> pl.DataFrame:
63
+ if self._id_col is None:
64
+ return df
65
+
66
+ agg_rules = []
67
+ for column in columns_to_include:
68
+ if column is not None:
69
+ agg_rules.append(pl.first(column))
70
+ if self._user_group_col is not None:
71
+ agg_rules.append(pl.col(self._user_group_col))
72
+ if self._parent_id_col is not None:
73
+ agg_rules.append(pl.col(self._parent_id_col))
74
+
75
+ try:
76
+ df_agg = df.group_by(self._id_col).agg(agg_rules).sort(by=self._id_col)
77
+ except pl.exceptions.ColumnNotFoundError as e:
78
+ raise ConfigurationError(e)
79
+
80
+ return df_agg
81
+
82
+ def _get_key_from_record(self, key: str | None, record: dict[t.Hashable, t.Any], default: t.Any) -> t.Any:
83
+ return record[key] if key is not None else default
84
+
85
+ def _get_key_from_record_as_list(self, key: str | None, record: dict[t.Hashable, t.Any]) -> t.Iterable[str]:
86
+ value = self._get_key_from_record(key, record, list())
87
+ return [str(x) for x in value]
88
+
89
+
90
+ @dataclass
91
+ class _SelectionDataSource(DataSource):
92
+ """
93
+ Abstract class for selection parameter data sources
94
+ """
95
+ _options_col: str
96
+ _order_by_col: str | None
97
+ _is_default_col: str | None
98
+ _custom_cols: dict[str, str]
99
+
100
+ @abc.abstractmethod
101
+ def __init__(
102
+ self, table_or_query: str, id_col: str, options_col: str, *, order_by_col: str | None = None,
103
+ is_default_col: str | None = None, custom_cols: dict[str, str] = {}, from_seeds: bool = False,
104
+ user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None,
105
+ **kwargs
106
+ ) -> None:
107
+ super().__init__(
108
+ table_or_query, id_col=id_col, from_seeds=from_seeds, user_group_col=user_group_col, parent_id_col=parent_id_col,
109
+ connection=connection
110
+ )
111
+ self._options_col = options_col
112
+ self._order_by_col = order_by_col
113
+ self._is_default_col = is_default_col
114
+ self._custom_cols = custom_cols
115
+
116
+ def _get_all_options(self, df: pl.DataFrame) -> t.Sequence[po.SelectParameterOption]:
117
+ columns = [self._options_col, self._order_by_col, self._is_default_col, *self._custom_cols.values()]
118
+ df_agg = self._get_aggregated_df(df, columns)
119
+
120
+ if self._order_by_col is None:
121
+ df_agg = df_agg.sort(by=self._id_col)
122
+ else:
123
+ df_agg = df_agg.sort(by=self._order_by_col)
124
+
125
+ def get_is_default(record: dict[t.Hashable, t.Any]) -> bool:
126
+ return int(record[self._is_default_col]) == 1 if self._is_default_col is not None else False
127
+
128
+ def get_custom_fields(record: dict[t.Hashable, t.Any]) -> dict[str, t.Any]:
129
+ result = {}
130
+ for key, val in self._custom_cols.items():
131
+ result[key] = record[val]
132
+ return result
133
+
134
+ records = df_agg.to_pandas().to_dict("records")
135
+ return tuple(
136
+ po.SelectParameterOption(
137
+ str(record[self._id_col]), str(record[self._options_col]),
138
+ is_default=get_is_default(record), custom_fields=get_custom_fields(record),
139
+ user_groups=self._get_key_from_record_as_list(self._user_group_col, record),
140
+ parent_option_ids=self._get_key_from_record_as_list(self._parent_id_col, record)
141
+ )
142
+ for record in records
143
+ )
144
+
145
+
146
+ @dataclass
147
+ class SelectDataSource(_SelectionDataSource):
148
+ """
149
+ Lookup table for select parameter options
150
+ """
151
+
152
+ def __init__(
153
+ self, table_or_query: str, id_col: str, options_col: str, *, order_by_col: str | None = None,
154
+ is_default_col: str | None = None, custom_cols: dict[str, str] = {}, from_seeds: bool = False,
155
+ user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None,
156
+ **kwargs
157
+ ) -> None:
158
+ """
159
+ Constructor for SelectDataSource
160
+
161
+ Arguments:
162
+ table_or_query: Either the name of the table to use, or a query to run
163
+ id_col: The column name of the id
164
+ options_col: The column name of the options
165
+ order_by_col: The column name to order the options by. Orders by the id_col instead if this is None
166
+ is_default_col: The column name that indicates which options are the default
167
+ custom_cols: Dictionary of attribute to column name for custom fields for the SelectParameterOption
168
+ from_seeds: Boolean for whether this datasource is created from seeds
169
+ user_group_col: The column name of the user group that the user is in for this option to be valid
170
+ parent_id_col: The column name of the parent option id that must be selected for this option to be valid
171
+ connection: Name of the connection to use defined in connections.py
172
+ """
173
+ super().__init__(
174
+ table_or_query, id_col, options_col, order_by_col=order_by_col, is_default_col=is_default_col, custom_cols=custom_cols,
175
+ from_seeds=from_seeds, user_group_col=user_group_col, parent_id_col=parent_id_col, connection=connection
176
+ )
177
+
178
+ def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.SelectionParameterConfig:
179
+ """
180
+ Method to convert the associated DataSourceParameter into a SingleSelectParameterConfig or MultiSelectParameterConfig
181
+
182
+ Arguments:
183
+ ds_param: The parameter to convert
184
+ df: The dataframe containing the parameter options data
185
+
186
+ Returns:
187
+ The converted parameter
188
+ """
189
+ all_options = self._get_all_options(df)
190
+ if ds_param.parameter_type == pc.SingleSelectParameterConfig:
191
+ return pc.SingleSelectParameterConfig(
192
+ ds_param.name, ds_param.label, all_options, description=ds_param.description,
193
+ user_attribute=ds_param.user_attribute, parent_name=ds_param.parent_name, **ds_param.extra_args
194
+ )
195
+ elif ds_param.parameter_type == pc.MultiSelectParameterConfig:
196
+ return pc.MultiSelectParameterConfig(
197
+ ds_param.name, ds_param.label, all_options, description=ds_param.description,
198
+ user_attribute=ds_param.user_attribute, parent_name=ds_param.parent_name, **ds_param.extra_args
199
+ )
200
+ else:
201
+ raise ConfigurationError(f'Invalid widget type "{ds_param.parameter_type}" for SelectDataSource')
202
+
203
+
204
+ @dataclass
205
+ class DateDataSource(DataSource):
206
+ """
207
+ Lookup table for date parameter default options
208
+ """
209
+ _default_date_col: str
210
+ _date_format: str
211
+
212
+ def __init__(
213
+ self, table_or_query: str, default_date_col: str, *, min_date_col: str | None = None,
214
+ max_date_col: str | None = None, date_format: str = '%Y-%m-%d', id_col: str | None = None,
215
+ from_seeds: bool = False, user_group_col: str | None = None, parent_id_col: str | None = None,
216
+ connection: str | None = None, **kwargs
217
+ ) -> None:
218
+ """
219
+ Constructor for DateDataSource
220
+
221
+ Arguments:
222
+ table_or_query: Either the name of the table to use, or a query to run
223
+ default_date_col: The column name of the default date
224
+ date_format: The format of the default date(s). Defaults to '%Y-%m-%d'
225
+ id_col: The column name of the id
226
+ from_seeds: Boolean for whether this datasource is created from seeds
227
+ user_group_col: The column name of the user group that the user is in for this option to be valid
228
+ parent_id_col: The column name of the parent option id that the default date belongs to
229
+ connection: Name of the connection to use defined in connections.py
230
+ """
231
+ super().__init__(
232
+ table_or_query, id_col=id_col, from_seeds=from_seeds, user_group_col=user_group_col, parent_id_col=parent_id_col,
233
+ connection=connection
234
+ )
235
+ self._default_date_col = default_date_col
236
+ self._min_date_col = min_date_col
237
+ self._max_date_col = max_date_col
238
+ self._date_format = date_format
239
+
240
+ def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.DateParameterConfig:
241
+ """
242
+ Method to convert the associated DataSourceParameter into a DateParameterConfig
243
+
244
+ Arguments:
245
+ ds_param: The parameter to convert
246
+ df: The dataframe containing the parameter options data
247
+
248
+ Returns:
249
+ The converted parameter
250
+ """
251
+ self._validate_parameter_type(ds_param, pc.DateParameterConfig)
252
+
253
+ columns = [self._default_date_col, self._min_date_col, self._max_date_col]
254
+ df_agg = self._get_aggregated_df(df, columns)
255
+
256
+ records = df_agg.to_pandas().to_dict("records")
257
+ options = tuple(
258
+ po.DateParameterOption(
259
+ str(record[self._default_date_col]), date_format=self._date_format,
260
+ min_date = str(record[self._min_date_col]) if self._min_date_col else None,
261
+ max_date = str(record[self._max_date_col]) if self._max_date_col else None,
262
+ user_groups=self._get_key_from_record_as_list(self._user_group_col, record),
263
+ parent_option_ids=self._get_key_from_record_as_list(self._parent_id_col, record)
264
+ )
265
+ for record in records
266
+ )
267
+ return pc.DateParameterConfig(
268
+ ds_param.name, ds_param.label, options, description=ds_param.description, user_attribute=ds_param.user_attribute,
269
+ parent_name=ds_param.parent_name, **ds_param.extra_args
270
+ )
271
+
272
+
273
+ @dataclass
274
+ class DateRangeDataSource(DataSource):
275
+ """
276
+ Lookup table for date parameter default options
277
+ """
278
+ _default_start_date_col: str
279
+ _default_end_date_col: str
280
+ _date_format: str
281
+
282
+ def __init__(
283
+ self, table_or_query: str, default_start_date_col: str, default_end_date_col: str, *, date_format: str = '%Y-%m-%d',
284
+ min_date_col: str | None = None, max_date_col: str | None = None, id_col: str | None = None, from_seeds: bool = False,
285
+ user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None, **kwargs
286
+ ) -> None:
287
+ """
288
+ Constructor for DateRangeDataSource
289
+
290
+ Arguments:
291
+ table_or_query: Either the name of the table to use, or a query to run
292
+ default_start_date_col: The column name of the default start date
293
+ default_end_date_col: The column name of the default end date
294
+ date_format: The format of the default date(s). Defaults to '%Y-%m-%d'
295
+ id_col: The column name of the id
296
+ from_seeds: Boolean for whether this datasource is created from seeds
297
+ user_group_col: The column name of the user group that the user is in for this option to be valid
298
+ parent_id_col: The column name of the parent option id that the default date belongs to
299
+ connection: Name of the connection to use defined in connections.py
300
+ """
301
+ super().__init__(
302
+ table_or_query, id_col=id_col, from_seeds=from_seeds, user_group_col=user_group_col, parent_id_col=parent_id_col,
303
+ connection=connection
304
+ )
305
+ self._default_start_date_col = default_start_date_col
306
+ self._default_end_date_col = default_end_date_col
307
+ self._min_date_col = min_date_col
308
+ self._max_date_col = max_date_col
309
+ self._date_format = date_format
310
+
311
+ def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.DateRangeParameterConfig:
312
+ """
313
+ Method to convert the associated DataSourceParameter into a DateRangeParameterConfig
314
+
315
+ Arguments:
316
+ ds_param: The parameter to convert
317
+ df: The dataframe containing the parameter options data
318
+
319
+ Returns:
320
+ The converted parameter
321
+ """
322
+ self._validate_parameter_type(ds_param, pc.DateRangeParameterConfig)
323
+
324
+ columns = [self._default_start_date_col, self._default_end_date_col, self._min_date_col, self._max_date_col]
325
+ df_agg = self._get_aggregated_df(df, columns)
326
+
327
+ records = df_agg.to_pandas().to_dict("records")
328
+ options = tuple(
329
+ po.DateRangeParameterOption(
330
+ str(record[self._default_start_date_col]), str(record[self._default_end_date_col]),
331
+ min_date=str(record[self._min_date_col]) if self._min_date_col else None,
332
+ max_date=str(record[self._max_date_col]) if self._max_date_col else None,
333
+ date_format=self._date_format,
334
+ user_groups=self._get_key_from_record_as_list(self._user_group_col, record),
335
+ parent_option_ids=self._get_key_from_record_as_list(self._parent_id_col, record)
336
+ )
337
+ for record in records
338
+ )
339
+ return pc.DateRangeParameterConfig(
340
+ ds_param.name, ds_param.label, options, description=ds_param.description, user_attribute=ds_param.user_attribute,
341
+ parent_name=ds_param.parent_name, **ds_param.extra_args
342
+ )
343
+
344
+
345
+ @dataclass
346
+ class _NumericDataSource(DataSource):
347
+ """
348
+ Abstract class for number or number range data sources
349
+ """
350
+ _min_value_col: str
351
+ _max_value_col: str
352
+ _increment_col: str | None
353
+
354
+ @abc.abstractmethod
355
+ def __init__(
356
+ self, table_or_query: str, min_value_col: str, max_value_col: str, *, increment_col: str | None = None,
357
+ id_col: str | None = None, from_seeds: bool = False, user_group_col: str | None = None,
358
+ parent_id_col: str | None = None, connection: str | None = None, **kwargs
359
+ ) -> None:
360
+ super().__init__(
361
+ table_or_query, id_col=id_col, from_seeds=from_seeds, user_group_col=user_group_col, parent_id_col=parent_id_col,
362
+ connection=connection
363
+ )
364
+ self._min_value_col = min_value_col
365
+ self._max_value_col = max_value_col
366
+ self._increment_col = increment_col
367
+
368
+
369
+ @dataclass
370
+ class NumberDataSource(_NumericDataSource):
371
+ """
372
+ Lookup table for number parameter default options
373
+ """
374
+ _default_value_col: str | None
375
+
376
+ def __init__(
377
+ self, table_or_query: str, min_value_col: str, max_value_col: str, *, increment_col: str | None = None,
378
+ default_value_col: str | None = None, id_col: str | None = None, from_seeds: bool = False,
379
+ user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None, **kwargs
380
+ ) -> None:
381
+ """
382
+ Constructor for NumberDataSource
383
+
384
+ Arguments:
385
+ table_or_query: Either the name of the table to use, or a query to run
386
+ min_value_col: The column name of the minimum value
387
+ max_value_col: The column name of the maximum value
388
+ increment_col: The column name of the increment value. Defaults to column of 1's if None
389
+ default_value_col: The column name of the default value. Defaults to min_value_col if None
390
+ id_col: The column name of the id
391
+ from_seeds: Boolean for whether this datasource is created from seeds
392
+ user_group_col: The column name of the user group that the user is in for this option to be valid
393
+ parent_id_col: The column name of the parent option id that the default value belongs to
394
+ connection: Name of the connection to use defined in connections.py
395
+ """
396
+ super().__init__(
397
+ table_or_query, min_value_col, max_value_col, increment_col=increment_col, id_col=id_col, from_seeds=from_seeds,
398
+ user_group_col=user_group_col, parent_id_col=parent_id_col, connection=connection
399
+ )
400
+ self._default_value_col = default_value_col
401
+
402
+ def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.NumberParameterConfig:
403
+ """
404
+ Method to convert the associated DataSourceParameter into a NumberParameterConfig
405
+
406
+ Arguments:
407
+ ds_param: The parameter to convert
408
+ df: The dataframe containing the parameter options data
409
+
410
+ Returns:
411
+ The converted parameter
412
+ """
413
+ self._validate_parameter_type(ds_param, pc.NumberParameterConfig)
414
+
415
+ columns = [self._min_value_col, self._max_value_col, self._increment_col, self._default_value_col]
416
+ df_agg = self._get_aggregated_df(df, columns)
417
+
418
+ records = df_agg.to_pandas().to_dict("records")
419
+ options = tuple(
420
+ po.NumberParameterOption(
421
+ record[self._min_value_col], record[self._max_value_col],
422
+ increment=self._get_key_from_record(self._increment_col, record, 1),
423
+ default_value=self._get_key_from_record(self._default_value_col, record, None),
424
+ user_groups=self._get_key_from_record_as_list(self._user_group_col, record),
425
+ parent_option_ids=self._get_key_from_record_as_list(self._parent_id_col, record)
426
+ )
427
+ for record in records
428
+ )
429
+ return pc.NumberParameterConfig(
430
+ ds_param.name, ds_param.label, options, description=ds_param.description, user_attribute=ds_param.user_attribute,
431
+ parent_name=ds_param.parent_name, **ds_param.extra_args
432
+ )
433
+
434
+
435
+ @dataclass
436
+ class NumberRangeDataSource(_NumericDataSource):
437
+ """
438
+ Lookup table for number range parameter default options
439
+ """
440
+ _default_lower_value_col: str | None
441
+ _default_upper_value_col: str | None
442
+
443
+ def __init__(
444
+ self, table_or_query: str, min_value_col: str, max_value_col: str, *, increment_col: str | None = None,
445
+ default_lower_value_col: str | None = None, default_upper_value_col: str | None = None, id_col: str | None = None,
446
+ from_seeds: bool = False, user_group_col: str | None = None, parent_id_col: str | None = None,
447
+ connection: str | None = None, **kwargs
448
+ ) -> None:
449
+ """
450
+ Constructor for NumRangeDataSource
451
+
452
+ Arguments:
453
+ table_or_query: Either the name of the table to use, or a query to
454
+ min_value_col: The column name of the minimum value
455
+ max_value_col: The column name of the maximum value
456
+ increment_col: The column name of the increment value. Defaults to column of 1's if None
457
+ default_lower_value_col: The column name of the default lower value. Defaults to min_value_col if None
458
+ default_upper_value_col: The column name of the default upper value. Defaults to max_value_col if None
459
+ id_col: The column name of the id
460
+ from_seeds: Boolean for whether this datasource is created from seeds
461
+ user_group_col: The column name of the user group that the user is in for this option to be valid
462
+ parent_id_col: The column name of the parent option id that the default value belongs to
463
+ connection: Name of the connection to use defined in connections.py
464
+ """
465
+ super().__init__(
466
+ table_or_query, min_value_col, max_value_col, increment_col=increment_col, id_col=id_col, from_seeds=from_seeds,
467
+ user_group_col=user_group_col, parent_id_col=parent_id_col, connection=connection
468
+ )
469
+ self._default_lower_value_col = default_lower_value_col
470
+ self._default_upper_value_col = default_upper_value_col
471
+
472
+ def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.NumberRangeParameterConfig:
473
+ """
474
+ Method to convert the associated DataSourceParameter into a NumberRangeParameterConfig
475
+
476
+ Arguments:
477
+ ds_param: The parameter to convert
478
+ df: The dataframe containing the parameter options data
479
+
480
+ Returns:
481
+ The converted parameter
482
+ """
483
+ self._validate_parameter_type(ds_param, pc.NumberRangeParameterConfig)
484
+
485
+ columns = [self._min_value_col, self._max_value_col, self._increment_col, self._default_lower_value_col, self._default_upper_value_col]
486
+ df_agg = self._get_aggregated_df(df, columns)
487
+
488
+ records = df_agg.to_pandas().to_dict("records")
489
+ options = tuple(
490
+ po.NumberRangeParameterOption(
491
+ record[self._min_value_col], record[self._max_value_col],
492
+ increment=self._get_key_from_record(self._increment_col, record, 1),
493
+ default_lower_value=self._get_key_from_record(self._default_lower_value_col, record, None),
494
+ default_upper_value=self._get_key_from_record(self._default_upper_value_col, record, None),
495
+ user_groups=self._get_key_from_record_as_list(self._user_group_col, record),
496
+ parent_option_ids=self._get_key_from_record_as_list(self._parent_id_col, record)
497
+ )
498
+ for record in records
499
+ )
500
+ return pc.NumberRangeParameterConfig(
501
+ ds_param.name, ds_param.label, options, description=ds_param.description, user_attribute=ds_param.user_attribute,
502
+ parent_name=ds_param.parent_name, **ds_param.extra_args
503
+ )
504
+
505
+
506
+ @dataclass
507
+ class TextDataSource(DataSource):
508
+ """
509
+ Lookup table for text parameter default options
510
+ """
511
+ _default_text_col: str
512
+
513
+ def __init__(
514
+ self, table_or_query: str, default_text_col: str, *, id_col: str | None = None, from_seeds: bool = False,
515
+ user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None,
516
+ **kwargs
517
+ ) -> None:
518
+ """
519
+ Constructor for TextDataSource
520
+
521
+ Arguments:
522
+ table_or_query: Either the name of the table to use, or a query to run
523
+ default_text_col: The column name of the default text
524
+ id_col: The column name of the id
525
+ from_seeds: Boolean for whether this datasource is created from seeds
526
+ user_group_col: The column name of the user group that the user is in for this option to be valid
527
+ parent_id_col: The column name of the parent option id that the default date belongs to
528
+ connection: Name of the connection to use defined in connections.py
529
+ """
530
+ super().__init__(
531
+ table_or_query, id_col=id_col, from_seeds=from_seeds, user_group_col=user_group_col, parent_id_col=parent_id_col,
532
+ connection=connection
533
+ )
534
+ self._default_text_col = default_text_col
535
+
536
+ def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.TextParameterConfig:
537
+ """
538
+ Method to convert the associated DataSourceParameter into a TextParameterConfig
539
+
540
+ Arguments:
541
+ ds_param: The parameter to convert
542
+ df: The dataframe containing the parameter options data
543
+
544
+ Returns:
545
+ The converted parameter
546
+ """
547
+ self._validate_parameter_type(ds_param, pc.TextParameterConfig)
548
+
549
+ columns = [self._default_text_col]
550
+ df_agg = self._get_aggregated_df(df, columns)
551
+
552
+ records = df_agg.to_pandas().to_dict("records")
553
+ options = tuple(
554
+ po.TextParameterOption(
555
+ default_text=str(record[self._default_text_col]),
556
+ user_groups=self._get_key_from_record_as_list(self._user_group_col, record),
557
+ parent_option_ids=self._get_key_from_record_as_list(self._parent_id_col, record)
558
+ )
559
+ for record in records
560
+ )
561
+ return pc.TextParameterConfig(
562
+ ds_param.name, ds_param.label, options, description=ds_param.description, user_attribute=ds_param.user_attribute,
563
+ parent_name=ds_param.parent_name, **ds_param.extra_args
564
+ )
squirrels/_exceptions.py CHANGED
@@ -20,7 +20,7 @@ class InvalidInputError(Exception):
20
20
  21 - Authorized user is forbidden to delete users
21
21
  22 - Cannot delete your own user
22
22
  23 - Cannot delete the admin user
23
- 24 - Cannot change the admin user
23
+ 24 - Setting the admin user to non-admin is not permitted
24
24
  25 - User does not have permission to access the dataset / dashboard
25
25
  26 - User does not have permission to build the virtual data environment
26
26
  27 - User does not have permission to query data models