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.
Files changed (93) hide show
  1. squirrels/__init__.py +4 -0
  2. squirrels/_api_routes/__init__.py +5 -0
  3. squirrels/_api_routes/auth.py +337 -0
  4. squirrels/_api_routes/base.py +196 -0
  5. squirrels/_api_routes/dashboards.py +156 -0
  6. squirrels/_api_routes/data_management.py +148 -0
  7. squirrels/_api_routes/datasets.py +220 -0
  8. squirrels/_api_routes/project.py +289 -0
  9. squirrels/_api_server.py +440 -792
  10. squirrels/_arguments/__init__.py +0 -0
  11. squirrels/_arguments/{_init_time_args.py → init_time_args.py} +23 -43
  12. squirrels/_arguments/{_run_time_args.py → run_time_args.py} +32 -68
  13. squirrels/_auth.py +590 -264
  14. squirrels/_command_line.py +130 -58
  15. squirrels/_compile_prompts.py +147 -0
  16. squirrels/_connection_set.py +16 -15
  17. squirrels/_constants.py +36 -11
  18. squirrels/_dashboards.py +179 -0
  19. squirrels/_data_sources.py +40 -34
  20. squirrels/_dataset_types.py +16 -11
  21. squirrels/_env_vars.py +209 -0
  22. squirrels/_exceptions.py +9 -37
  23. squirrels/_http_error_responses.py +52 -0
  24. squirrels/_initializer.py +7 -6
  25. squirrels/_logging.py +121 -0
  26. squirrels/_manifest.py +155 -77
  27. squirrels/_mcp_server.py +578 -0
  28. squirrels/_model_builder.py +11 -55
  29. squirrels/_model_configs.py +5 -5
  30. squirrels/_model_queries.py +1 -1
  31. squirrels/_models.py +276 -143
  32. squirrels/_package_data/base_project/.env +1 -24
  33. squirrels/_package_data/base_project/.env.example +31 -17
  34. squirrels/_package_data/base_project/connections.yml +4 -3
  35. squirrels/_package_data/base_project/dashboards/dashboard_example.py +13 -7
  36. squirrels/_package_data/base_project/dashboards/dashboard_example.yml +6 -6
  37. squirrels/_package_data/base_project/docker/Dockerfile +2 -2
  38. squirrels/_package_data/base_project/docker/compose.yml +1 -1
  39. squirrels/_package_data/base_project/duckdb_init.sql +1 -0
  40. squirrels/_package_data/base_project/models/builds/build_example.py +2 -2
  41. squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +7 -2
  42. squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +16 -10
  43. squirrels/_package_data/base_project/models/federates/federate_example.py +27 -17
  44. squirrels/_package_data/base_project/models/federates/federate_example.sql +3 -7
  45. squirrels/_package_data/base_project/models/federates/federate_example.yml +7 -7
  46. squirrels/_package_data/base_project/models/sources.yml +5 -6
  47. squirrels/_package_data/base_project/parameters.yml +24 -38
  48. squirrels/_package_data/base_project/pyconfigs/connections.py +8 -3
  49. squirrels/_package_data/base_project/pyconfigs/context.py +26 -14
  50. squirrels/_package_data/base_project/pyconfigs/parameters.py +124 -81
  51. squirrels/_package_data/base_project/pyconfigs/user.py +48 -15
  52. squirrels/_package_data/base_project/resources/public/.gitkeep +0 -0
  53. squirrels/_package_data/base_project/seeds/seed_categories.yml +1 -1
  54. squirrels/_package_data/base_project/seeds/seed_subcategories.yml +1 -1
  55. squirrels/_package_data/base_project/squirrels.yml.j2 +21 -31
  56. squirrels/_package_data/templates/login_successful.html +53 -0
  57. squirrels/_package_data/templates/squirrels_studio.html +22 -0
  58. squirrels/_parameter_configs.py +43 -22
  59. squirrels/_parameter_options.py +1 -1
  60. squirrels/_parameter_sets.py +41 -30
  61. squirrels/_parameters.py +560 -123
  62. squirrels/_project.py +487 -277
  63. squirrels/_py_module.py +71 -10
  64. squirrels/_request_context.py +33 -0
  65. squirrels/_schemas/__init__.py +0 -0
  66. squirrels/_schemas/auth_models.py +83 -0
  67. squirrels/_schemas/query_param_models.py +70 -0
  68. squirrels/_schemas/request_models.py +26 -0
  69. squirrels/_schemas/response_models.py +286 -0
  70. squirrels/_seeds.py +52 -13
  71. squirrels/_sources.py +29 -23
  72. squirrels/_utils.py +221 -42
  73. squirrels/_version.py +1 -3
  74. squirrels/arguments.py +7 -2
  75. squirrels/auth.py +4 -0
  76. squirrels/connections.py +2 -0
  77. squirrels/dashboards.py +3 -1
  78. squirrels/data_sources.py +6 -0
  79. squirrels/parameter_options.py +5 -0
  80. squirrels/parameters.py +5 -0
  81. squirrels/types.py +10 -3
  82. squirrels-0.6.0.post0.dist-info/METADATA +148 -0
  83. squirrels-0.6.0.post0.dist-info/RECORD +101 -0
  84. {squirrels-0.5.0b3.dist-info → squirrels-0.6.0.post0.dist-info}/WHEEL +1 -1
  85. squirrels/_api_response_models.py +0 -190
  86. squirrels/_dashboard_types.py +0 -82
  87. squirrels/_dashboards_io.py +0 -79
  88. squirrels-0.5.0b3.dist-info/METADATA +0 -110
  89. squirrels-0.5.0b3.dist-info/RECORD +0 -80
  90. /squirrels/_package_data/base_project/{assets → resources}/expenses.db +0 -0
  91. /squirrels/_package_data/base_project/{assets → resources}/weather.db +0 -0
  92. {squirrels-0.5.0b3.dist-info → squirrels-0.6.0.post0.dist-info}/entry_points.txt +0 -0
  93. {squirrels-0.5.0b3.dist-info → squirrels-0.6.0.post0.dist-info}/licenses/LICENSE +0 -0
