squirrels 0.5.0b3__py3-none-any.whl → 0.6.0.post0__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.
- squirrels/__init__.py +4 -0
- squirrels/_api_routes/__init__.py +5 -0
- squirrels/_api_routes/auth.py +337 -0
- squirrels/_api_routes/base.py +196 -0
- squirrels/_api_routes/dashboards.py +156 -0
- squirrels/_api_routes/data_management.py +148 -0
- squirrels/_api_routes/datasets.py +220 -0
- squirrels/_api_routes/project.py +289 -0
- squirrels/_api_server.py +440 -792
- squirrels/_arguments/__init__.py +0 -0
- squirrels/_arguments/{_init_time_args.py → init_time_args.py} +23 -43
- squirrels/_arguments/{_run_time_args.py → run_time_args.py} +32 -68
- squirrels/_auth.py +590 -264
- squirrels/_command_line.py +130 -58
- squirrels/_compile_prompts.py +147 -0
- squirrels/_connection_set.py +16 -15
- squirrels/_constants.py +36 -11
- squirrels/_dashboards.py +179 -0
- squirrels/_data_sources.py +40 -34
- squirrels/_dataset_types.py +16 -11
- squirrels/_env_vars.py +209 -0
- squirrels/_exceptions.py +9 -37
- squirrels/_http_error_responses.py +52 -0
- squirrels/_initializer.py +7 -6
- squirrels/_logging.py +121 -0
- squirrels/_manifest.py +155 -77
- squirrels/_mcp_server.py +578 -0
- squirrels/_model_builder.py +11 -55
- squirrels/_model_configs.py +5 -5
- squirrels/_model_queries.py +1 -1
- squirrels/_models.py +276 -143
- squirrels/_package_data/base_project/.env +1 -24
- squirrels/_package_data/base_project/.env.example +31 -17
- squirrels/_package_data/base_project/connections.yml +4 -3
- squirrels/_package_data/base_project/dashboards/dashboard_example.py +13 -7
- squirrels/_package_data/base_project/dashboards/dashboard_example.yml +6 -6
- squirrels/_package_data/base_project/docker/Dockerfile +2 -2
- squirrels/_package_data/base_project/docker/compose.yml +1 -1
- squirrels/_package_data/base_project/duckdb_init.sql +1 -0
- squirrels/_package_data/base_project/models/builds/build_example.py +2 -2
- squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +7 -2
- squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +16 -10
- squirrels/_package_data/base_project/models/federates/federate_example.py +27 -17
- squirrels/_package_data/base_project/models/federates/federate_example.sql +3 -7
- squirrels/_package_data/base_project/models/federates/federate_example.yml +7 -7
- squirrels/_package_data/base_project/models/sources.yml +5 -6
- squirrels/_package_data/base_project/parameters.yml +24 -38
- squirrels/_package_data/base_project/pyconfigs/connections.py +8 -3
- squirrels/_package_data/base_project/pyconfigs/context.py +26 -14
- squirrels/_package_data/base_project/pyconfigs/parameters.py +124 -81
- squirrels/_package_data/base_project/pyconfigs/user.py +48 -15
- squirrels/_package_data/base_project/resources/public/.gitkeep +0 -0
- squirrels/_package_data/base_project/seeds/seed_categories.yml +1 -1
- squirrels/_package_data/base_project/seeds/seed_subcategories.yml +1 -1
- squirrels/_package_data/base_project/squirrels.yml.j2 +21 -31
- squirrels/_package_data/templates/login_successful.html +53 -0
- squirrels/_package_data/templates/squirrels_studio.html +22 -0
- squirrels/_parameter_configs.py +43 -22
- squirrels/_parameter_options.py +1 -1
- squirrels/_parameter_sets.py +41 -30
- squirrels/_parameters.py +560 -123
- squirrels/_project.py +487 -277
- squirrels/_py_module.py +71 -10
- squirrels/_request_context.py +33 -0
- squirrels/_schemas/__init__.py +0 -0
- squirrels/_schemas/auth_models.py +83 -0
- squirrels/_schemas/query_param_models.py +70 -0
- squirrels/_schemas/request_models.py +26 -0
- squirrels/_schemas/response_models.py +286 -0
- squirrels/_seeds.py +52 -13
- squirrels/_sources.py +29 -23
- squirrels/_utils.py +221 -42
- squirrels/_version.py +1 -3
- squirrels/arguments.py +7 -2
- squirrels/auth.py +4 -0
- squirrels/connections.py +2 -0
- squirrels/dashboards.py +3 -1
- squirrels/data_sources.py +6 -0
- squirrels/parameter_options.py +5 -0
- squirrels/parameters.py +5 -0
- squirrels/types.py +10 -3
- squirrels-0.6.0.post0.dist-info/METADATA +148 -0
- squirrels-0.6.0.post0.dist-info/RECORD +101 -0
- {squirrels-0.5.0b3.dist-info → squirrels-0.6.0.post0.dist-info}/WHEEL +1 -1
- squirrels/_api_response_models.py +0 -190
- squirrels/_dashboard_types.py +0 -82
- squirrels/_dashboards_io.py +0 -79
- squirrels-0.5.0b3.dist-info/METADATA +0 -110
- squirrels-0.5.0b3.dist-info/RECORD +0 -80
- /squirrels/_package_data/base_project/{assets → resources}/expenses.db +0 -0
- /squirrels/_package_data/base_project/{assets → resources}/weather.db +0 -0
- {squirrels-0.5.0b3.dist-info → squirrels-0.6.0.post0.dist-info}/entry_points.txt +0 -0
- {squirrels-0.5.0b3.dist-info → squirrels-0.6.0.post0.dist-info}/licenses/LICENSE +0 -0
squirrels/_dashboards.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
from typing import Type, TypeVar, Callable, Coroutine, Any
|
|
2
|
+
from typing_extensions import Self
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator, ValidationError
|
|
6
|
+
import matplotlib.figure as figure
|
|
7
|
+
import os, time, io, abc, typing
|
|
8
|
+
|
|
9
|
+
from ._arguments.run_time_args import DashboardArgs
|
|
10
|
+
from ._py_module import PyModule
|
|
11
|
+
from ._manifest import AnalyticsOutputConfig, AuthType, PermissionScope, ConfigurableOverride
|
|
12
|
+
from ._exceptions import InvalidInputError, ConfigurationError, FileExecutionError
|
|
13
|
+
from . import _constants as c, _utils as u
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Dashboard(metaclass=abc.ABCMeta):
|
|
17
|
+
"""
|
|
18
|
+
Abstract parent class for all Dashboard classes.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
@abc.abstractmethod
|
|
23
|
+
def _content(self) -> bytes | str:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
@abc.abstractmethod
|
|
28
|
+
def _format(self) -> str:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PngDashboard(Dashboard):
|
|
33
|
+
"""
|
|
34
|
+
Instantiate a Dashboard in PNG format from a matplotlib figure or bytes
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, content: figure.Figure | io.BytesIO | bytes) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Constructor for PngDashboard
|
|
40
|
+
|
|
41
|
+
Arguments:
|
|
42
|
+
content: The content of the dashboard as a matplotlib.figure.Figure or bytes
|
|
43
|
+
"""
|
|
44
|
+
if isinstance(content, figure.Figure):
|
|
45
|
+
buffer = io.BytesIO()
|
|
46
|
+
content.savefig(buffer, format=c.PNG)
|
|
47
|
+
content = buffer.getvalue()
|
|
48
|
+
|
|
49
|
+
if isinstance(content, io.BytesIO):
|
|
50
|
+
content = content.getvalue()
|
|
51
|
+
|
|
52
|
+
self.__content = content
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def _content(self) -> bytes:
|
|
56
|
+
return self.__content
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def _format(self) -> typing.Literal['png']:
|
|
60
|
+
return c.PNG
|
|
61
|
+
|
|
62
|
+
def _repr_png_(self):
|
|
63
|
+
return self._content
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class HtmlDashboard(Dashboard):
|
|
67
|
+
"""
|
|
68
|
+
Instantiate a Dashboard from an HTML string
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, content: io.StringIO | str) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Constructor for HtmlDashboard
|
|
74
|
+
|
|
75
|
+
Arguments:
|
|
76
|
+
content: The content of the dashboard as HTML string
|
|
77
|
+
"""
|
|
78
|
+
if isinstance(content, io.StringIO):
|
|
79
|
+
content = content.getvalue()
|
|
80
|
+
|
|
81
|
+
self.__content = content
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def _content(self) -> str:
|
|
85
|
+
return self.__content
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def _format(self) -> typing.Literal['html']:
|
|
89
|
+
return c.HTML
|
|
90
|
+
|
|
91
|
+
def _repr_html_(self):
|
|
92
|
+
return self._content
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
T = TypeVar('T', bound=Dashboard)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DashboardFormat(Enum):
|
|
99
|
+
PNG = "png"
|
|
100
|
+
HTML = "html"
|
|
101
|
+
|
|
102
|
+
class DashboardDependencies(BaseModel):
|
|
103
|
+
name: str
|
|
104
|
+
dataset: str
|
|
105
|
+
fixed_parameters: dict[str, str] = Field(default_factory=dict)
|
|
106
|
+
|
|
107
|
+
class DashboardConfig(AnalyticsOutputConfig):
|
|
108
|
+
format: DashboardFormat = Field(default=DashboardFormat.PNG)
|
|
109
|
+
depends_on: list[DashboardDependencies] = Field(default_factory=list)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class DashboardDefinition:
|
|
114
|
+
dashboard_name: str
|
|
115
|
+
filepath: str
|
|
116
|
+
config: DashboardConfig
|
|
117
|
+
project_path: str
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def dashboard_func(self) -> Callable[[DashboardArgs], Coroutine[Any, Any, Dashboard]]:
|
|
121
|
+
if not hasattr(self, '_dashboard_func'):
|
|
122
|
+
module = PyModule(self.filepath, self.project_path)
|
|
123
|
+
self._dashboard_func = module.get_func_or_class(c.MAIN_FUNC)
|
|
124
|
+
return self._dashboard_func
|
|
125
|
+
|
|
126
|
+
def get_dashboard_format(self) -> str:
|
|
127
|
+
return self.config.format.value
|
|
128
|
+
|
|
129
|
+
async def get_dashboard(self, args: DashboardArgs, *, dashboard_type: Type[T] = Dashboard) -> T:
|
|
130
|
+
try:
|
|
131
|
+
dashboard = await self.dashboard_func(args)
|
|
132
|
+
assert isinstance(dashboard, dashboard_type), f"Function does not return expected Dashboard type: {dashboard_type}"
|
|
133
|
+
except (InvalidInputError, ConfigurationError, FileExecutionError) as e:
|
|
134
|
+
raise e
|
|
135
|
+
except Exception as e:
|
|
136
|
+
raise FileExecutionError(f'Failed to run "{c.MAIN_FUNC}" function for dashboard "{self.dashboard_name}"', e) from e
|
|
137
|
+
|
|
138
|
+
return dashboard
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class DashboardsIO:
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def load_files(
|
|
145
|
+
cls, logger: u.Logger, project_path: str, auth_type: AuthType = AuthType.OPTIONAL, project_configurables: dict[str, Any] | None = None
|
|
146
|
+
) -> dict[str, DashboardDefinition]:
|
|
147
|
+
start = time.time()
|
|
148
|
+
|
|
149
|
+
if project_configurables is None:
|
|
150
|
+
project_configurables = {}
|
|
151
|
+
|
|
152
|
+
default_scope = PermissionScope.PROTECTED if auth_type == AuthType.REQUIRED else PermissionScope.PUBLIC
|
|
153
|
+
|
|
154
|
+
dashboards_by_name = {}
|
|
155
|
+
for dp, _, filenames in os.walk(u.Path(project_path, c.DASHBOARDS_FOLDER)):
|
|
156
|
+
for file in filenames:
|
|
157
|
+
filepath = os.path.join(dp, file)
|
|
158
|
+
file_stem, extension = os.path.splitext(file)
|
|
159
|
+
if not extension == '.py':
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
# Check for corresponding .yml file
|
|
163
|
+
yml_path = os.path.join(dp, file_stem + '.yml')
|
|
164
|
+
config_dict = u.load_yaml_config(yml_path) if os.path.exists(yml_path) else {}
|
|
165
|
+
try:
|
|
166
|
+
config = DashboardConfig(name=file_stem, project_configurables=project_configurables, **config_dict)
|
|
167
|
+
except ValidationError as e:
|
|
168
|
+
raise ConfigurationError(f'Failed to process dashboard config "{file_stem}". ' + str(e)) from e
|
|
169
|
+
|
|
170
|
+
if config.scope is None:
|
|
171
|
+
config.scope = default_scope
|
|
172
|
+
|
|
173
|
+
if auth_type == AuthType.REQUIRED and config.scope == PermissionScope.PUBLIC:
|
|
174
|
+
raise ConfigurationError(f'Authentication is required, so dashboard "{file_stem}" cannot be public. Update the scope in "{yml_path}"')
|
|
175
|
+
|
|
176
|
+
dashboards_by_name[file_stem] = DashboardDefinition(file_stem, filepath, config, project_path)
|
|
177
|
+
|
|
178
|
+
logger.log_activity_time("loading files for dashboards", start)
|
|
179
|
+
return dashboards_by_name
|
squirrels/_data_sources.py
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
|
|
3
|
+
from enum import Enum
|
|
4
|
+
import polars as pl, typing as t, abc, re
|
|
4
5
|
|
|
5
6
|
from . import _parameter_configs as pc, _parameter_options as po
|
|
6
7
|
from ._exceptions import ConfigurationError
|
|
7
8
|
|
|
9
|
+
class SourceEnum(Enum):
|
|
10
|
+
CONNECTION = "connection"
|
|
11
|
+
SEEDS = "seeds"
|
|
12
|
+
VDL = "vdl"
|
|
13
|
+
|
|
8
14
|
|
|
9
15
|
@dataclass
|
|
10
16
|
class DataSource(metaclass=abc.ABCMeta):
|
|
@@ -13,19 +19,19 @@ class DataSource(metaclass=abc.ABCMeta):
|
|
|
13
19
|
"""
|
|
14
20
|
_table_or_query: str
|
|
15
21
|
_id_col: str | None
|
|
16
|
-
|
|
22
|
+
_source: SourceEnum
|
|
17
23
|
_user_group_col: str | None
|
|
18
24
|
_parent_id_col: str | None
|
|
19
25
|
_connection: str | None
|
|
20
26
|
|
|
21
27
|
@abc.abstractmethod
|
|
22
28
|
def __init__(
|
|
23
|
-
self, table_or_query: str, *, id_col: str | None = None,
|
|
24
|
-
parent_id_col: str | None = None, connection: str | None = None, **kwargs
|
|
29
|
+
self, table_or_query: str, *, id_col: str | None = None, source: SourceEnum = SourceEnum.CONNECTION,
|
|
30
|
+
user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None, **kwargs
|
|
25
31
|
) -> None:
|
|
26
32
|
self._table_or_query = table_or_query
|
|
27
33
|
self._id_col = id_col
|
|
28
|
-
self.
|
|
34
|
+
self._source = source
|
|
29
35
|
self._user_group_col = user_group_col
|
|
30
36
|
self._parent_id_col = parent_id_col
|
|
31
37
|
self._connection = connection
|
|
@@ -40,7 +46,7 @@ class DataSource(metaclass=abc.ABCMeta):
|
|
|
40
46
|
Returns:
|
|
41
47
|
str: The converted select query
|
|
42
48
|
"""
|
|
43
|
-
if self._table_or_query.strip().
|
|
49
|
+
if re.match(r'select\s', self._table_or_query.strip(), re.IGNORECASE):
|
|
44
50
|
query = self._table_or_query
|
|
45
51
|
else:
|
|
46
52
|
query = f'SELECT * FROM {self._table_or_query}'
|
|
@@ -100,12 +106,12 @@ class _SelectionDataSource(DataSource):
|
|
|
100
106
|
@abc.abstractmethod
|
|
101
107
|
def __init__(
|
|
102
108
|
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] = {},
|
|
109
|
+
is_default_col: str | None = None, custom_cols: dict[str, str] = {}, source: SourceEnum = SourceEnum.CONNECTION,
|
|
104
110
|
user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None,
|
|
105
111
|
**kwargs
|
|
106
112
|
) -> None:
|
|
107
113
|
super().__init__(
|
|
108
|
-
table_or_query, id_col=id_col,
|
|
114
|
+
table_or_query, id_col=id_col, source=source, user_group_col=user_group_col, parent_id_col=parent_id_col,
|
|
109
115
|
connection=connection
|
|
110
116
|
)
|
|
111
117
|
self._options_col = options_col
|
|
@@ -151,7 +157,7 @@ class SelectDataSource(_SelectionDataSource):
|
|
|
151
157
|
|
|
152
158
|
def __init__(
|
|
153
159
|
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] = {},
|
|
160
|
+
is_default_col: str | None = None, custom_cols: dict[str, str] = {}, source: SourceEnum = SourceEnum.CONNECTION,
|
|
155
161
|
user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None,
|
|
156
162
|
**kwargs
|
|
157
163
|
) -> None:
|
|
@@ -165,19 +171,19 @@ class SelectDataSource(_SelectionDataSource):
|
|
|
165
171
|
order_by_col: The column name to order the options by. Orders by the id_col instead if this is None
|
|
166
172
|
is_default_col: The column name that indicates which options are the default
|
|
167
173
|
custom_cols: Dictionary of attribute to column name for custom fields for the SelectParameterOption
|
|
168
|
-
|
|
174
|
+
source: The source to fetch data from. Must be "connection", "seeds", or "vdl". Defaults to "connection"
|
|
169
175
|
user_group_col: The column name of the user group that the user is in for this option to be valid
|
|
170
176
|
parent_id_col: The column name of the parent option id that must be selected for this option to be valid
|
|
171
177
|
connection: Name of the connection to use defined in connections.py
|
|
172
178
|
"""
|
|
173
179
|
super().__init__(
|
|
174
180
|
table_or_query, id_col, options_col, order_by_col=order_by_col, is_default_col=is_default_col, custom_cols=custom_cols,
|
|
175
|
-
|
|
181
|
+
source=source, user_group_col=user_group_col, parent_id_col=parent_id_col, connection=connection
|
|
176
182
|
)
|
|
177
183
|
|
|
178
184
|
def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.SelectionParameterConfig:
|
|
179
185
|
"""
|
|
180
|
-
Method to convert the associated
|
|
186
|
+
Method to convert the associated DataSourceParameterConfig into a SingleSelectParameterConfig or MultiSelectParameterConfig
|
|
181
187
|
|
|
182
188
|
Arguments:
|
|
183
189
|
ds_param: The parameter to convert
|
|
@@ -212,7 +218,7 @@ class DateDataSource(DataSource):
|
|
|
212
218
|
def __init__(
|
|
213
219
|
self, table_or_query: str, default_date_col: str, *, min_date_col: str | None = None,
|
|
214
220
|
max_date_col: str | None = None, date_format: str = '%Y-%m-%d', id_col: str | None = None,
|
|
215
|
-
|
|
221
|
+
source: SourceEnum = SourceEnum.CONNECTION, user_group_col: str | None = None, parent_id_col: str | None = None,
|
|
216
222
|
connection: str | None = None, **kwargs
|
|
217
223
|
) -> None:
|
|
218
224
|
"""
|
|
@@ -223,13 +229,13 @@ class DateDataSource(DataSource):
|
|
|
223
229
|
default_date_col: The column name of the default date
|
|
224
230
|
date_format: The format of the default date(s). Defaults to '%Y-%m-%d'
|
|
225
231
|
id_col: The column name of the id
|
|
226
|
-
|
|
232
|
+
source: The source to fetch data from. Must be "connection", "seeds", or "vdl". Defaults to "connection"
|
|
227
233
|
user_group_col: The column name of the user group that the user is in for this option to be valid
|
|
228
234
|
parent_id_col: The column name of the parent option id that the default date belongs to
|
|
229
235
|
connection: Name of the connection to use defined in connections.py
|
|
230
236
|
"""
|
|
231
237
|
super().__init__(
|
|
232
|
-
table_or_query, id_col=id_col,
|
|
238
|
+
table_or_query, id_col=id_col, source=source, user_group_col=user_group_col, parent_id_col=parent_id_col,
|
|
233
239
|
connection=connection
|
|
234
240
|
)
|
|
235
241
|
self._default_date_col = default_date_col
|
|
@@ -239,7 +245,7 @@ class DateDataSource(DataSource):
|
|
|
239
245
|
|
|
240
246
|
def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.DateParameterConfig:
|
|
241
247
|
"""
|
|
242
|
-
Method to convert the associated
|
|
248
|
+
Method to convert the associated DataSourceParameterConfig into a DateParameterConfig
|
|
243
249
|
|
|
244
250
|
Arguments:
|
|
245
251
|
ds_param: The parameter to convert
|
|
@@ -281,7 +287,7 @@ class DateRangeDataSource(DataSource):
|
|
|
281
287
|
|
|
282
288
|
def __init__(
|
|
283
289
|
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,
|
|
290
|
+
min_date_col: str | None = None, max_date_col: str | None = None, id_col: str | None = None, source: SourceEnum = SourceEnum.CONNECTION,
|
|
285
291
|
user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None, **kwargs
|
|
286
292
|
) -> None:
|
|
287
293
|
"""
|
|
@@ -293,13 +299,13 @@ class DateRangeDataSource(DataSource):
|
|
|
293
299
|
default_end_date_col: The column name of the default end date
|
|
294
300
|
date_format: The format of the default date(s). Defaults to '%Y-%m-%d'
|
|
295
301
|
id_col: The column name of the id
|
|
296
|
-
|
|
302
|
+
source: The source to fetch data from. Must be "connection", "seeds", or "vdl". Defaults to "connection"
|
|
297
303
|
user_group_col: The column name of the user group that the user is in for this option to be valid
|
|
298
304
|
parent_id_col: The column name of the parent option id that the default date belongs to
|
|
299
305
|
connection: Name of the connection to use defined in connections.py
|
|
300
306
|
"""
|
|
301
307
|
super().__init__(
|
|
302
|
-
table_or_query, id_col=id_col,
|
|
308
|
+
table_or_query, id_col=id_col, source=source, user_group_col=user_group_col, parent_id_col=parent_id_col,
|
|
303
309
|
connection=connection
|
|
304
310
|
)
|
|
305
311
|
self._default_start_date_col = default_start_date_col
|
|
@@ -310,7 +316,7 @@ class DateRangeDataSource(DataSource):
|
|
|
310
316
|
|
|
311
317
|
def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.DateRangeParameterConfig:
|
|
312
318
|
"""
|
|
313
|
-
Method to convert the associated
|
|
319
|
+
Method to convert the associated DataSourceParameterConfig into a DateRangeParameterConfig
|
|
314
320
|
|
|
315
321
|
Arguments:
|
|
316
322
|
ds_param: The parameter to convert
|
|
@@ -354,11 +360,11 @@ class _NumericDataSource(DataSource):
|
|
|
354
360
|
@abc.abstractmethod
|
|
355
361
|
def __init__(
|
|
356
362
|
self, table_or_query: str, min_value_col: str, max_value_col: str, *, increment_col: str | None = None,
|
|
357
|
-
id_col: str | None = None,
|
|
363
|
+
id_col: str | None = None, source: SourceEnum = SourceEnum.CONNECTION, user_group_col: str | None = None,
|
|
358
364
|
parent_id_col: str | None = None, connection: str | None = None, **kwargs
|
|
359
365
|
) -> None:
|
|
360
366
|
super().__init__(
|
|
361
|
-
table_or_query, id_col=id_col,
|
|
367
|
+
table_or_query, id_col=id_col, source=source, user_group_col=user_group_col, parent_id_col=parent_id_col,
|
|
362
368
|
connection=connection
|
|
363
369
|
)
|
|
364
370
|
self._min_value_col = min_value_col
|
|
@@ -375,7 +381,7 @@ class NumberDataSource(_NumericDataSource):
|
|
|
375
381
|
|
|
376
382
|
def __init__(
|
|
377
383
|
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,
|
|
384
|
+
default_value_col: str | None = None, id_col: str | None = None, source: SourceEnum = SourceEnum.CONNECTION,
|
|
379
385
|
user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None, **kwargs
|
|
380
386
|
) -> None:
|
|
381
387
|
"""
|
|
@@ -388,20 +394,20 @@ class NumberDataSource(_NumericDataSource):
|
|
|
388
394
|
increment_col: The column name of the increment value. Defaults to column of 1's if None
|
|
389
395
|
default_value_col: The column name of the default value. Defaults to min_value_col if None
|
|
390
396
|
id_col: The column name of the id
|
|
391
|
-
|
|
397
|
+
source: The source to fetch data from. Must be "connection", "seeds", or "vdl". Defaults to "connection"
|
|
392
398
|
user_group_col: The column name of the user group that the user is in for this option to be valid
|
|
393
399
|
parent_id_col: The column name of the parent option id that the default value belongs to
|
|
394
400
|
connection: Name of the connection to use defined in connections.py
|
|
395
401
|
"""
|
|
396
402
|
super().__init__(
|
|
397
|
-
table_or_query, min_value_col, max_value_col, increment_col=increment_col, id_col=id_col,
|
|
403
|
+
table_or_query, min_value_col, max_value_col, increment_col=increment_col, id_col=id_col, source=source,
|
|
398
404
|
user_group_col=user_group_col, parent_id_col=parent_id_col, connection=connection
|
|
399
405
|
)
|
|
400
406
|
self._default_value_col = default_value_col
|
|
401
407
|
|
|
402
408
|
def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.NumberParameterConfig:
|
|
403
409
|
"""
|
|
404
|
-
Method to convert the associated
|
|
410
|
+
Method to convert the associated DataSourceParameterConfig into a NumberParameterConfig
|
|
405
411
|
|
|
406
412
|
Arguments:
|
|
407
413
|
ds_param: The parameter to convert
|
|
@@ -443,7 +449,7 @@ class NumberRangeDataSource(_NumericDataSource):
|
|
|
443
449
|
def __init__(
|
|
444
450
|
self, table_or_query: str, min_value_col: str, max_value_col: str, *, increment_col: str | None = None,
|
|
445
451
|
default_lower_value_col: str | None = None, default_upper_value_col: str | None = None, id_col: str | None = None,
|
|
446
|
-
|
|
452
|
+
source: SourceEnum = SourceEnum.CONNECTION, user_group_col: str | None = None, parent_id_col: str | None = None,
|
|
447
453
|
connection: str | None = None, **kwargs
|
|
448
454
|
) -> None:
|
|
449
455
|
"""
|
|
@@ -457,13 +463,13 @@ class NumberRangeDataSource(_NumericDataSource):
|
|
|
457
463
|
default_lower_value_col: The column name of the default lower value. Defaults to min_value_col if None
|
|
458
464
|
default_upper_value_col: The column name of the default upper value. Defaults to max_value_col if None
|
|
459
465
|
id_col: The column name of the id
|
|
460
|
-
|
|
466
|
+
source: The source to fetch data from. Must be "connection", "seeds", or "vdl". Defaults to "connection"
|
|
461
467
|
user_group_col: The column name of the user group that the user is in for this option to be valid
|
|
462
468
|
parent_id_col: The column name of the parent option id that the default value belongs to
|
|
463
469
|
connection: Name of the connection to use defined in connections.py
|
|
464
470
|
"""
|
|
465
471
|
super().__init__(
|
|
466
|
-
table_or_query, min_value_col, max_value_col, increment_col=increment_col, id_col=id_col,
|
|
472
|
+
table_or_query, min_value_col, max_value_col, increment_col=increment_col, id_col=id_col, source=source,
|
|
467
473
|
user_group_col=user_group_col, parent_id_col=parent_id_col, connection=connection
|
|
468
474
|
)
|
|
469
475
|
self._default_lower_value_col = default_lower_value_col
|
|
@@ -471,7 +477,7 @@ class NumberRangeDataSource(_NumericDataSource):
|
|
|
471
477
|
|
|
472
478
|
def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.NumberRangeParameterConfig:
|
|
473
479
|
"""
|
|
474
|
-
Method to convert the associated
|
|
480
|
+
Method to convert the associated DataSourceParameterConfig into a NumberRangeParameterConfig
|
|
475
481
|
|
|
476
482
|
Arguments:
|
|
477
483
|
ds_param: The parameter to convert
|
|
@@ -511,7 +517,7 @@ class TextDataSource(DataSource):
|
|
|
511
517
|
_default_text_col: str
|
|
512
518
|
|
|
513
519
|
def __init__(
|
|
514
|
-
self, table_or_query: str, default_text_col: str, *, id_col: str | None = None,
|
|
520
|
+
self, table_or_query: str, default_text_col: str, *, id_col: str | None = None, source: SourceEnum = SourceEnum.CONNECTION,
|
|
515
521
|
user_group_col: str | None = None, parent_id_col: str | None = None, connection: str | None = None,
|
|
516
522
|
**kwargs
|
|
517
523
|
) -> None:
|
|
@@ -522,20 +528,20 @@ class TextDataSource(DataSource):
|
|
|
522
528
|
table_or_query: Either the name of the table to use, or a query to run
|
|
523
529
|
default_text_col: The column name of the default text
|
|
524
530
|
id_col: The column name of the id
|
|
525
|
-
|
|
531
|
+
source: The source to fetch data from. Must be "connection", "seeds", or "vdl". Defaults to "connection"
|
|
526
532
|
user_group_col: The column name of the user group that the user is in for this option to be valid
|
|
527
533
|
parent_id_col: The column name of the parent option id that the default date belongs to
|
|
528
534
|
connection: Name of the connection to use defined in connections.py
|
|
529
535
|
"""
|
|
530
536
|
super().__init__(
|
|
531
|
-
table_or_query, id_col=id_col,
|
|
537
|
+
table_or_query, id_col=id_col, source=source, user_group_col=user_group_col, parent_id_col=parent_id_col,
|
|
532
538
|
connection=connection
|
|
533
539
|
)
|
|
534
540
|
self._default_text_col = default_text_col
|
|
535
541
|
|
|
536
542
|
def _convert(self, ds_param: pc.DataSourceParameterConfig, df: pl.DataFrame) -> pc.TextParameterConfig:
|
|
537
543
|
"""
|
|
538
|
-
Method to convert the associated
|
|
544
|
+
Method to convert the associated DataSourceParameterConfig into a TextParameterConfig
|
|
539
545
|
|
|
540
546
|
Arguments:
|
|
541
547
|
ds_param: The parameter to convert
|
squirrels/_dataset_types.py
CHANGED
|
@@ -32,29 +32,34 @@ class DatasetMetadata:
|
|
|
32
32
|
return self._json_repr
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class DatasetResultFormat:
|
|
37
|
+
orientation: Literal["records", "rows", "columns"]
|
|
38
|
+
offset: int
|
|
39
|
+
limit: int | None
|
|
40
|
+
|
|
41
|
+
|
|
35
42
|
@dataclass
|
|
36
43
|
class DatasetResult(DatasetMetadata):
|
|
37
44
|
df: pl.DataFrame
|
|
38
|
-
to_json: Callable[[
|
|
45
|
+
to_json: Callable[[DatasetResultFormat], dict] = field(init=False)
|
|
39
46
|
|
|
40
47
|
def __post_init__(self):
|
|
41
48
|
self.to_json = lru_cache()(self._to_json)
|
|
42
49
|
|
|
43
|
-
def _to_json(self,
|
|
50
|
+
def _to_json(self, result_format: DatasetResultFormat) -> dict:
|
|
44
51
|
df = self.df.lazy()
|
|
45
|
-
if offset > 0:
|
|
46
|
-
df = df.filter(pl.col("_row_num") > offset)
|
|
47
|
-
if limit
|
|
48
|
-
df = df.limit(limit)
|
|
49
|
-
if select:
|
|
50
|
-
df = df.select(select)
|
|
52
|
+
if result_format.offset > 0:
|
|
53
|
+
df = df.filter(pl.col("_row_num") > result_format.offset)
|
|
54
|
+
if result_format.limit is not None:
|
|
55
|
+
df = df.limit(result_format.limit)
|
|
51
56
|
df = df.collect()
|
|
52
57
|
|
|
53
|
-
if orientation == "columns":
|
|
58
|
+
if result_format.orientation == "columns":
|
|
54
59
|
data = df.to_dict(as_series=False)
|
|
55
60
|
else:
|
|
56
61
|
data = df.to_dicts()
|
|
57
|
-
if orientation == "rows":
|
|
62
|
+
if result_format.orientation == "rows":
|
|
58
63
|
data = [[row[col] for col in df.columns] for row in data]
|
|
59
64
|
|
|
60
65
|
column_details_by_name = {col.name: col for col in self.target_model_config.columns}
|
|
@@ -80,7 +85,7 @@ class DatasetResult(DatasetMetadata):
|
|
|
80
85
|
"total_num_rows": self.df.select(pl.len()).item(),
|
|
81
86
|
"data_details": {
|
|
82
87
|
"num_rows": df.select(pl.len()).item(),
|
|
83
|
-
"orientation": orientation
|
|
88
|
+
"orientation": result_format.orientation
|
|
84
89
|
},
|
|
85
90
|
"data": data
|
|
86
91
|
}
|