squirrels 0.5.0b2__py3-none-any.whl → 0.5.0b4__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 (96) 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 +9 -13
  6. squirrels/_api_routes/__init__.py +5 -0
  7. squirrels/_api_routes/auth.py +262 -0
  8. squirrels/_api_routes/base.py +154 -0
  9. squirrels/_api_routes/dashboards.py +142 -0
  10. squirrels/_api_routes/data_management.py +103 -0
  11. squirrels/_api_routes/datasets.py +242 -0
  12. squirrels/_api_routes/oauth2.py +300 -0
  13. squirrels/_api_routes/project.py +214 -0
  14. squirrels/_api_server.py +145 -748
  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 +4 -26
  18. squirrels/_auth.py +646 -93
  19. squirrels/_connection_set.py +5 -5
  20. squirrels/_constants.py +7 -1
  21. squirrels/{_dashboards_io.py → _dashboards.py} +87 -6
  22. squirrels/_data_sources.py +564 -0
  23. squirrels/_exceptions.py +9 -37
  24. squirrels/_initializer.py +31 -26
  25. squirrels/_manifest.py +5 -5
  26. squirrels/_model_builder.py +1 -1
  27. squirrels/_model_configs.py +2 -2
  28. squirrels/_model_queries.py +1 -1
  29. squirrels/_models.py +40 -27
  30. squirrels/{package_data → _package_data}/base_project/.env +1 -0
  31. squirrels/{package_data → _package_data}/base_project/.env.example +1 -0
  32. squirrels/{package_data → _package_data}/base_project/dashboards/dashboard_example.py +4 -4
  33. squirrels/{package_data → _package_data}/base_project/dashboards/dashboard_example.yml +2 -2
  34. squirrels/_package_data/base_project/macros/macros_example.sql +17 -0
  35. squirrels/{package_data → _package_data}/base_project/models/builds/build_example.py +2 -2
  36. squirrels/{package_data → _package_data}/base_project/models/builds/build_example.sql +1 -1
  37. squirrels/{package_data → _package_data}/base_project/models/dbviews/dbview_example.sql +1 -1
  38. squirrels/_package_data/base_project/models/federates/federate_example.py +41 -0
  39. squirrels/_package_data/base_project/models/federates/federate_example.sql +25 -0
  40. squirrels/{package_data → _package_data}/base_project/models/federates/federate_example.yml +6 -6
  41. squirrels/{package_data → _package_data}/base_project/parameters.yml +9 -8
  42. squirrels/_package_data/base_project/pyconfigs/connections.py +14 -0
  43. squirrels/{package_data → _package_data}/base_project/pyconfigs/context.py +14 -16
  44. squirrels/_package_data/base_project/pyconfigs/parameters.py +106 -0
  45. squirrels/_package_data/base_project/pyconfigs/user.py +51 -0
  46. squirrels/_package_data/templates/dataset_results.html +112 -0
  47. squirrels/_package_data/templates/oauth_login.html +271 -0
  48. squirrels/_parameter_configs.py +35 -35
  49. squirrels/_parameter_options.py +348 -0
  50. squirrels/_parameter_sets.py +47 -37
  51. squirrels/_parameters.py +1664 -0
  52. squirrels/_project.py +76 -32
  53. squirrels/_py_module.py +3 -2
  54. squirrels/_schemas/__init__.py +0 -0
  55. squirrels/_schemas/auth_models.py +144 -0
  56. squirrels/_schemas/query_param_models.py +67 -0
  57. squirrels/{_api_response_models.py → _schemas/response_models.py} +12 -8
  58. squirrels/_utils.py +38 -4
  59. squirrels/arguments.py +2 -0
  60. squirrels/auth.py +1 -0
  61. squirrels/connections.py +1 -0
  62. squirrels/dashboards.py +1 -82
  63. squirrels/data_sources.py +8 -563
  64. squirrels/parameter_options.py +8 -348
  65. squirrels/parameters.py +9 -1266
  66. squirrels/types.py +11 -0
  67. {squirrels-0.5.0b2.dist-info → squirrels-0.5.0b4.dist-info}/METADATA +4 -1
  68. squirrels-0.5.0b4.dist-info/RECORD +94 -0
  69. squirrels/package_data/base_project/macros/macros_example.sql +0 -15
  70. squirrels/package_data/base_project/models/federates/federate_example.py +0 -44
  71. squirrels/package_data/base_project/models/federates/federate_example.sql +0 -17
  72. squirrels/package_data/base_project/pyconfigs/connections.py +0 -14
  73. squirrels/package_data/base_project/pyconfigs/parameters.py +0 -93
  74. squirrels/package_data/base_project/pyconfigs/user.py +0 -23
  75. squirrels-0.5.0b2.dist-info/RECORD +0 -70
  76. /squirrels/{dataset_result.py → _dataset_types.py} +0 -0
  77. /squirrels/{package_data → _package_data}/base_project/assets/expenses.db +0 -0
  78. /squirrels/{package_data → _package_data}/base_project/assets/weather.db +0 -0
  79. /squirrels/{package_data → _package_data}/base_project/connections.yml +0 -0
  80. /squirrels/{package_data → _package_data}/base_project/docker/.dockerignore +0 -0
  81. /squirrels/{package_data → _package_data}/base_project/docker/Dockerfile +0 -0
  82. /squirrels/{package_data → _package_data}/base_project/docker/compose.yml +0 -0
  83. /squirrels/{package_data → _package_data}/base_project/duckdb_init.sql +0 -0
  84. /squirrels/{package_data/base_project/.gitignore → _package_data/base_project/gitignore} +0 -0
  85. /squirrels/{package_data → _package_data}/base_project/models/builds/build_example.yml +0 -0
  86. /squirrels/{package_data → _package_data}/base_project/models/dbviews/dbview_example.yml +0 -0
  87. /squirrels/{package_data → _package_data}/base_project/models/sources.yml +0 -0
  88. /squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.csv +0 -0
  89. /squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.yml +0 -0
  90. /squirrels/{package_data → _package_data}/base_project/seeds/seed_subcategories.csv +0 -0
  91. /squirrels/{package_data → _package_data}/base_project/seeds/seed_subcategories.yml +0 -0
  92. /squirrels/{package_data → _package_data}/base_project/squirrels.yml.j2 +0 -0
  93. /squirrels/{package_data → _package_data}/base_project/tmp/.gitignore +0 -0
  94. {squirrels-0.5.0b2.dist-info → squirrels-0.5.0b4.dist-info}/WHEEL +0 -0
  95. {squirrels-0.5.0b2.dist-info → squirrels-0.5.0b4.dist-info}/entry_points.txt +0 -0
  96. {squirrels-0.5.0b2.dist-info → squirrels-0.5.0b4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,214 @@
1
+ """
2
+ Project metadata routes
3
+ """
4
+ from typing import Any
5
+ from fastapi import FastAPI, Depends, Request
6
+ from fastapi.responses import JSONResponse
7
+ from fastapi.security import HTTPBearer
8
+ from mcp.server.fastmcp import FastMCP, Context
9
+ from dataclasses import asdict
10
+ from cachetools import TTLCache
11
+ import time
12
+
13
+ from .. import _utils as u, _constants as c
14
+ from .._schemas import response_models as rm
15
+ from .._parameter_sets import ParameterSet
16
+ from .._exceptions import ConfigurationError, InvalidInputError
17
+ from .._manifest import PermissionScope
18
+ from .._version import __version__
19
+ from .._schemas.query_param_models import get_query_models_for_parameters
20
+ from .._auth import BaseUser
21
+ from .base import RouteBase
22
+
23
+
24
+ class ProjectRoutes(RouteBase):
25
+ """Project metadata and data catalog routes"""
26
+
27
+ def __init__(self, get_bearer_token: HTTPBearer, project, no_cache: bool = False):
28
+ super().__init__(get_bearer_token, project, no_cache)
29
+
30
+ # Setup caches
31
+ parameters_cache_size = int(self.env_vars.get(c.SQRL_PARAMETERS_CACHE_SIZE, 1024))
32
+ parameters_cache_ttl = int(self.env_vars.get(c.SQRL_PARAMETERS_CACHE_TTL_MINUTES, 60))
33
+ self.parameters_cache = TTLCache(maxsize=parameters_cache_size, ttl=parameters_cache_ttl*60)
34
+
35
+ async def _get_parameters_helper(
36
+ self, parameters_tuple: tuple[str, ...] | None, entity_type: str, entity_name: str, entity_scope: PermissionScope,
37
+ user: BaseUser | None, selections: tuple[tuple[str, Any], ...]
38
+ ) -> ParameterSet:
39
+ """Helper for getting parameters"""
40
+ selections_dict = dict(selections)
41
+ if "x_parent_param" not in selections_dict:
42
+ if len(selections_dict) > 1:
43
+ raise InvalidInputError(400, "Invalid input for cascading parameters", f"The parameters endpoint takes at most 1 widget parameter selection (unless x_parent_param is provided). Got {selections_dict}")
44
+ elif len(selections_dict) == 1:
45
+ parent_param = next(iter(selections_dict))
46
+ selections_dict["x_parent_param"] = parent_param
47
+
48
+ parent_param = selections_dict.get("x_parent_param")
49
+ if parent_param is not None and parent_param not in selections_dict:
50
+ # this condition is possible for multi-select parameters with empty selection
51
+ selections_dict[parent_param] = list()
52
+
53
+ if not self.authenticator.can_user_access_scope(user, entity_scope):
54
+ raise self.project._permission_error(user, entity_type, entity_name, entity_scope.name)
55
+
56
+ param_set = self.param_cfg_set.apply_selections(parameters_tuple, selections_dict, user, parent_param=parent_param)
57
+ return param_set
58
+
59
+ async def _get_parameters_cachable(
60
+ self, parameters_tuple: tuple[str, ...] | None, entity_type: str, entity_name: str, entity_scope: PermissionScope,
61
+ user: BaseUser | None, selections: tuple[tuple[str, Any], ...]
62
+ ) -> ParameterSet:
63
+ """Cachable version of parameters helper"""
64
+ return await self.do_cachable_action(
65
+ self.parameters_cache, self._get_parameters_helper, parameters_tuple, entity_type, entity_name, entity_scope, user, selections
66
+ )
67
+
68
+ def setup_routes(
69
+ self, app: FastAPI, mcp: FastMCP, project_metadata_path: str, project_name: str, project_version: str, param_fields: dict
70
+ ):
71
+ """Setup project metadata routes"""
72
+
73
+ # Project metadata endpoint
74
+ @app.get(project_metadata_path, tags=["Project Metadata"], response_class=JSONResponse)
75
+ async def get_project_metadata(request: Request) -> rm.ProjectModel:
76
+ return rm.ProjectModel(
77
+ name=project_name,
78
+ version=project_version,
79
+ label=self.manifest_cfg.project_variables.label,
80
+ description=self.manifest_cfg.project_variables.description,
81
+ squirrels_version=__version__
82
+ )
83
+
84
+ # Data catalog endpoint
85
+ data_catalog_path = project_metadata_path + '/data-catalog'
86
+
87
+ async def get_data_catalog0(user: BaseUser | None) -> rm.CatalogModel:
88
+ parameters = self.param_cfg_set.apply_selections(None, {}, user)
89
+ parameters_model = parameters.to_api_response_model0()
90
+ full_parameters_list = [p.name for p in parameters_model.parameters]
91
+
92
+ dataset_items: list[rm.DatasetItemModel] = []
93
+ for name, config in self.manifest_cfg.datasets.items():
94
+ if self.authenticator.can_user_access_scope(user, config.scope):
95
+ name_normalized = u.normalize_name_for_api(name)
96
+ metadata = self.project.dataset_metadata(name).to_json()
97
+ parameters = config.parameters if config.parameters is not None else full_parameters_list
98
+ dataset_items.append(rm.DatasetItemModel(
99
+ name=name_normalized, label=config.label,
100
+ description=config.description,
101
+ schema=metadata["schema"], # type: ignore
102
+ parameters=parameters,
103
+ parameters_path=f"{project_metadata_path}/dataset/{name_normalized}/parameters",
104
+ result_path=f"{project_metadata_path}/dataset/{name_normalized}"
105
+ ))
106
+
107
+ dashboard_items: list[rm.DashboardItemModel] = []
108
+ for name, dashboard in self.project._dashboards.items():
109
+ config = dashboard.config
110
+ if self.authenticator.can_user_access_scope(user, config.scope):
111
+ name_normalized = u.normalize_name_for_api(name)
112
+
113
+ try:
114
+ dashboard_format = self.project._dashboards[name].get_dashboard_format()
115
+ except KeyError:
116
+ raise ConfigurationError(f"No dashboard file found for: {name}")
117
+
118
+ parameters = config.parameters if config.parameters is not None else full_parameters_list
119
+ dashboard_items.append(rm.DashboardItemModel(
120
+ name=name, label=config.label,
121
+ description=config.description,
122
+ result_format=dashboard_format,
123
+ parameters=parameters,
124
+ parameters_path=f"{project_metadata_path}/dashboard/{name_normalized}/parameters",
125
+ result_path=f"{project_metadata_path}/dashboard/{name_normalized}"
126
+ ))
127
+
128
+ if user and user.is_admin:
129
+ compiled_dag = await self.project._get_compiled_dag(user=user)
130
+ connections_items = self.project._get_all_connections()
131
+ data_models = self.project._get_all_data_models(compiled_dag)
132
+ lineage_items = self.project._get_all_data_lineage(compiled_dag)
133
+ else:
134
+ connections_items = []
135
+ data_models = []
136
+ lineage_items = []
137
+
138
+ return rm.CatalogModel(
139
+ parameters=parameters_model.parameters,
140
+ datasets=dataset_items,
141
+ dashboards=dashboard_items,
142
+ connections=connections_items,
143
+ models=data_models,
144
+ lineage=lineage_items,
145
+ )
146
+
147
+ @app.get(data_catalog_path, tags=["Project Metadata"], summary="Get catalog of datasets and dashboards available for user")
148
+ async def get_data_catalog(request: Request, user: BaseUser | None = Depends(self.get_current_user)) -> rm.CatalogModel:
149
+ """
150
+ Get catalog of datasets and dashboards available for the authenticated user.
151
+
152
+ For admin users, this endpoint will also return detailed information about all models and their lineage in the project.
153
+ """
154
+ return await get_data_catalog0(user)
155
+
156
+ @mcp.tool(
157
+ name=f"get_data_catalog_for_{project_name}_{project_version}",
158
+ description=f"Use this tool to get the details of all datasets and parameters you can access in the Squirrels project '{project_name}'."
159
+ )
160
+ async def get_data_catalog_tool(ctx: Context):
161
+ user = self.get_user_from_tool_ctx(ctx)
162
+ data_catalog = await get_data_catalog0(user)
163
+ restricted_data_catalog = {
164
+ "parameters": data_catalog.parameters,
165
+ "datasets": data_catalog.datasets,
166
+ }
167
+ return restricted_data_catalog
168
+
169
+ # Project-level parameters endpoints
170
+ project_level_parameters_path = project_metadata_path + '/parameters'
171
+ parameters_description = "Selections of one parameter may cascade the available options in another parameter. " \
172
+ "For example, if the dataset has parameters for 'country' and 'city', available options for 'city' would " \
173
+ "depend on the selected option 'country'. If a parameter has 'trigger_refresh' as true, provide the parameter " \
174
+ "selection to this endpoint whenever it changes to refresh the parameter options of children parameters."
175
+
176
+ QueryModelForGetProjectParams, QueryModelForPostProjectParams = get_query_models_for_parameters(None, param_fields)
177
+
178
+ async def get_parameters_definition(
179
+ parameters_list: list[str] | None, entity_type: str, entity_name: str, entity_scope: PermissionScope,
180
+ user, all_request_params: dict, params: dict
181
+ ) -> rm.ParametersModel:
182
+ self._validate_request_params(all_request_params, params)
183
+
184
+ get_parameters_function = self._get_parameters_helper if self.no_cache else self._get_parameters_cachable
185
+ selections = self.get_selections_as_immutable(params, uncached_keys={"x_verify_params"})
186
+ parameters_tuple = tuple(parameters_list) if parameters_list is not None else None
187
+ result = await get_parameters_function(parameters_tuple, entity_type, entity_name, entity_scope, user, selections)
188
+ return result.to_api_response_model0()
189
+
190
+ @app.get(project_level_parameters_path, tags=["Project Metadata"], description=parameters_description)
191
+ async def get_project_parameters(
192
+ request: Request, params: QueryModelForGetProjectParams, user=Depends(self.get_current_user) # type: ignore
193
+ ) -> rm.ParametersModel:
194
+ start = time.time()
195
+ result = await get_parameters_definition(
196
+ None, "project", "", PermissionScope.PUBLIC, user, dict(request.query_params), asdict(params)
197
+ )
198
+ self.log_activity_time("GET REQUEST for PROJECT PARAMETERS", start, request)
199
+ return result
200
+
201
+ @app.post(project_level_parameters_path, tags=["Project Metadata"], description=parameters_description)
202
+ async def get_project_parameters_with_post(
203
+ request: Request, params: QueryModelForPostProjectParams, user=Depends(self.get_current_user) # type: ignore
204
+ ) -> rm.ParametersModel:
205
+ start = time.time()
206
+ payload: dict = await request.json()
207
+ result = await get_parameters_definition(
208
+ None, "project", "", PermissionScope.PUBLIC, user, payload, params.model_dump()
209
+ )
210
+ self.log_activity_time("POST REQUEST for PROJECT PARAMETERS", start, request)
211
+ return result
212
+
213
+ return get_parameters_definition
214
+