@@ -11,7 +11,7 @@ import polars as pl, re
11
11
 
12
12
  from . import _data_sources as d, _parameter_options as po, _parameters as p, _utils as u, _constants as c
13
13
  from ._exceptions import InvalidInputError
14
- from ._auth import BaseUser
14
+ from ._schemas.auth_models import AbstractUser
15
15
  from ._connection_set import ConnectionSet
16
16
  from ._seeds import Seeds
17
17
 
@@ -56,12 +56,20 @@ class ParameterConfigBase(metaclass=ABCMeta):
56
56
  user_attribute: str | None = field(default=None, kw_only=True)
57
57
  parent_name: str | None = field(default=None, kw_only=True)
58
58
 
59
- def _get_user_group(self, user: BaseUser | None) -> Any:
60
- if self.user_attribute is not None:
61
- if user is None:
62
- raise u.ConfigurationError(f"Public datasets (accessible without authentication) cannot use parameter " +
63
- f"'{self.name}' because 'user_attribute' is defined on this parameter.")
64
- return getattr(user, self.user_attribute)
59
+ def _get_user_group(self, user: AbstractUser) -> Any:
60
+ if self.user_attribute is None:
61
+ return None
62
+
63
+ final_object = user
64
+ attribute = self.user_attribute
65
+ try:
66
+ if "." in attribute:
67
+ parts = attribute.split(".", 1)
68
+ final_object = getattr(final_object, parts[0])
69
+ attribute = parts[1]
70
+ return getattr(final_object, attribute)
71
+ except AttributeError:
72
+ raise u.ConfigurationError(f"User attribute '{self.user_attribute}' is not valid")
65
73
 
66
74
  def copy(self):
