squirrels 0.4.0__py3-none-any.whl → 0.5.0__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.
- dateutils/__init__.py +6 -0
- dateutils/_enums.py +25 -0
- squirrels/dateutils.py → dateutils/_implementation.py +58 -111
- dateutils/types.py +6 -0
- squirrels/__init__.py +13 -11
- squirrels/_api_routes/__init__.py +5 -0
- squirrels/_api_routes/auth.py +271 -0
- squirrels/_api_routes/base.py +165 -0
- squirrels/_api_routes/dashboards.py +150 -0
- squirrels/_api_routes/data_management.py +145 -0
- squirrels/_api_routes/datasets.py +257 -0
- squirrels/_api_routes/oauth2.py +298 -0
- squirrels/_api_routes/project.py +252 -0
- squirrels/_api_server.py +256 -450
- squirrels/_arguments/__init__.py +0 -0
- squirrels/_arguments/init_time_args.py +108 -0
- squirrels/_arguments/run_time_args.py +147 -0
- squirrels/_auth.py +960 -0
- squirrels/_command_line.py +126 -45
- squirrels/_compile_prompts.py +147 -0
- squirrels/_connection_set.py +48 -26
- squirrels/_constants.py +68 -38
- squirrels/_dashboards.py +160 -0
- squirrels/_data_sources.py +570 -0
- squirrels/_dataset_types.py +84 -0
- squirrels/_exceptions.py +29 -0
- squirrels/_initializer.py +177 -80
- squirrels/_logging.py +115 -0
- squirrels/_manifest.py +208 -79
- squirrels/_model_builder.py +69 -0
- squirrels/_model_configs.py +74 -0
- squirrels/_model_queries.py +52 -0
- squirrels/_models.py +926 -367
- squirrels/_package_data/base_project/.env +42 -0
- squirrels/_package_data/base_project/.env.example +42 -0
- squirrels/_package_data/base_project/assets/expenses.db +0 -0
- squirrels/_package_data/base_project/connections.yml +16 -0
- squirrels/_package_data/base_project/dashboards/dashboard_example.py +34 -0
- squirrels/_package_data/base_project/dashboards/dashboard_example.yml +22 -0
- squirrels/{package_data → _package_data}/base_project/docker/.dockerignore +5 -2
- squirrels/{package_data → _package_data}/base_project/docker/Dockerfile +3 -3
- squirrels/{package_data → _package_data}/base_project/docker/compose.yml +1 -1
- squirrels/_package_data/base_project/duckdb_init.sql +10 -0
- squirrels/{package_data/base_project/.gitignore → _package_data/base_project/gitignore} +3 -2
- squirrels/_package_data/base_project/macros/macros_example.sql +17 -0
- squirrels/_package_data/base_project/models/builds/build_example.py +26 -0
- squirrels/_package_data/base_project/models/builds/build_example.sql +16 -0
- squirrels/_package_data/base_project/models/builds/build_example.yml +57 -0
- squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +12 -0
- squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +26 -0
- squirrels/_package_data/base_project/models/federates/federate_example.py +37 -0
- squirrels/_package_data/base_project/models/federates/federate_example.sql +19 -0
- squirrels/_package_data/base_project/models/federates/federate_example.yml +65 -0
- squirrels/_package_data/base_project/models/sources.yml +38 -0
- squirrels/{package_data → _package_data}/base_project/parameters.yml +56 -40
- squirrels/_package_data/base_project/pyconfigs/connections.py +14 -0
- squirrels/{package_data → _package_data}/base_project/pyconfigs/context.py +21 -40
- squirrels/_package_data/base_project/pyconfigs/parameters.py +141 -0
- squirrels/_package_data/base_project/pyconfigs/user.py +44 -0
- squirrels/_package_data/base_project/seeds/seed_categories.yml +15 -0
- squirrels/_package_data/base_project/seeds/seed_subcategories.csv +15 -0
- squirrels/_package_data/base_project/seeds/seed_subcategories.yml +21 -0
- squirrels/_package_data/base_project/squirrels.yml.j2 +61 -0
- squirrels/_package_data/templates/dataset_results.html +112 -0
- squirrels/_package_data/templates/oauth_login.html +271 -0
- squirrels/_package_data/templates/squirrels_studio.html +20 -0
- squirrels/_package_loader.py +8 -4
- squirrels/_parameter_configs.py +104 -103
- squirrels/_parameter_options.py +348 -0
- squirrels/_parameter_sets.py +57 -47
- squirrels/_parameters.py +1664 -0
- squirrels/_project.py +721 -0
- squirrels/_py_module.py +7 -5
- squirrels/_schemas/__init__.py +0 -0
- squirrels/_schemas/auth_models.py +167 -0
- squirrels/_schemas/query_param_models.py +75 -0
- squirrels/{_api_response_models.py → _schemas/response_models.py} +126 -47
- squirrels/_seeds.py +35 -16
- squirrels/_sources.py +110 -0
- squirrels/_utils.py +248 -73
- squirrels/_version.py +1 -1
- squirrels/arguments.py +7 -0
- squirrels/auth.py +4 -0
- squirrels/connections.py +3 -0
- squirrels/dashboards.py +2 -81
- squirrels/data_sources.py +14 -631
- squirrels/parameter_options.py +13 -348
- squirrels/parameters.py +14 -1266
- squirrels/types.py +16 -0
- squirrels-0.5.0.dist-info/METADATA +113 -0
- squirrels-0.5.0.dist-info/RECORD +97 -0
- {squirrels-0.4.0.dist-info → squirrels-0.5.0.dist-info}/WHEEL +1 -1
- squirrels-0.5.0.dist-info/entry_points.txt +3 -0
- {squirrels-0.4.0.dist-info → squirrels-0.5.0.dist-info/licenses}/LICENSE +1 -1
- squirrels/_authenticator.py +0 -85
- squirrels/_dashboards_io.py +0 -61
- squirrels/_environcfg.py +0 -84
- squirrels/arguments/init_time_args.py +0 -40
- squirrels/arguments/run_time_args.py +0 -208
- squirrels/package_data/assets/favicon.ico +0 -0
- squirrels/package_data/assets/index.css +0 -1
- squirrels/package_data/assets/index.js +0 -58
- squirrels/package_data/base_project/assets/expenses.db +0 -0
- squirrels/package_data/base_project/connections.yml +0 -7
- squirrels/package_data/base_project/dashboards/dashboard_example.py +0 -32
- squirrels/package_data/base_project/dashboards.yml +0 -10
- squirrels/package_data/base_project/env.yml +0 -29
- squirrels/package_data/base_project/models/dbviews/dbview_example.py +0 -47
- squirrels/package_data/base_project/models/dbviews/dbview_example.sql +0 -22
- squirrels/package_data/base_project/models/federates/federate_example.py +0 -21
- squirrels/package_data/base_project/models/federates/federate_example.sql +0 -3
- squirrels/package_data/base_project/pyconfigs/auth.py +0 -45
- squirrels/package_data/base_project/pyconfigs/connections.py +0 -19
- squirrels/package_data/base_project/pyconfigs/parameters.py +0 -95
- squirrels/package_data/base_project/seeds/seed_subcategories.csv +0 -15
- squirrels/package_data/base_project/squirrels.yml.j2 +0 -94
- squirrels/package_data/templates/index.html +0 -18
- squirrels/project.py +0 -378
- squirrels/user_base.py +0 -55
- squirrels-0.4.0.dist-info/METADATA +0 -117
- squirrels-0.4.0.dist-info/RECORD +0 -60
- squirrels-0.4.0.dist-info/entry_points.txt +0 -4
- /squirrels/{package_data → _package_data}/base_project/assets/weather.db +0 -0
- /squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.csv +0 -0
- /squirrels/{package_data → _package_data}/base_project/tmp/.gitignore +0 -0
squirrels/_py_module.py
CHANGED
|
@@ -2,6 +2,7 @@ from typing import Type, Optional, Any
|
|
|
2
2
|
import importlib.util
|
|
3
3
|
|
|
4
4
|
from . import _constants as c, _utils as u
|
|
5
|
+
from ._exceptions import ConfigurationError, FileExecutionError
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class PyModule:
|
|
@@ -21,7 +22,7 @@ class PyModule:
|
|
|
21
22
|
spec.loader.exec_module(self.module)
|
|
22
23
|
except FileNotFoundError as e:
|
|
23
24
|
if is_required:
|
|
24
|
-
raise
|
|
25
|
+
raise ConfigurationError(f"Required file not found: '{self.filepath}'") from e
|
|
25
26
|
self.module = default_class
|
|
26
27
|
|
|
27
28
|
def get_func_or_class(self, attr_name: str, *, default_attr: Any = None, is_required: bool = True) -> Any:
|
|
@@ -38,15 +39,16 @@ class PyModule:
|
|
|
38
39
|
"""
|
|
39
40
|
func_or_class = getattr(self.module, attr_name, default_attr)
|
|
40
41
|
if func_or_class is None and is_required:
|
|
41
|
-
raise
|
|
42
|
+
raise ConfigurationError(f"Module '{self.filepath}' missing required attribute '{attr_name}'")
|
|
42
43
|
return func_or_class
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
def run_pyconfig_main(base_path: str, filename: str, kwargs: dict[str, Any] = {}) -> None:
|
|
46
|
+
def run_pyconfig_main(base_path: str, filename: str, kwargs: dict[str, Any] = {}) -> Any | None:
|
|
46
47
|
"""
|
|
47
48
|
Given a python file in the 'pyconfigs' folder, run its main function
|
|
48
49
|
|
|
49
50
|
Arguments:
|
|
51
|
+
base_path: The base path of the project
|
|
50
52
|
filename: The name of the file to run main function
|
|
51
53
|
kwargs: Dictionary of the main function arguments
|
|
52
54
|
"""
|
|
@@ -55,6 +57,6 @@ def run_pyconfig_main(base_path: str, filename: str, kwargs: dict[str, Any] = {}
|
|
|
55
57
|
main_function = module.get_func_or_class(c.MAIN_FUNC, is_required=False)
|
|
56
58
|
if main_function:
|
|
57
59
|
try:
|
|
58
|
-
main_function(**kwargs)
|
|
60
|
+
return main_function(**kwargs)
|
|
59
61
|
except Exception as e:
|
|
60
|
-
raise
|
|
62
|
+
raise FileExecutionError(f'Failed to run python file "{filepath}"', e) from e
|
|
File without changes
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from typing import Callable, Any, Literal
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CustomUserFields(BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
Extend this class to add custom user fields.
|
|
9
|
+
- Only the following types are supported: [str, int, float, bool, typing.Literal]
|
|
10
|
+
- Add "| None" after the type to make it nullable.
|
|
11
|
+
- Always set a default value for the column (use None if default is null).
|
|
12
|
+
"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AbstractUser(BaseModel):
|
|
17
|
+
model_config = ConfigDict(from_attributes=True)
|
|
18
|
+
username: str
|
|
19
|
+
access_level: Literal["admin", "member", "guest"]
|
|
20
|
+
custom_fields: CustomUserFields
|
|
21
|
+
|
|
22
|
+
def __hash__(self):
|
|
23
|
+
return hash(self.username)
|
|
24
|
+
|
|
25
|
+
def __str__(self):
|
|
26
|
+
return self.username
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class GuestUser(AbstractUser):
|
|
30
|
+
access_level: Literal["guest"] = "guest"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RegisteredUser(AbstractUser):
|
|
34
|
+
access_level: Literal["admin", "member"] = "member"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ApiKey(BaseModel):
|
|
38
|
+
model_config = ConfigDict(from_attributes=True)
|
|
39
|
+
id: str
|
|
40
|
+
title: str
|
|
41
|
+
username: str
|
|
42
|
+
created_at: datetime
|
|
43
|
+
expires_at: datetime
|
|
44
|
+
|
|
45
|
+
@field_serializer('created_at', 'expires_at')
|
|
46
|
+
def serialize_datetime(self, dt: datetime) -> str:
|
|
47
|
+
return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class UserField(BaseModel):
|
|
51
|
+
name: str
|
|
52
|
+
type: str
|
|
53
|
+
nullable: bool
|
|
54
|
+
enum: list[str] | None
|
|
55
|
+
default: Any | None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ProviderConfigs(BaseModel):
|
|
59
|
+
client_id: str
|
|
60
|
+
client_secret: str
|
|
61
|
+
server_url: str
|
|
62
|
+
server_metadata_path: str = Field(default="/.well-known/openid-configuration")
|
|
63
|
+
client_kwargs: dict = Field(default_factory=dict)
|
|
64
|
+
get_user: Callable[[dict], RegisteredUser]
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def server_metadata_url(self) -> str:
|
|
68
|
+
return f"{self.server_url}{self.server_metadata_path}"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class AuthProvider(BaseModel):
|
|
72
|
+
name: str
|
|
73
|
+
label: str
|
|
74
|
+
icon: str
|
|
75
|
+
provider_configs: ProviderConfigs
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# OAuth 2.1 Models
|
|
79
|
+
|
|
80
|
+
class OAuthClientModel(BaseModel):
|
|
81
|
+
"""OAuth client details"""
|
|
82
|
+
model_config = ConfigDict(from_attributes=True)
|
|
83
|
+
client_id: str
|
|
84
|
+
client_name: str
|
|
85
|
+
redirect_uris: list[str]
|
|
86
|
+
scope: str
|
|
87
|
+
grant_types: list[str]
|
|
88
|
+
response_types: list[str]
|
|
89
|
+
created_at: datetime
|
|
90
|
+
is_active: bool
|
|
91
|
+
|
|
92
|
+
@field_serializer('created_at')
|
|
93
|
+
def serialize_datetime(self, dt: datetime) -> str:
|
|
94
|
+
return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class ClientRegistrationRequest(BaseModel):
|
|
98
|
+
"""Request model for OAuth client registration"""
|
|
99
|
+
client_name: str = Field(description="Human-readable name for the OAuth client")
|
|
100
|
+
redirect_uris: list[str] = Field(description="List of allowed redirect URIs for the client")
|
|
101
|
+
scope: str = Field(default="read", description="Default scope for the client")
|
|
102
|
+
grant_types: list[str] = Field(default=["authorization_code", "refresh_token"], description="Allowed grant types")
|
|
103
|
+
response_types: list[str] = Field(default=["code"], description="Allowed response types")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ClientUpdateRequest(BaseModel):
|
|
107
|
+
"""Request model for OAuth client update"""
|
|
108
|
+
client_name: str | None = Field(default=None, description="Human-readable name for the OAuth client")
|
|
109
|
+
redirect_uris: list[str] | None = Field(default=None, description="List of allowed redirect URIs for the client")
|
|
110
|
+
scope: str | None = Field(default=None, description="Default scope for the client")
|
|
111
|
+
grant_types: list[str] | None = Field(default=None, description="Allowed grant types")
|
|
112
|
+
response_types: list[str] | None = Field(default=None, description="Allowed response types")
|
|
113
|
+
is_active: bool | None = Field(default=None, description="Whether the client is active")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ClientDetailsResponse(BaseModel):
|
|
117
|
+
"""Response model for OAuth client details (without client_secret)"""
|
|
118
|
+
client_id: str = Field(description="Client ID")
|
|
119
|
+
client_name: str = Field(description="Client name")
|
|
120
|
+
redirect_uris: list[str] = Field(description="Registered redirect URIs")
|
|
121
|
+
scope: str = Field(description="Default scope")
|
|
122
|
+
grant_types: list[str] = Field(description="Allowed grant types")
|
|
123
|
+
response_types: list[str] = Field(description="Allowed response types")
|
|
124
|
+
created_at: datetime = Field(description="Registration timestamp")
|
|
125
|
+
is_active: bool = Field(description="Whether the client is active")
|
|
126
|
+
|
|
127
|
+
@field_serializer('created_at')
|
|
128
|
+
def serialize_datetime(self, dt: datetime) -> str:
|
|
129
|
+
return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ClientUpdateResponse(ClientDetailsResponse):
|
|
133
|
+
"""Response model for OAuth client update"""
|
|
134
|
+
registration_access_token: str | None = Field(default=None, description="Token for managing this client registration (store securely)")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ClientRegistrationResponse(ClientUpdateResponse):
|
|
138
|
+
"""Response model for OAuth client registration"""
|
|
139
|
+
client_secret: str = Field(description="Generated client secret (store securely)")
|
|
140
|
+
registration_client_uri: str | None = Field(default=None, description="URI for managing this client registration")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class TokenResponse(BaseModel):
|
|
144
|
+
access_token: str
|
|
145
|
+
token_type: str = "bearer"
|
|
146
|
+
expires_in: int
|
|
147
|
+
refresh_token: str | None = None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class OAuthServerMetadata(BaseModel):
|
|
151
|
+
"""OAuth 2.1 Authorization Server Metadata (RFC 8414)"""
|
|
152
|
+
issuer: str = Field(description="Authorization server's issuer identifier URL")
|
|
153
|
+
authorization_endpoint: str = Field(description="URL of the authorization endpoint")
|
|
154
|
+
token_endpoint: str = Field(description="URL of the token endpoint")
|
|
155
|
+
revocation_endpoint: str = Field(description="URL of the token revocation endpoint")
|
|
156
|
+
registration_endpoint: str = Field(description="URL of the client registration endpoint")
|
|
157
|
+
scopes_supported: list[str] = Field(description="List of OAuth 2.1 scope values supported")
|
|
158
|
+
response_types_supported: list[str] = Field(description="List of OAuth 2.1 response_type values supported")
|
|
159
|
+
grant_types_supported: list[str] = Field(description="List of OAuth 2.1 grant type values supported")
|
|
160
|
+
token_endpoint_auth_methods_supported: list[str] = Field(
|
|
161
|
+
default=["client_secret_basic", "client_secret_post"],
|
|
162
|
+
description="List of client authentication methods supported by the token endpoint"
|
|
163
|
+
)
|
|
164
|
+
code_challenge_methods_supported: list[str] = Field(
|
|
165
|
+
default=["S256"],
|
|
166
|
+
description="List of PKCE code challenge methods supported"
|
|
167
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Query model generation utilities for API routes
|
|
3
|
+
"""
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
from dataclasses import make_dataclass
|
|
6
|
+
from fastapi import Depends
|
|
7
|
+
from pydantic import create_model
|
|
8
|
+
|
|
9
|
+
from .._parameter_configs import APIParamFieldInfo
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _get_query_models_helper(widget_parameters: list[str] | None, predefined_params: list[APIParamFieldInfo], param_fields: dict):
|
|
13
|
+
"""Helper function to generate query models"""
|
|
14
|
+
if widget_parameters is None:
|
|
15
|
+
widget_parameters = list(param_fields.keys())
|
|
16
|
+
|
|
17
|
+
QueryModelForGetRaw = make_dataclass("QueryParams", [
|
|
18
|
+
param_fields[param].as_query_info() for param in widget_parameters
|
|
19
|
+
] + [param.as_query_info() for param in predefined_params])
|
|
20
|
+
QueryModelForGet = Annotated[QueryModelForGetRaw, Depends()]
|
|
21
|
+
|
|
22
|
+
field_definitions = {param: param_fields[param].as_body_info() for param in widget_parameters}
|
|
23
|
+
for param in predefined_params:
|
|
24
|
+
field_definitions[param.name] = param.as_body_info()
|
|
25
|
+
QueryModelForPost = create_model("RequestBodyParams", **field_definitions) # type: ignore
|
|
26
|
+
return QueryModelForGet, QueryModelForPost
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_query_models_for_parameters(widget_parameters: list[str] | None, param_fields: dict):
|
|
30
|
+
"""Generate query models for parameter endpoints"""
|
|
31
|
+
predefined_params = [
|
|
32
|
+
APIParamFieldInfo("x_verify_params", bool, default=False, description="If true, the query parameters are verified to be valid for the dataset"),
|
|
33
|
+
APIParamFieldInfo("x_parent_param", str, description="The parameter name used for parameter updates. If not provided, then all parameters are retrieved"),
|
|
34
|
+
]
|
|
35
|
+
return _get_query_models_helper(widget_parameters, predefined_params, param_fields)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_query_models_for_dataset(widget_parameters: list[str] | None, param_fields: dict):
|
|
39
|
+
"""Generate query models for dataset endpoints"""
|
|
40
|
+
predefined_params = [
|
|
41
|
+
APIParamFieldInfo("x_verify_params", bool, default=False, description="If true, the query parameters are verified to be valid for the dataset"),
|
|
42
|
+
APIParamFieldInfo("x_orientation", str, default="records", description="The orientation of the data to return, one of: 'records', 'rows', or 'columns'"),
|
|
43
|
+
APIParamFieldInfo("x_sql_query", str, description="Optional DuckDB SQL to transform the final dataset. Use table name 'result' to reference the dataset."),
|
|
44
|
+
APIParamFieldInfo("x_offset", int, default=0, description="The number of rows to skip before returning data (applied after data caching)"),
|
|
45
|
+
APIParamFieldInfo("x_limit", int, default=1000, description="The maximum number of rows to return (applied after data caching and offset)"),
|
|
46
|
+
]
|
|
47
|
+
return _get_query_models_helper(widget_parameters, predefined_params, param_fields)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_query_models_for_dashboard(widget_parameters: list[str] | None, param_fields: dict):
|
|
51
|
+
"""Generate query models for dashboard endpoints"""
|
|
52
|
+
predefined_params = [
|
|
53
|
+
APIParamFieldInfo("x_verify_params", bool, default=False, description="If true, the query parameters are verified to be valid for the dashboard"),
|
|
54
|
+
]
|
|
55
|
+
return _get_query_models_helper(widget_parameters, predefined_params, param_fields)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_query_models_for_querying_models(param_fields: dict):
|
|
59
|
+
"""Generate query models for querying data models"""
|
|
60
|
+
predefined_params = [
|
|
61
|
+
APIParamFieldInfo("x_verify_params", bool, default=False, description="If true, the query parameters are verified to be valid"),
|
|
62
|
+
APIParamFieldInfo("x_orientation", str, default="records", description="The orientation of the data to return, one of: 'records', 'rows', or 'columns'"),
|
|
63
|
+
APIParamFieldInfo("x_offset", int, default=0, description="The number of rows to skip before returning data (applied after data caching)"),
|
|
64
|
+
APIParamFieldInfo("x_limit", int, default=1000, description="The maximum number of rows to return (applied after data caching and offset)"),
|
|
65
|
+
APIParamFieldInfo("x_sql_query", str, description="The SQL query to execute on the data models"),
|
|
66
|
+
]
|
|
67
|
+
return _get_query_models_helper(None, predefined_params, param_fields)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_query_models_for_compiled_models(param_fields: dict):
|
|
71
|
+
"""Generate query models for fetching compiled model SQL"""
|
|
72
|
+
predefined_params = [
|
|
73
|
+
APIParamFieldInfo("x_verify_params", bool, default=False, description="If true, the query parameters are verified to be valid for the model"),
|
|
74
|
+
]
|
|
75
|
+
return _get_query_models_helper(None, predefined_params, param_fields)
|
|
@@ -1,41 +1,20 @@
|
|
|
1
|
-
from typing import Annotated
|
|
1
|
+
from typing import Annotated, Literal, Any
|
|
2
2
|
from pydantic import BaseModel, Field
|
|
3
|
-
from datetime import
|
|
3
|
+
from datetime import date
|
|
4
4
|
|
|
5
|
+
from .. import _model_configs as mc, _sources as s
|
|
5
6
|
|
|
6
|
-
class LoginReponse(BaseModel):
|
|
7
|
-
access_token: Annotated[str, Field(examples=["encoded_jwt_token"], description="An encoded JSON web token to use subsequent API requests")]
|
|
8
|
-
token_type: Annotated[str, Field(examples=["bearer"], description='Always "bearer" for Bearer token')]
|
|
9
|
-
username: Annotated[str, Field(examples=["johndoe"], description='The username authenticated with from the form data')]
|
|
10
|
-
expiry_time: Annotated[datetime, Field(examples=["2023-08-01T12:00:00.000000Z"], description="The expiry time of the access token in yyyy-MM-dd'T'hh:mm:ss.SSSSSS'Z' format")]
|
|
11
7
|
|
|
8
|
+
## Simple Auth Response Models
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
class ApiKeyResponse(BaseModel):
|
|
11
|
+
api_key: Annotated[str, Field(examples=["sqrl-12345678"], description="The API key to use subsequent API requests")]
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class DatasetItemModel(BaseModel):
|
|
22
|
-
name: Annotated[str, Field(examples=["mydataset"], description=name_description)]
|
|
23
|
-
label: Annotated[str, Field(examples=["My Dataset"], description=label_description)]
|
|
24
|
-
description: Annotated[str, Field(examples=[""], description=description_description)]
|
|
25
|
-
parameters_path: Annotated[str, Field(examples=["/squirrels-v0/myproject/v1/dataset/mydataset/parameters"], description=parameters_path_description)]
|
|
26
|
-
result_path: Annotated[str, Field(examples=["/squirrels-v0/myproject/v1/dataset/mydataset"], description=result_path_description)]
|
|
27
|
-
|
|
28
|
-
class DashboardItemModel(BaseModel):
|
|
29
|
-
name: Annotated[str, Field(examples=["mydashboard"], description=name_description)]
|
|
30
|
-
label: Annotated[str, Field(examples=["My Dashboard"], description=label_description)]
|
|
31
|
-
description: Annotated[str, Field(examples=[""], description=description_description)]
|
|
32
|
-
parameters_path: Annotated[str, Field(examples=["/squirrels-v0/myproject/v1/dashboard/mydashboard/parameters"], description=parameters_path_description)]
|
|
33
|
-
result_path: Annotated[str, Field(examples=["/squirrels-v0/myproject/v1/dashboard/mydashboard"], description=result_path_description)]
|
|
34
|
-
result_format: Annotated[str, Field(examples=["png", "html"], description="The format of the dashboard's result API response (one of 'png' or 'html')")]
|
|
35
|
-
|
|
36
|
-
class CatalogModel(BaseModel):
|
|
37
|
-
datasets: Annotated[list[DatasetItemModel], Field(description="The list of accessible datasets")]
|
|
38
|
-
dashboards: Annotated[list[DashboardItemModel], Field(description="The list of accessible dashboards")]
|
|
13
|
+
class ProviderResponse(BaseModel):
|
|
14
|
+
name: Annotated[str, Field(examples=["my_provider"], description="The name of the provider")]
|
|
15
|
+
label: Annotated[str, Field(examples=["My Provider"], description="The human-friendly display name for the provider")]
|
|
16
|
+
icon: Annotated[str, Field(examples=["https://example.com/my_provider_icon.png"], description="The URL of the provider's icon")]
|
|
17
|
+
login_url: Annotated[str, Field(examples=["https://example.com/my_provider_login"], description="The URL to redirect to for provider login")]
|
|
39
18
|
|
|
40
19
|
|
|
41
20
|
## Parameters Response Models
|
|
@@ -45,7 +24,7 @@ class ParameterOptionModel(BaseModel):
|
|
|
45
24
|
label: Annotated[str, Field(examples=["My Option"], description="The human-friendly display name for the option")]
|
|
46
25
|
|
|
47
26
|
class ParameterModelBase(BaseModel):
|
|
48
|
-
widget_type: Annotated[str, Field(examples=["
|
|
27
|
+
widget_type: Annotated[str, Field(examples=["disabled"], description="The parameter type")]
|
|
49
28
|
name: Annotated[str, Field(examples=["my_unique_param_name"], description="The name of the parameter. Use this as the key when providing the API request parameters")]
|
|
50
29
|
label: Annotated[str, Field(examples=["My Parameter"], description="The human-friendly display name for the parameter")]
|
|
51
30
|
description: Annotated[str, Field(examples=[""], description="The description of the parameter")]
|
|
@@ -102,40 +81,140 @@ class TextParameterModel(ParameterModelBase):
|
|
|
102
81
|
description='A string for the input type (one of "text", "textarea", "number", "date", "datetime-local", "month", "time", "color", or "password")'
|
|
103
82
|
)]
|
|
104
83
|
|
|
84
|
+
ParametersListType = list[
|
|
85
|
+
NoneParameterModel | SingleSelectParameterModel | MultiSelectParameterModel | DateParameterModel | DateRangeParameterModel |
|
|
86
|
+
NumberParameterModel | NumberRangeParameterModel | TextParameterModel
|
|
87
|
+
]
|
|
88
|
+
|
|
105
89
|
class ParametersModel(BaseModel):
|
|
106
|
-
parameters: list
|
|
107
|
-
NoneParameterModel | SingleSelectParameterModel | MultiSelectParameterModel | DateParameterModel | DateRangeParameterModel |
|
|
108
|
-
NumberParameterModel | NumberRangeParameterModel | TextParameterModel
|
|
109
|
-
]
|
|
90
|
+
parameters: Annotated[ParametersListType, Field(description="The list of parameters for the dataset / dashboard")]
|
|
110
91
|
|
|
111
92
|
|
|
112
|
-
##
|
|
93
|
+
## Datasets / Dashboards Catalog Response Models
|
|
94
|
+
|
|
95
|
+
name_description = "The name of the dataset / dashboard (usually in snake case)"
|
|
96
|
+
label_description = "The human-friendly display name for the dataset / dashboard"
|
|
97
|
+
description_description = "The description of the dataset / dashboard"
|
|
98
|
+
parameters_path_description = "The API path to the parameters for the dataset / dashboard"
|
|
99
|
+
metadata_path_description = "The API path to the metadata (i.e., description and schema) for the dataset"
|
|
100
|
+
result_path_description = "The API path to the results for the dataset / dashboard"
|
|
101
|
+
|
|
102
|
+
class ConfigurableDefaultModel(BaseModel):
|
|
103
|
+
name: str
|
|
104
|
+
default: str
|
|
105
|
+
|
|
106
|
+
class ConfigurableItemModel(ConfigurableDefaultModel):
|
|
107
|
+
label: str
|
|
108
|
+
description: str
|
|
113
109
|
|
|
114
110
|
class ColumnModel(BaseModel):
|
|
115
111
|
name: Annotated[str, Field(examples=["mycol"], description="Name of column")]
|
|
116
|
-
type: Annotated[str, Field(examples=["string", "
|
|
112
|
+
type: Annotated[str, Field(examples=["string", "integer", "boolean", "datetime"], description='Column type (such as "string", "integer", "boolean", "datetime", etc.)')]
|
|
113
|
+
description: Annotated[str, Field(examples=["My column description"], description="The description of the column")]
|
|
114
|
+
category: Annotated[str, Field(examples=["dimension", "measure", "misc"], description="The category of the column (such as 'dimension', 'measure', or 'misc')")]
|
|
115
|
+
|
|
116
|
+
class ColumnWithConditionModel(ColumnModel):
|
|
117
|
+
condition: Annotated[str | None, Field(None, examples=["My condition"], description="The condition of when the column is included (such as based on a parameter selection)")]
|
|
117
118
|
|
|
118
119
|
class SchemaModel(BaseModel):
|
|
119
120
|
fields: Annotated[list[ColumnModel], Field(description="A list of JSON objects containing the 'name' and 'type' for each of the columns in the result")]
|
|
120
|
-
|
|
121
|
+
|
|
122
|
+
class SchemaWithConditionModel(BaseModel):
|
|
123
|
+
fields: Annotated[list[ColumnWithConditionModel], Field(description="A list of JSON objects containing the 'name' and 'type' for each of the columns in the result")]
|
|
124
|
+
|
|
125
|
+
class DatasetItemModel(BaseModel):
|
|
126
|
+
name: Annotated[str, Field(examples=["mydataset"], description=name_description)]
|
|
127
|
+
label: Annotated[str, Field(examples=["My Dataset"], description=label_description)]
|
|
128
|
+
description: Annotated[str, Field(examples=[""], description=description_description)]
|
|
129
|
+
configurables: Annotated[list[ConfigurableDefaultModel], Field(default_factory=list, description="The list of configurables with their default values")]
|
|
130
|
+
parameters: Annotated[list[str], Field(examples=["myparam1", "myparam2"], description="The list of parameter names used by the dataset. If the list is empty, the dataset does not accept any parameters.")]
|
|
131
|
+
data_schema: Annotated[SchemaWithConditionModel, Field(alias="schema", description="JSON object describing the schema of the dataset")]
|
|
132
|
+
parameters_path: Annotated[str, Field(examples=["/squirrels/v0/myproject/v1/dataset/mydataset/parameters"], description=parameters_path_description)]
|
|
133
|
+
result_path: Annotated[str, Field(examples=["/squirrels/v0/myproject/v1/dataset/mydataset"], description=result_path_description)]
|
|
134
|
+
|
|
135
|
+
class DashboardItemModel(ParametersModel):
|
|
136
|
+
name: Annotated[str, Field(examples=["mydashboard"], description=name_description)]
|
|
137
|
+
label: Annotated[str, Field(examples=["My Dashboard"], description=label_description)]
|
|
138
|
+
description: Annotated[str, Field(examples=[""], description=description_description)]
|
|
139
|
+
parameters: Annotated[list[str], Field(examples=["myparam1", "myparam2"], description="The list of parameter names used by the dashboard")]
|
|
140
|
+
parameters_path: Annotated[str, Field(examples=["/squirrels/v0/myproject/v1/dashboard/mydashboard/parameters"], description=parameters_path_description)]
|
|
141
|
+
result_path: Annotated[str, Field(examples=["/squirrels/v0/myproject/v1/dashboard/mydashboard"], description=result_path_description)]
|
|
142
|
+
result_format: Annotated[str, Field(examples=["png", "html"], description="The format of the dashboard's result API response (one of 'png' or 'html')")]
|
|
143
|
+
|
|
144
|
+
ModelConfigType = mc.ModelConfig | s.Source | mc.SeedConfig | mc.BuildModelConfig | mc.DbviewModelConfig | mc.FederateModelConfig
|
|
145
|
+
|
|
146
|
+
class ConnectionItemModel(BaseModel):
|
|
147
|
+
name: Annotated[str, Field(examples=["myconnection"], description="The name of the connection")]
|
|
148
|
+
label: Annotated[str, Field(examples=["My Connection"], description="The human-friendly display name for the connection")]
|
|
149
|
+
|
|
150
|
+
class DataModelItem(BaseModel):
|
|
151
|
+
name: Annotated[str, Field(examples=["model_name"], description="The name of the model")]
|
|
152
|
+
model_type: Annotated[Literal["source", "dbview", "federate", "seed", "build"], Field(
|
|
153
|
+
examples=["source", "dbview", "federate", "seed", "build"], description="The type of the model"
|
|
154
|
+
)]
|
|
155
|
+
config: Annotated[ModelConfigType, Field(description="The configuration of the model")]
|
|
156
|
+
is_queryable: Annotated[bool, Field(examples=[True], description="Whether the model is queryable")]
|
|
157
|
+
|
|
158
|
+
class LineageNode(BaseModel):
|
|
159
|
+
name: str
|
|
160
|
+
type: Literal["model", "dataset", "dashboard"]
|
|
161
|
+
|
|
162
|
+
class LineageRelation(BaseModel):
|
|
163
|
+
type: Literal["buildtime", "runtime"]
|
|
164
|
+
source: LineageNode
|
|
165
|
+
target: LineageNode
|
|
166
|
+
|
|
167
|
+
class CatalogModelForTool(BaseModel):
|
|
168
|
+
parameters: Annotated[ParametersListType, Field(description="The list of all parameters in the project. It is possible that not all parameters are used by a dataset.")]
|
|
169
|
+
datasets: Annotated[list[DatasetItemModel], Field(description="The list of accessible datasets")]
|
|
170
|
+
|
|
171
|
+
class CatalogModel(CatalogModelForTool):
|
|
172
|
+
dashboards: Annotated[list[DashboardItemModel], Field(description="The list of accessible dashboards")]
|
|
173
|
+
connections: Annotated[list[ConnectionItemModel], Field(description="The list of connections in the project (only provided for admin users)")]
|
|
174
|
+
models: Annotated[list[DataModelItem], Field(description="The list of data models in the project (only provided for admin users)")]
|
|
175
|
+
lineage: Annotated[list[LineageRelation], Field(description="The lineage information between data assets (only provided for admin users)")]
|
|
176
|
+
configurables: Annotated[list[ConfigurableItemModel], Field(description="The list of configurables (only provided for admin users)")]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
## Dataset Results Response Models
|
|
180
|
+
|
|
181
|
+
class DataDetailsModel(BaseModel):
|
|
182
|
+
num_rows: Annotated[int, Field(examples=[2], description="The number of rows in the data field")]
|
|
183
|
+
orientation: Annotated[Literal["records", "rows", "columns"], Field(examples=["records", "rows", "columns"], description="The orientation of the data field")]
|
|
121
184
|
|
|
122
185
|
class DatasetResultModel(BaseModel):
|
|
123
186
|
data_schema: Annotated[SchemaModel, Field(alias="schema", description="JSON object describing the schema of the dataset")]
|
|
124
|
-
|
|
125
|
-
|
|
187
|
+
total_num_rows: Annotated[int, Field(examples=[2], description="The total number of rows for the dataset")]
|
|
188
|
+
data_details: Annotated[DataDetailsModel, Field(description="A JSON object containing the details of the data field")]
|
|
189
|
+
data: Annotated[list[dict] | list[list] | dict[str, list], Field(
|
|
190
|
+
examples=[[{"mycol": "col_value1"}, {"mycol": "col_value2"}], [["col_value1"], ["col_value2"]], {"mycol": ["col_value1", "col_value2"]}],
|
|
126
191
|
description="A list of JSON objects where each object is a row of the tabular results. The keys and values of the object are column names (described in fields) and values of the row."
|
|
127
192
|
)]
|
|
128
193
|
|
|
129
194
|
|
|
195
|
+
## Compiled Query Response Model
|
|
196
|
+
|
|
197
|
+
class CompiledQueryModel(BaseModel):
|
|
198
|
+
language: Annotated[Literal["sql", "python"], Field(examples=["sql"], description="The language of the data model query: 'sql' or 'python'")]
|
|
199
|
+
definition: Annotated[str, Field("", description="The compiled SQL or Python definition of the data model.")]
|
|
200
|
+
placeholders: Annotated[dict[str, Any], Field({}, description="The placeholders for the data model.")]
|
|
201
|
+
|
|
202
|
+
|
|
130
203
|
## Project Metadata Response Models
|
|
131
204
|
|
|
132
205
|
class ProjectVersionModel(BaseModel):
|
|
133
|
-
major_version: int
|
|
134
|
-
|
|
135
|
-
token_path: Annotated[str, Field(examples=["/squirrels-v0/myproject/v1/token"])]
|
|
136
|
-
data_catalog_path: Annotated[str, Field(examples=["/squirrels-v0/myproject/v1/datasets"])]
|
|
206
|
+
major_version: Annotated[int, Field(examples=[1])]
|
|
207
|
+
data_catalog_path: Annotated[str, Field(examples=["/squirrels/v0/project/myproject/v1/data-catalog"])]
|
|
137
208
|
|
|
138
209
|
class ProjectModel(BaseModel):
|
|
139
210
|
name: Annotated[str, Field(examples=["myproject"])]
|
|
211
|
+
version: Annotated[str, Field(examples=["v1"])]
|
|
140
212
|
label: Annotated[str, Field(examples=["My Project"])]
|
|
141
|
-
|
|
213
|
+
description: Annotated[str, Field(examples=["My project description"])]
|
|
214
|
+
elevated_access_level: Annotated[Literal["admin", "member", "guest"], Field(
|
|
215
|
+
examples=["admin"], description="The access level required to access elevated features (such as configurables and data lineage)"
|
|
216
|
+
)]
|
|
217
|
+
redoc_path: Annotated[str, Field(examples=["/squirrels/v0/project/myproject/v1/redoc"])]
|
|
218
|
+
swagger_path: Annotated[str, Field(examples=["/squirrels/v0/project/myproject/v1/docs"])]
|
|
219
|
+
mcp_server_path: Annotated[str, Field(examples=["/squirrels/v0/project/myproject/v1/mcp"])]
|
|
220
|
+
squirrels_version: Annotated[str, Field(examples=["0.1.0"])]
|
squirrels/_seeds.py
CHANGED
|
@@ -1,39 +1,58 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
import os, time, glob,
|
|
2
|
+
import os, time, glob, polars as pl, json
|
|
3
3
|
|
|
4
|
-
from .
|
|
5
|
-
|
|
4
|
+
from . import _utils as u, _constants as c, _model_configs as mc
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Seed:
|
|
9
|
+
config: mc.SeedConfig
|
|
10
|
+
df: pl.LazyFrame
|
|
11
|
+
|
|
12
|
+
def __post_init__(self):
|
|
13
|
+
if self.config.cast_column_types:
|
|
14
|
+
exprs = []
|
|
15
|
+
for col_config in self.config.columns:
|
|
16
|
+
sqrl_dtype = "double" if col_config.type.lower().startswith("decimal") else col_config.type
|
|
17
|
+
polars_dtype = u.sqrl_dtypes_to_polars_dtypes.get(sqrl_dtype, pl.String)
|
|
18
|
+
exprs.append(pl.col(col_config.name).cast(polars_dtype))
|
|
19
|
+
|
|
20
|
+
self.df = self.df.with_columns(*exprs)
|
|
6
21
|
|
|
7
22
|
|
|
8
23
|
@dataclass
|
|
9
24
|
class Seeds:
|
|
10
|
-
_data: dict[str,
|
|
11
|
-
_manifest_cfg: ManifestConfig
|
|
25
|
+
_data: dict[str, Seed]
|
|
12
26
|
|
|
13
|
-
def run_query(self, sql_query: str) ->
|
|
14
|
-
|
|
15
|
-
return
|
|
27
|
+
def run_query(self, sql_query: str) -> pl.DataFrame:
|
|
28
|
+
dataframes = {key: seed.df for key, seed in self._data.items()}
|
|
29
|
+
return u.run_sql_on_dataframes(sql_query, dataframes)
|
|
16
30
|
|
|
17
|
-
def get_dataframes(self) -> dict[str,
|
|
31
|
+
def get_dataframes(self) -> dict[str, Seed]:
|
|
18
32
|
return self._data.copy()
|
|
19
33
|
|
|
20
34
|
|
|
21
35
|
class SeedsIO:
|
|
22
36
|
|
|
23
37
|
@classmethod
|
|
24
|
-
def load_files(cls, logger:
|
|
38
|
+
def load_files(cls, logger: u.Logger, base_path: str, env_vars: dict[str, str]) -> Seeds:
|
|
25
39
|
start = time.time()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
csv_dtype = None if infer_schema else str
|
|
40
|
+
infer_schema_setting: bool = u.to_bool(env_vars.get(c.SQRL_SEEDS_INFER_SCHEMA, "true"))
|
|
41
|
+
na_values_setting: list[str] = json.loads(env_vars.get(c.SQRL_SEEDS_NA_VALUES, "[]"))
|
|
29
42
|
|
|
30
43
|
seeds_dict = {}
|
|
31
44
|
csv_files = glob.glob(os.path.join(base_path, c.SEEDS_FOLDER, '**/*.csv'), recursive=True)
|
|
32
45
|
for csv_file in csv_files:
|
|
46
|
+
config_file = os.path.splitext(csv_file)[0] + '.yml'
|
|
47
|
+
config_dict = u.load_yaml_config(config_file) if os.path.exists(config_file) else {}
|
|
48
|
+
config = mc.SeedConfig(**config_dict)
|
|
49
|
+
|
|
33
50
|
file_stem = os.path.splitext(os.path.basename(csv_file))[0]
|
|
34
|
-
|
|
35
|
-
|
|
51
|
+
infer_schema = not config.cast_column_types and infer_schema_setting
|
|
52
|
+
df = pl.read_csv(csv_file, try_parse_dates=True, infer_schema=infer_schema, null_values=na_values_setting).lazy()
|
|
53
|
+
|
|
54
|
+
seeds_dict[file_stem] = Seed(config, df)
|
|
36
55
|
|
|
37
|
-
seeds = Seeds(seeds_dict
|
|
56
|
+
seeds = Seeds(seeds_dict)
|
|
38
57
|
logger.log_activity_time("loading seed files", start)
|
|
39
58
|
return seeds
|