67
75
  """
@@ -108,16 +116,16 @@ class ParameterConfig(Generic[ParamOptionType], ParameterConfigBase):
108
116
  pass
109
117
 
110
118
  def _invalid_input_error(self, selection: str, more_details: str = '') -> InvalidInputError:
111
- return InvalidInputError(200, f'Selected value "{selection}" is not valid for parameter "{self.name}". ' + more_details)
119
+ return InvalidInputError(400, "invalid_parameter_selection", f'Selected value "{selection}" is not valid for parameter "{self.name}". ' + more_details)
112
120
 
113
121
  @abstractmethod
114
122
  def with_selection(
115
- self, selection: str | None, user: BaseUser | None, parent_param: p._SelectionParameter | None
123
+ self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
116
124
  ) -> p.Parameter:
117
125
  pass
118
126
 
119
127
  def _get_options_iterator(
120
- self, all_options: Sequence[ParamOptionType], user: BaseUser | None, parent_param: p._SelectionParameter | None
128
+ self, all_options: Sequence[ParamOptionType], user: AbstractUser, parent_param: p._SelectionParameter | None
121
129
  ) -> Iterator[ParamOptionType]:
122
130
  user_group = self._get_user_group(user)
123
131
  selected_parent_option_ids = frozenset(parent_param._get_selected_ids_as_list()) if parent_param else None
@@ -153,7 +161,7 @@ class SelectionParameterConfig(ParameterConfig[po.SelectParameterOption]):
153
161
  self.children[child.name] = child
154
162
  self.trigger_refresh = True
155
163
 
156
- def _get_options(self, user: BaseUser | None, parent_param: p._SelectionParameter | None) -> Sequence[po.SelectParameterOption]:
164
+ def _get_options(self, user: AbstractUser, parent_param: p._SelectionParameter | None) -> Sequence[po.SelectParameterOption]:
157
165
  return tuple(self._get_options_iterator(self.all_options, user, parent_param))
158
166
 
159
167
  def _get_default_ids_iterator(self, options: Sequence[po.SelectParameterOption]) -> Iterator[str]:
@@ -189,7 +197,7 @@ class SingleSelectParameterConfig(SelectionParameterConfig):
189
197
  return d.SelectDataSource(*args, **kwargs)
190
198
 
191
199
  def with_selection(
192
- self, selection: str | None, user: BaseUser | None, parent_param: p._SelectionParameter | None
200
+ self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
193
201
  ) -> p.SingleSelectParameter:
194
202
  options = self._get_options(user, parent_param)
195
203
  if selection is None:
@@ -237,7 +245,7 @@ class MultiSelectParameterConfig(SelectionParameterConfig):
237
245
  return d.SelectDataSource(*args, **kwargs)
238
246
 
239
247
  def with_selection(
240
- self, selection: str | None, user: BaseUser | None, parent_param: p._SelectionParameter | None
248
+ self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
241
249
  ) -> p.MultiSelectParameter:
242
250
  options = self._get_options(user, parent_param)
243
251
  if selection is None:
@@ -293,7 +301,7 @@ class DateParameterConfig(_DateTypeParameterConfig[po.DateParameterOption]):
293
301
  return d.DateDataSource(*args, **kwargs)
294
302
 
295
303
  def with_selection(
296
- self, selection: str | None, user: BaseUser | None, parent_param: p._SelectionParameter | None
304
+ self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
297
305
  ) -> p.DateParameter:
298
306
  curr_option: po.DateParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
299
307
  selected_date = curr_option._default_date if selection is None and curr_option is not None else selection
@@ -332,7 +340,7 @@ class DateRangeParameterConfig(_DateTypeParameterConfig[po.DateRangeParameterOpt
332
340
  return d.DateRangeDataSource(*args, **kwargs)
333
341
 
334
342
  def with_selection(
335
- self, selection: str | None, user: BaseUser | None, parent_param: p._SelectionParameter | None
343
+ self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
336
344
  ) -> p.DateRangeParameter:
337
345
  curr_option: po.DateRangeParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
338
346
  if selection is None:
@@ -395,7 +403,7 @@ class NumberParameterConfig(_NumericParameterConfig[po.NumberParameterOption]):
395
403
  return d.NumberDataSource(*args, **kwargs)
396
404
 
397
405
  def with_selection(
398
- self, selection: str | None, user: BaseUser | None, parent_param: p._SelectionParameter | None
406
+ self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
399
407
  ) -> p.NumberParameter:
400
408
  curr_option: po.NumberParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
401
409
  selected_value = curr_option._default_value if selection is None and curr_option is not None else selection
@@ -434,7 +442,7 @@ class NumberRangeParameterConfig(_NumericParameterConfig[po.NumberRangeParameter
434
442
  return d.NumberRangeDataSource(*args, **kwargs)
435
443
 
436
444
  def with_selection(
437
- self, selection: str | None, user: BaseUser | None, parent_param: p._SelectionParameter | None
445
+ self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
438
446
  ) -> p.NumberRangeParameter:
439
447
  curr_option: po.NumberRangeParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
440
448
  if selection is None:
@@ -524,7 +532,7 @@ class TextParameterConfig(ParameterConfig[po.TextParameterOption]):
524
532
  return d.TextDataSource(*args, **kwargs)
525
533
 
526
534
  def with_selection(
527
- self, selection: str | None, user: BaseUser | None, parent_param: p._SelectionParameter | None
535
+ self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
528
536
  ) -> p.TextParameter:
529
537
  curr_option: po.TextParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
530
538
  entered_text = curr_option._default_text if selection is None and curr_option is not None else selection
@@ -550,6 +558,8 @@ class DataSourceParameterConfig(Generic[ParamConfigType], ParameterConfigBase):
550
558
  super().__init__(name, label, description=description, user_attribute=user_attribute, parent_name=parent_name)
551
559
  self.parameter_type = parameter_type
552
560
  if isinstance(data_source, dict):
561
+ if "source" in data_source:
562
+ data_source["source"] = d.SourceEnum(data_source["source"])
553
563
  data_source = parameter_type.DataSource(**data_source)
554
564
  self.data_source = data_source
555
565
  self.extra_args = extra_args
@@ -557,15 +567,26 @@ class DataSourceParameterConfig(Generic[ParamConfigType], ParameterConfigBase):
557
567
  def convert(self, df: pl.DataFrame) -> ParamConfigType:
558
568
  return self.data_source._convert(self, df)
559
569
 
560
- def get_dataframe(self, default_conn_name: str, conn_set: ConnectionSet, seeds: Seeds) -> pl.DataFrame:
570
+ def get_dataframe(self, default_conn_name: str, conn_set: ConnectionSet, seeds: Seeds, datalake_db_path: str = "") -> pl.DataFrame:
561
571
  datasource = self.data_source
562
572
  query = datasource._get_query()
563
- if datasource._is_from_seeds:
573
+ if datasource._source == d.SourceEnum.SEEDS:
564
574
  df = seeds.run_query(query)
565
- else:
575
+ elif datasource._source == d.SourceEnum.VDL:
576
+ vdl_conn = u.create_duckdb_connection(datalake_db_path)
577
+ try:
578
+ # Query the VDL database
579
+ df = vdl_conn.sql(query).pl()
580
+ except Exception as e:
581
+ raise u.ConfigurationError(f'Error executing query for datasource parameter "{self.name}" from VDL') from e
582
+ finally:
583
+ vdl_conn.close()
584
+ else: # source == "connection"
585
+ conn_name = None
566
586
  try:
567
587
  conn_name = datasource._get_connection_name(default_conn_name)
568
588
  df = conn_set.run_sql_query_from_conn_name(query, conn_name)
569
589
  except RuntimeError as e:
570
- raise u.ConfigurationError(f'Error executing query for datasource parameter "{self.name}"') from e
590
+ ending = f' "{conn_name}"' if conn_name is not None else ""
591
+ raise u.ConfigurationError(f'Error executing query for datasource parameter "{self.name}" from connection{ending}') from e
571
592
  return df
@@ -35,7 +35,7 @@ class ParameterOption(metaclass=ABCMeta):
35
35
 
36
36
  Arguments:
37
37
  user_group: The value of the user's "user group attribute". Only None when "user_attribute" is not specified
38
- for the Parameter factory. Note that when user is None but "user_attribute" is specified, an error is thrown
38
+ for the Parameter factory.
39
39
  selected_parent_option_ids: List of selected option ids from the parent parameter. Only None when the Parameter
40
40
  object has no parent parameter.
41
41
 
@@ -1,15 +1,17 @@
1
1
  from __future__ import annotations
2
- from typing import Optional, Sequence, Any
2
+ from typing import Optional, Sequence, Callable, Any
3
3
  from dataclasses import dataclass, field
4
4
  from collections import OrderedDict
5
5
  import time, concurrent.futures, polars as pl
6
6
 
7
- from . import _parameters as p, _utils as u, _constants as c, _parameter_configs as pc, _py_module as pm, _api_response_models as arm
8
- from ._arguments._init_time_args import ParametersArgs
7
+ from . import _parameters as p, _utils as u, _constants as c, _parameter_configs as pc, _py_module as pm
8
+ from ._schemas import response_models as rm
9
+ from ._arguments.init_time_args import ParametersArgs
9
10
  from ._manifest import ParametersConfig, ManifestConfig
10
- from ._connection_set import ConnectionSet, ConnectionsArgs
11
+ from ._connection_set import ConnectionSet
11
12
  from ._seeds import Seeds
12
- from ._auth import BaseUser
13
+ from ._schemas.auth_models import AbstractUser
14
+ from ._env_vars import SquirrelsEnvVars
13
15
 
14
16
 
15
17
  @dataclass
@@ -22,11 +24,11 @@ class ParameterSet:
22
24
  def get_parameters_as_dict(self) -> dict[str, p.Parameter]:
23
25
  return self._parameters_dict.copy()
24
26
 
25
- def to_api_response_model0(self) -> arm.ParametersModel:
27
+ def to_api_response_model0(self) -> rm.ParametersModel:
26
28
  parameters = []
27
29
  for x in self._parameters_dict.values():
28
30
  parameters.append(x._to_api_response_model0())
29
- return arm.ParametersModel(parameters=parameters)
31
+ return rm.ParametersModel(parameters=parameters)
30
32
 
31
33
 
32
34
  @dataclass
@@ -103,13 +105,10 @@ class ParameterConfigsSet:
103
105
  self.__validate_param_relationships()
104
106
 
105
107
  def apply_selections(
106
- self, dataset_params: Optional[Sequence[str]], selections: dict[str, Any], user: BaseUser | None, *, parent_param: str | None = None
108
+ self, dataset_params: Optional[Sequence[str]], selections: dict[str, Any], user: AbstractUser, *, parent_param: str | None = None
107
109
  ) -> ParameterSet:
108
110
  if dataset_params is None:
109
- if user is None:
110
- dataset_params = [k for k, v in self._data.items() if v.user_attribute is None]
111
- else:
112
- dataset_params = list(self._data.keys())
111
+ dataset_params = list(self._data.keys())
113
112
 
114
113
  parameters_by_name: dict[str, p.Parameter] = {}
115
114
  params_to_process = [parent_param] if parent_param else dataset_params
@@ -153,44 +152,56 @@ class ParameterConfigsSetIO:
153
152
  """
154
153
  Static class for the singleton object of ParameterConfigsSet
155
154
  """
156
- obj: ParameterConfigsSet # this is static (set in load_from_file) to simplify development experience for pyconfigs/parameters.py
155
+ param_factories: list[Callable[[ParametersArgs], pc.ParameterConfigBase]] = [] # this is static (set in load_from_file) to stage the functions from pyconfigs/parameters.py before using them
157
156
 
158
157
  @classmethod
159
- def _get_df_dict_from_data_sources(cls, default_conn_name: str, seeds: Seeds, conn_set: ConnectionSet) -> dict[str, pl.DataFrame]:
158
+ def _get_df_dict_from_data_sources(
159
+ cls, param_configs_set: ParameterConfigsSet, default_conn_name: str, seeds: Seeds, conn_set: ConnectionSet, datalake_db_path: str
160
+ ) -> dict[str, pl.DataFrame]:
161
+
160
162
  def get_dataframe(ds_param_config: pc.DataSourceParameterConfig) -> tuple[str, pl.DataFrame]:
161
- return ds_param_config.name, ds_param_config.get_dataframe(default_conn_name, conn_set, seeds)
163
+ return ds_param_config.name, ds_param_config.get_dataframe(default_conn_name, conn_set, seeds, datalake_db_path)
162
164
 
163
- ds_param_configs = cls.obj._get_all_ds_param_configs()
165
+ ds_param_configs = param_configs_set._get_all_ds_param_configs()
164
166
  with concurrent.futures.ThreadPoolExecutor() as executor:
165
167
  df_dict = dict(executor.map(get_dataframe, ds_param_configs))
166
168
 
167
169
  return df_dict
168
170
 
169
171
  @classmethod
170
- def _add_from_dict(cls, param_config: ParametersConfig) -> None:
172
+ def _add_from_dict(cls, param_configs_set: ParameterConfigsSet, param_config: ParametersConfig) -> None:
171
173
  ptype = getattr(p, param_config.type)
172
174
  factory = getattr(ptype, param_config.factory)
173
- factory(**param_config.arguments)
174
-
175
- @classmethod
176
- def get_param_args(cls, conn_args: ConnectionsArgs) -> ParametersArgs:
177
- return ParametersArgs(conn_args.project_path, conn_args.proj_vars, conn_args.env_vars)
175
+ obj = factory(**param_config.arguments)
176
+ param_configs_set.add(obj)
178
177
 
179
178
  @classmethod
180
179
  def load_from_file(
181
- cls, logger: u.Logger, base_path: str, manifest_cfg: ManifestConfig, seeds: Seeds, conn_set: ConnectionSet, param_args: ParametersArgs
180
+ cls, logger: u.Logger, env_vars: SquirrelsEnvVars, manifest_cfg: ManifestConfig, seeds: Seeds, conn_set: ConnectionSet,
181
+ param_args: ParametersArgs
182
182
  ) -> ParameterConfigsSet:
183
183
  start = time.time()
184
- cls.obj = ParameterConfigsSet()
184
+ param_configs_set = ParameterConfigsSet()
185
185
 
186
186
  for param_as_dict in manifest_cfg.parameters:
187
- cls._add_from_dict(param_as_dict)
187
+ cls._add_from_dict(param_configs_set, param_as_dict)
188
+
189
+ # adds to cls.param_factories as side effect
190
+ main_result = pm.run_pyconfig_main(env_vars.project_path, c.PARAMETERS_FILE, {"sqrl": param_args})
191
+ param_factories = cls.param_factories
192
+ cls.param_factories = []
193
+
194
+ for param_factory in param_factories:
195
+ param_configs_set.add(param_factory(param_args))
188
196
 
189
- pm.run_pyconfig_main(base_path, c.PARAMETERS_FILE, {"sqrl": param_args})
197
+ if isinstance(main_result, list):
198
+ for param_config in main_result:
199
+ param_configs_set.add(param_config)
190
200
 
191
- default_conn_name = manifest_cfg.env_vars.get(c.SQRL_CONNECTIONS_DEFAULT_NAME_USED, "default")
192
- df_dict = cls._get_df_dict_from_data_sources(default_conn_name, seeds, conn_set)
193
- cls.obj._post_process_params(df_dict)
201
+ default_conn_name = env_vars.connections_default_name_used
202
+ datalake_db_path = env_vars.vdl_catalog_db_path
203
+ df_dict = cls._get_df_dict_from_data_sources(param_configs_set, default_conn_name, seeds, conn_set, datalake_db_path)
204
+ param_configs_set._post_process_params(df_dict)
194
205
 
195
206
  logger.log_activity_time("loading parameters", start)
196
- return cls.obj
207
+ return param_configs_set