squirrels 0.2.0rc1__tar.gz → 0.2.2__tar.gz
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.
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/PKG-INFO +13 -11
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/README.md +1 -1
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/pyproject.toml +16 -14
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/__init__.py +2 -2
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_api_server.py +66 -49
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_authenticator.py +2 -3
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_command_line.py +1 -1
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_constants.py +7 -5
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_environcfg.py +1 -1
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_initializer.py +1 -2
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_manifest.py +8 -12
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_models.py +43 -21
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_parameter_configs.py +4 -4
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_parameter_sets.py +1 -3
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_py_module.py +4 -2
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_utils.py +7 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/arguments/run_time_args.py +15 -4
- squirrels-0.2.2/squirrels/package_data/assets/favicon.ico +0 -0
- squirrels-0.2.2/squirrels/package_data/assets/index.js +49 -0
- {squirrels-0.2.0rc1/squirrels/package_data/base_project/ignores → squirrels-0.2.2/squirrels/package_data/base_project}/.gitignore +4 -0
- {squirrels-0.2.0rc1/squirrels/package_data/base_project → squirrels-0.2.2/squirrels/package_data/base_project/docker}/Dockerfile +2 -2
- squirrels-0.2.2/squirrels/package_data/base_project/docker/compose.yml +7 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/environcfg.yml +1 -1
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/parameters.yml +18 -18
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/pyconfigs/auth.py +10 -14
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/pyconfigs/context.py +12 -2
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/squirrels.yml.j2 +18 -6
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/parameter_options.py +24 -24
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/parameters.py +3 -3
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/user_base.py +10 -11
- squirrels-0.2.0rc1/squirrels/package_data/assets/favicon.ico +0 -0
- squirrels-0.2.0rc1/squirrels/package_data/assets/index.js +0 -49
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/LICENSE +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_connection_set.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_package_loader.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_timer.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/_version.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/arguments/init_time_args.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/data_sources.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/dateutils.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/assets/index.css +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/connections.yml +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/database/expenses.db +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/database/weather.db +0 -0
- {squirrels-0.2.0rc1/squirrels/package_data/base_project/ignores → squirrels-0.2.2/squirrels/package_data/base_project/docker}/.dockerignore +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/models/dbviews/database_view1.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/models/dbviews/database_view1.sql +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/models/federates/dataset_example.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/models/federates/dataset_example.sql +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/pyconfigs/connections.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/pyconfigs/parameters.py +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/seeds/mocks/category.csv +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/seeds/mocks/max_filter.csv +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/seeds/mocks/subcategory.csv +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/base_project/tmp/.gitignore +0 -0
- {squirrels-0.2.0rc1 → squirrels-0.2.2}/squirrels/package_data/templates/index.html +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: squirrels
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Squirrels - API Framework for Data Analytics
|
|
5
|
-
Home-page: https://squirrels-nest.github.io
|
|
5
|
+
Home-page: https://squirrels-nest.github.io
|
|
6
6
|
License: MIT
|
|
7
7
|
Author: Tim Huang
|
|
8
8
|
Author-email: tim.yuting@hotmail.com
|
|
@@ -17,19 +17,21 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Classifier: Typing :: Typed
|
|
20
|
+
Provides-Extra: duckdb
|
|
20
21
|
Requires-Dist: cachetools (>=5.3.2,<6.0.0)
|
|
21
22
|
Requires-Dist: cryptography (>=41.0.7,<42.0.0)
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: duckdb (>=0.10.0) ; extra == "duckdb"
|
|
24
|
+
Requires-Dist: fastapi (>=0.109.2,<0.110.0)
|
|
25
|
+
Requires-Dist: gitpython (>=3.1.41,<4.0.0)
|
|
26
|
+
Requires-Dist: inquirer (>=3.2.1,<4.0.0)
|
|
27
|
+
Requires-Dist: jinja2 (>=3.1.3,<4.0.0)
|
|
26
28
|
Requires-Dist: pandas (>=2.1.4,<3.0.0)
|
|
27
29
|
Requires-Dist: python-jose (>=3.3.0,<4.0.0)
|
|
28
|
-
Requires-Dist: python-multipart (>=0.0.
|
|
30
|
+
Requires-Dist: python-multipart (>=0.0.9,<0.0.10)
|
|
29
31
|
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
|
30
|
-
Requires-Dist: sqlalchemy (>=2.0.
|
|
31
|
-
Requires-Dist: uvicorn (>=0.
|
|
32
|
-
Project-URL: Documentation, https://squirrels-nest.github.io
|
|
32
|
+
Requires-Dist: sqlalchemy (>=2.0.25,<3.0.0)
|
|
33
|
+
Requires-Dist: uvicorn (>=0.27.1,<0.28.0)
|
|
34
|
+
Project-URL: Documentation, https://squirrels-nest.github.io
|
|
33
35
|
Project-URL: Repository, https://github.com/squirrels-nest/squirrels
|
|
34
36
|
Description-Content-Type: text/markdown
|
|
35
37
|
|
|
@@ -37,7 +39,7 @@ Description-Content-Type: text/markdown
|
|
|
37
39
|
|
|
38
40
|
Squirrels is an API framework that lets you create REST APIs for dynamic data analytics!
|
|
39
41
|
|
|
40
|
-
**Documentation**: <a href="https://squirrels-nest.github.io/
|
|
42
|
+
**Documentation**: <a href="https://squirrels-nest.github.io/" target="_blank">https://squirrels-nest.github.io/</a>
|
|
41
43
|
|
|
42
44
|
**Source Code**: <a href="https://github.com/squirrels-nest/squirrels" target="_blank">https://github.com/squirrels-nest/squirrels</a>
|
|
43
45
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Squirrels is an API framework that lets you create REST APIs for dynamic data analytics!
|
|
4
4
|
|
|
5
|
-
**Documentation**: <a href="https://squirrels-nest.github.io/
|
|
5
|
+
**Documentation**: <a href="https://squirrels-nest.github.io/" target="_blank">https://squirrels-nest.github.io/</a>
|
|
6
6
|
|
|
7
7
|
**Source Code**: <a href="https://github.com/squirrels-nest/squirrels" target="_blank">https://github.com/squirrels-nest/squirrels</a>
|
|
8
8
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "squirrels"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Squirrels - API Framework for Data Analytics"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
authors = ["Tim Huang <tim.yuting@hotmail.com>"]
|
|
7
7
|
readme = "README.md"
|
|
8
|
-
homepage = "https://squirrels-nest.github.io
|
|
8
|
+
homepage = "https://squirrels-nest.github.io"
|
|
9
9
|
repository = "https://github.com/squirrels-nest/squirrels"
|
|
10
|
-
documentation = "https://squirrels-nest.github.io
|
|
10
|
+
documentation = "https://squirrels-nest.github.io"
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Intended Audience :: Developers",
|
|
13
13
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
@@ -17,28 +17,30 @@ classifiers = [
|
|
|
17
17
|
|
|
18
18
|
[tool.poetry.scripts]
|
|
19
19
|
squirrels = "squirrels._command_line:main"
|
|
20
|
+
sqrl = "squirrels._command_line:main"
|
|
20
21
|
|
|
21
22
|
[tool.poetry.dependencies]
|
|
22
23
|
python = "^3.9"
|
|
23
24
|
cachetools = "^5.3.2"
|
|
24
25
|
cryptography = "^41.0.7"
|
|
25
|
-
fastapi = "^0.
|
|
26
|
-
gitpython = "^3.1.
|
|
27
|
-
inquirer = "^3.1
|
|
28
|
-
jinja2 = "^3.1.
|
|
26
|
+
fastapi = "^0.109.2"
|
|
27
|
+
gitpython = "^3.1.41"
|
|
28
|
+
inquirer = "^3.2.1"
|
|
29
|
+
jinja2 = "^3.1.3"
|
|
29
30
|
pandas = "^2.1.4"
|
|
30
31
|
python-jose = "^3.3.0"
|
|
31
|
-
python-multipart = "^0.0.
|
|
32
|
+
python-multipart = "^0.0.9"
|
|
32
33
|
pyyaml = "^6.0.1"
|
|
33
|
-
sqlalchemy = "^2.0.
|
|
34
|
-
uvicorn = "^0.
|
|
34
|
+
sqlalchemy = "^2.0.25"
|
|
35
|
+
uvicorn = "^0.27.1"
|
|
36
|
+
duckdb = { version = ">=0.10.0", optional = true }
|
|
35
37
|
|
|
36
38
|
|
|
37
|
-
[tool.poetry.
|
|
38
|
-
|
|
39
|
+
[tool.poetry.extras]
|
|
40
|
+
duckdb = ["duckdb"]
|
|
39
41
|
|
|
40
|
-
[tool.poetry.group.
|
|
41
|
-
|
|
42
|
+
[tool.poetry.group.test.dependencies]
|
|
43
|
+
pytest = "^7.4.4"
|
|
42
44
|
|
|
43
45
|
[build-system]
|
|
44
46
|
requires = ["poetry-core"]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
__version__ = '0.2.
|
|
1
|
+
__version__ = '0.2.2'
|
|
2
2
|
|
|
3
3
|
from .arguments.init_time_args import ConnectionsArgs, ParametersArgs
|
|
4
4
|
from .arguments.run_time_args import AuthArgs, ContextArgs, ModelDepsArgs, ModelArgs
|
|
5
5
|
from .parameter_options import SelectParameterOption, DateParameterOption, DateRangeParameterOption, NumberParameterOption, NumberRangeParameterOption
|
|
6
|
-
from .parameters import
|
|
6
|
+
from .parameters import SingleSelectParameter, MultiSelectParameter, DateParameter, DateRangeParameter, NumberParameter, NumberRangeParameter
|
|
7
7
|
from .data_sources import SingleSelectDataSource, MultiSelectDataSource, DateDataSource, DateRangeDataSource, NumberDataSource, NumberRangeDataSource
|
|
8
8
|
from .user_base import User, WrongPassword
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from typing import Iterable, Optional, Mapping, Callable, Coroutine, TypeVar, Any
|
|
2
|
-
from fastapi import Depends, FastAPI, Request, HTTPException, status
|
|
1
|
+
from typing import List, Iterable, Optional, Mapping, Callable, Coroutine, TypeVar, Any
|
|
2
|
+
from fastapi import Depends, FastAPI, Request, HTTPException, Response, status
|
|
3
3
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
4
4
|
from fastapi.templating import Jinja2Templates
|
|
5
5
|
from fastapi.staticfiles import StaticFiles
|
|
@@ -45,36 +45,35 @@ class ApiServer:
|
|
|
45
45
|
start = time.time()
|
|
46
46
|
app = FastAPI()
|
|
47
47
|
|
|
48
|
-
app.
|
|
48
|
+
@app.middleware("http")
|
|
49
|
+
async def catch_exceptions_middleware(request: Request, call_next):
|
|
50
|
+
try:
|
|
51
|
+
return await call_next(request)
|
|
52
|
+
except u.InvalidInputError as exc:
|
|
53
|
+
traceback.print_exc()
|
|
54
|
+
return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST,
|
|
55
|
+
content={"message": f"Invalid user input: {str(exc)}"})
|
|
56
|
+
except u.ConfigurationError as exc:
|
|
57
|
+
traceback.print_exc()
|
|
58
|
+
return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
59
|
+
content={"message": f"Squirrels configuration error: {str(exc)}"})
|
|
60
|
+
except NotImplementedError as exc:
|
|
61
|
+
traceback.print_exc()
|
|
62
|
+
return JSONResponse(status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
|
63
|
+
content={"message": f"Not implemented error: {str(exc)}"})
|
|
64
|
+
except Exception as exc:
|
|
65
|
+
traceback.print_exc()
|
|
66
|
+
return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
67
|
+
content={"message": f"Server error: {str(exc)}"})
|
|
68
|
+
|
|
69
|
+
app.add_middleware(
|
|
70
|
+
CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"],
|
|
71
|
+
expose_headers=["Applied-Username"]
|
|
72
|
+
)
|
|
49
73
|
|
|
50
74
|
squirrels_version_path = f'/squirrels-v{sq_major_version}'
|
|
51
75
|
partial_base_path = f'/{ManifestIO.obj.project_variables.get_name()}/v{ManifestIO.obj.project_variables.get_major_version()}'
|
|
52
76
|
base_path = squirrels_version_path + u.normalize_name_for_api(partial_base_path)
|
|
53
|
-
|
|
54
|
-
static_dir = u.join_paths(os.path.dirname(__file__), c.PACKAGE_DATA_FOLDER, c.ASSETS_FOLDER)
|
|
55
|
-
app.mount('/'+c.ASSETS_FOLDER, StaticFiles(directory=static_dir), name=c.ASSETS_FOLDER)
|
|
56
|
-
|
|
57
|
-
templates_dir = u.join_paths(os.path.dirname(__file__), c.PACKAGE_DATA_FOLDER, c.TEMPLATES_FOLDER)
|
|
58
|
-
templates = Jinja2Templates(directory=templates_dir)
|
|
59
|
-
|
|
60
|
-
# Exception handlers
|
|
61
|
-
@app.exception_handler(u.InvalidInputError)
|
|
62
|
-
async def invalid_input_error_handler(request: Request, exc: u.InvalidInputError):
|
|
63
|
-
traceback.print_exc()
|
|
64
|
-
return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST,
|
|
65
|
-
content={"message": f"Invalid user input: {str(exc)}"})
|
|
66
|
-
|
|
67
|
-
@app.exception_handler(u.ConfigurationError)
|
|
68
|
-
async def configuration_error_handler(request: Request, exc: u.InvalidInputError):
|
|
69
|
-
traceback.print_exc()
|
|
70
|
-
return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
71
|
-
content={"message": f"Squirrels configuration error: {str(exc)}"})
|
|
72
|
-
|
|
73
|
-
@app.exception_handler(NotImplementedError)
|
|
74
|
-
async def not_implemented_error_handler(request: Request, exc: u.InvalidInputError):
|
|
75
|
-
traceback.print_exc()
|
|
76
|
-
return JSONResponse(status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
|
77
|
-
content={"message": f"Not implemented error: {str(exc)}"})
|
|
78
77
|
|
|
79
78
|
# Helpers
|
|
80
79
|
T = TypeVar('T')
|
|
@@ -127,13 +126,21 @@ class ApiServer:
|
|
|
127
126
|
# Changing selections into a cachable "frozenset" that will later be converted to dictionary
|
|
128
127
|
selections = set()
|
|
129
128
|
for key, val in params.items():
|
|
130
|
-
if
|
|
129
|
+
if isinstance(val, List):
|
|
131
130
|
val = tuple(val)
|
|
132
131
|
selections.add((u.normalize_name(key), val))
|
|
133
132
|
selections = frozenset(selections)
|
|
134
133
|
|
|
135
134
|
return await api_function(user, dataset_normalized, selections, request_version)
|
|
136
135
|
|
|
136
|
+
async def do_cachable_action(cache: TTLCache, action: Callable[..., Coroutine[Any, Any, T]], *args) -> T:
|
|
137
|
+
cache_key = tuple(args)
|
|
138
|
+
result = cache.get(cache_key)
|
|
139
|
+
if result is None:
|
|
140
|
+
result = await action(*args)
|
|
141
|
+
cache[cache_key] = result
|
|
142
|
+
return result
|
|
143
|
+
|
|
137
144
|
# Login
|
|
138
145
|
token_path = base_path + '/token'
|
|
139
146
|
|
|
@@ -154,20 +161,14 @@ class ApiServer:
|
|
|
154
161
|
"expiry_time": expiry
|
|
155
162
|
}
|
|
156
163
|
|
|
157
|
-
async def get_current_user(token: str = Depends(oauth2_scheme)) -> Optional[User]:
|
|
164
|
+
async def get_current_user(response: Response, token: str = Depends(oauth2_scheme)) -> Optional[User]:
|
|
158
165
|
user = self.authenticator.get_user_from_token(token)
|
|
166
|
+
username = "" if user is None else user.username
|
|
167
|
+
response.headers["Applied-Username"] = username
|
|
159
168
|
return user
|
|
160
169
|
|
|
161
|
-
async def do_cachable_action(cache: TTLCache, action: Callable[..., Coroutine[Any, Any, T]], *args) -> T:
|
|
162
|
-
cache_key = tuple(args)
|
|
163
|
-
result = cache.get(cache_key)
|
|
164
|
-
if result is None:
|
|
165
|
-
result = await action(*args)
|
|
166
|
-
cache[cache_key] = result
|
|
167
|
-
return result
|
|
168
|
-
|
|
169
170
|
# Parameters API
|
|
170
|
-
parameters_path = base_path + '/{dataset}/parameters'
|
|
171
|
+
parameters_path = base_path + '/dataset/{dataset}/parameters'
|
|
171
172
|
|
|
172
173
|
parameters_cache_size = ManifestIO.obj.settings.get(c.PARAMETERS_CACHE_SIZE_SETTING, 1024)
|
|
173
174
|
parameters_cache_ttl = ManifestIO.obj.settings.get(c.PARAMETERS_CACHE_TTL_SETTING, 0)
|
|
@@ -209,7 +210,7 @@ class ApiServer:
|
|
|
209
210
|
return result
|
|
210
211
|
|
|
211
212
|
# Results API
|
|
212
|
-
results_path = base_path + '/{dataset}'
|
|
213
|
+
results_path = base_path + '/dataset/{dataset}'
|
|
213
214
|
|
|
214
215
|
results_cache_size = ManifestIO.obj.settings.get(c.RESULTS_CACHE_SIZE_SETTING, 128)
|
|
215
216
|
results_cache_ttl = ManifestIO.obj.settings.get(c.RESULTS_CACHE_TTL_SETTING, 0)
|
|
@@ -248,8 +249,10 @@ class ApiServer:
|
|
|
248
249
|
timer.add_activity_time("POST REQUEST total time for DATASET", start)
|
|
249
250
|
return result
|
|
250
251
|
|
|
251
|
-
# Catalog API
|
|
252
|
-
|
|
252
|
+
# Datasets Catalog API
|
|
253
|
+
datasets_path = base_path + '/datasets'
|
|
254
|
+
|
|
255
|
+
def get_datasets0(user: Optional[User]):
|
|
253
256
|
datasets_info = []
|
|
254
257
|
for dataset_name, dataset_config in self.dataset_configs.items():
|
|
255
258
|
if can_user_access_dataset(user, dataset_name):
|
|
@@ -258,30 +261,44 @@ class ApiServer:
|
|
|
258
261
|
'name': dataset_name,
|
|
259
262
|
'label': dataset_config.label,
|
|
260
263
|
'parameters_path': parameters_path.format(dataset=dataset_normalized),
|
|
261
|
-
'result_path': results_path.format(dataset=dataset_normalized)
|
|
262
|
-
'first_minor_version': 0
|
|
264
|
+
'result_path': results_path.format(dataset=dataset_normalized)
|
|
263
265
|
})
|
|
264
|
-
|
|
266
|
+
return {"datasets": datasets_info}
|
|
267
|
+
|
|
268
|
+
@app.get(datasets_path)
|
|
269
|
+
def get_datasets(request: Request, user: Optional[User] = Depends(get_current_user)):
|
|
270
|
+
return process_based_on_response_version_header(request.headers, {
|
|
271
|
+
0: lambda: get_datasets0(user)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
# Projects Catalog API
|
|
275
|
+
def get_catalog0():
|
|
265
276
|
return {
|
|
266
277
|
'projects': [{
|
|
267
278
|
'name': ManifestIO.obj.project_variables.get_name(),
|
|
268
279
|
'label': ManifestIO.obj.project_variables.get_label(),
|
|
269
280
|
'versions': [{
|
|
270
281
|
'major_version': ManifestIO.obj.project_variables.get_major_version(),
|
|
271
|
-
'
|
|
282
|
+
'minor_versions': [0],
|
|
272
283
|
'token_path': token_path,
|
|
273
|
-
'
|
|
284
|
+
'datasets_path': datasets_path
|
|
274
285
|
}]
|
|
275
286
|
}]
|
|
276
287
|
}
|
|
277
288
|
|
|
278
289
|
@app.get(squirrels_version_path, response_class=JSONResponse)
|
|
279
|
-
async def get_catalog(request: Request
|
|
290
|
+
async def get_catalog(request: Request):
|
|
280
291
|
return process_based_on_response_version_header(request.headers, {
|
|
281
|
-
0: lambda: get_catalog0(
|
|
292
|
+
0: lambda: get_catalog0()
|
|
282
293
|
})
|
|
283
294
|
|
|
284
295
|
# Squirrels UI
|
|
296
|
+
static_dir = u.join_paths(os.path.dirname(__file__), c.PACKAGE_DATA_FOLDER, c.ASSETS_FOLDER)
|
|
297
|
+
app.mount('/'+c.ASSETS_FOLDER, StaticFiles(directory=static_dir), name=c.ASSETS_FOLDER)
|
|
298
|
+
|
|
299
|
+
templates_dir = u.join_paths(os.path.dirname(__file__), c.PACKAGE_DATA_FOLDER, c.TEMPLATES_FOLDER)
|
|
300
|
+
templates = Jinja2Templates(directory=templates_dir)
|
|
301
|
+
|
|
285
302
|
@app.get('/', response_class=HTMLResponse)
|
|
286
303
|
async def get_ui(request: Request):
|
|
287
304
|
return templates.TemplateResponse('index.html', {
|
|
@@ -34,7 +34,7 @@ class Authenticator:
|
|
|
34
34
|
return AuthArgs(conn_args.proj_vars, conn_args.env_vars, conn_args._get_credential, connections, username, password)
|
|
35
35
|
|
|
36
36
|
def authenticate_user(self, username: str, password: str) -> Optional[User]:
|
|
37
|
-
user_cls = self.auth_helper.get_func_or_class("User", default_attr=User)
|
|
37
|
+
user_cls: type[User] = self.auth_helper.get_func_or_class("User", default_attr=User)
|
|
38
38
|
get_user = self.auth_helper.get_func_or_class(c.GET_USER_FUNC, is_required=False)
|
|
39
39
|
try:
|
|
40
40
|
real_user = get_user(self._get_auth_args(username, password)) if get_user is not None else None
|
|
@@ -48,9 +48,8 @@ class Authenticator:
|
|
|
48
48
|
fake_users = EnvironConfigIO.obj.get_users()
|
|
49
49
|
if username in fake_users and secrets.compare_digest(fake_users[username][c.USER_PWD_KEY], password):
|
|
50
50
|
is_internal = fake_users[username].get("is_internal", False)
|
|
51
|
-
user: User = user_cls(username, is_internal=is_internal)
|
|
52
51
|
try:
|
|
53
|
-
return
|
|
52
|
+
return user_cls.Create(username, fake_users[username], is_internal=is_internal)
|
|
54
53
|
except Exception as e:
|
|
55
54
|
raise u.FileExecutionError(f'Failed to create user from User model in {c.AUTH_FILE}', e)
|
|
56
55
|
|
|
@@ -33,7 +33,7 @@ def main():
|
|
|
33
33
|
|
|
34
34
|
compile_parser = subparsers.add_parser(c.COMPILE_CMD, help='Create files for rendered sql queries in the "target/compile" folder', add_help=False)
|
|
35
35
|
compile_parser.add_argument('-h', '--help', action="help", help="Show this help message and exit")
|
|
36
|
-
compile_parser.add_argument('-d', '--dataset', type=str, help="Select dataset to use for dataset
|
|
36
|
+
compile_parser.add_argument('-d', '--dataset', type=str, help="Select dataset to use for dataset traits. If not specified, all models for all datasets are compiled")
|
|
37
37
|
compile_parser.add_argument('-a', '--all-test-sets', action="store_true", help="Compile models for all selection test sets")
|
|
38
38
|
compile_parser.add_argument('-t', '--test-set', type=str, help="The selection test set to use. Default selections are used if not specified. Ignored if using --all-test-sets")
|
|
39
39
|
compile_parser.add_argument('-s', '--select', type=str, help="Select single model to compile. If not specified, all models for the dataset are compiled. Also, ignored if --dataset is not specified")
|
|
@@ -9,7 +9,6 @@ PROJ_VARS_KEY = 'project_variables'
|
|
|
9
9
|
PROJECT_NAME_KEY = 'name'
|
|
10
10
|
PROJECT_LABEL_KEY = 'label'
|
|
11
11
|
MAJOR_VERSION_KEY = 'major_version'
|
|
12
|
-
MINOR_VERSION_KEY = 'minor_version'
|
|
13
12
|
|
|
14
13
|
PACKAGES_KEY = 'packages'
|
|
15
14
|
PACKAGE_GIT_KEY = 'git'
|
|
@@ -48,7 +47,7 @@ DATASET_NAME_KEY = 'name'
|
|
|
48
47
|
DATASET_LABEL_KEY = 'label'
|
|
49
48
|
DATASET_MODEL_KEY = 'model'
|
|
50
49
|
DATASET_PARAMETERS_KEY = 'parameters'
|
|
51
|
-
|
|
50
|
+
DATASET_TRAITS_KEY = 'traits'
|
|
52
51
|
|
|
53
52
|
DATASET_SCOPE_KEY = 'scope'
|
|
54
53
|
PUBLIC_SCOPE = 'public'
|
|
@@ -109,14 +108,17 @@ PARAMETERS_OUTPUT = 'parameters.json'
|
|
|
109
108
|
FINAL_VIEW_OUT_STEM = 'final_view'
|
|
110
109
|
|
|
111
110
|
# Dataset setting names
|
|
112
|
-
AUTH_TOKEN_EXPIRE_SETTING = 'auth.token.
|
|
111
|
+
AUTH_TOKEN_EXPIRE_SETTING = 'auth.token.expire_minutes'
|
|
113
112
|
PARAMETERS_CACHE_SIZE_SETTING = 'parameters.cache.size'
|
|
114
|
-
PARAMETERS_CACHE_TTL_SETTING = 'parameters.cache.
|
|
113
|
+
PARAMETERS_CACHE_TTL_SETTING = 'parameters.cache.ttl_minutes'
|
|
115
114
|
RESULTS_CACHE_SIZE_SETTING = 'results.cache.size'
|
|
116
|
-
RESULTS_CACHE_TTL_SETTING = 'results.cache.
|
|
115
|
+
RESULTS_CACHE_TTL_SETTING = 'results.cache.ttl_minutes'
|
|
117
116
|
TEST_SET_DEFAULT_USED_SETTING = 'selection_test_sets.default_name_used'
|
|
118
117
|
DB_CONN_DEFAULT_USED_SETTING = 'connections.default_name_used'
|
|
119
118
|
DEFAULT_MATERIALIZE_SETTING = 'defaults.federates.materialized'
|
|
119
|
+
IN_MEMORY_DB_SETTING = 'in_memory_database'
|
|
120
|
+
SQLITE = 'sqlite'
|
|
121
|
+
DUCKDB = 'duckdb'
|
|
120
122
|
|
|
121
123
|
# Selection cfg sections
|
|
122
124
|
USER_ATTRIBUTES_SECTION = 'user_attributes'
|
|
@@ -43,7 +43,7 @@ class _EnvironConfig:
|
|
|
43
43
|
return credential[c.USERNAME_KEY], credential[c.PASSWORD_KEY]
|
|
44
44
|
|
|
45
45
|
def get_secret(self, key: str, *, default_factory: Optional[Callable[[],str]] = None) -> str:
|
|
46
|
-
if
|
|
46
|
+
if not self._secrets.get(key) and default_factory is not None:
|
|
47
47
|
self._secrets[key] = default_factory()
|
|
48
48
|
return self._secrets.get(key)
|
|
49
49
|
|
|
@@ -40,7 +40,6 @@ class Initializer:
|
|
|
40
40
|
options = ["core", "connections", "parameters", "dbview", "federate", "auth", "sample_db"]
|
|
41
41
|
CORE, CONNECTIONS, PARAMETERS, DBVIEW, FEDERATE, AUTH, SAMPLE_DB = options
|
|
42
42
|
TMP_FOLDER = "tmp"
|
|
43
|
-
IGNORES_FOLDER = "ignores"
|
|
44
43
|
|
|
45
44
|
answers = { x: getattr(args, x) for x in options }
|
|
46
45
|
if not any(answers.values()):
|
|
@@ -136,7 +135,7 @@ class Initializer:
|
|
|
136
135
|
|
|
137
136
|
create_manifest_file()
|
|
138
137
|
|
|
139
|
-
self._copy_file(".gitignore"
|
|
138
|
+
self._copy_file(".gitignore")
|
|
140
139
|
self._copy_file(c.MANIFEST_FILE, src_folder=TMP_FOLDER)
|
|
141
140
|
|
|
142
141
|
if connections_use_py:
|
|
@@ -26,10 +26,10 @@ class ProjectVarsConfig(ManifestComponentConfig):
|
|
|
26
26
|
data: dict
|
|
27
27
|
|
|
28
28
|
def __post_init__(self):
|
|
29
|
-
required_keys = [c.PROJECT_NAME_KEY, c.MAJOR_VERSION_KEY
|
|
29
|
+
required_keys = [c.PROJECT_NAME_KEY, c.MAJOR_VERSION_KEY]
|
|
30
30
|
self._validate_required(self.data, required_keys, c.PROJ_VARS_KEY)
|
|
31
31
|
|
|
32
|
-
integer_keys = [c.MAJOR_VERSION_KEY
|
|
32
|
+
integer_keys = [c.MAJOR_VERSION_KEY]
|
|
33
33
|
for key in integer_keys:
|
|
34
34
|
if key in self.data and not isinstance(self.data[key], int):
|
|
35
35
|
raise u.ConfigurationError(f'Project variable "{key}" must be an integer')
|
|
@@ -46,9 +46,6 @@ class ProjectVarsConfig(ManifestComponentConfig):
|
|
|
46
46
|
|
|
47
47
|
def get_major_version(self) -> int:
|
|
48
48
|
return self.data[c.MAJOR_VERSION_KEY]
|
|
49
|
-
|
|
50
|
-
def get_minor_version(self) -> int:
|
|
51
|
-
return self.data[c.MINOR_VERSION_KEY]
|
|
52
49
|
|
|
53
50
|
|
|
54
51
|
@dataclass
|
|
@@ -84,14 +81,13 @@ class DbConnConfig(ManifestComponentConfig):
|
|
|
84
81
|
|
|
85
82
|
@dataclass
|
|
86
83
|
class ParametersConfig(ManifestComponentConfig):
|
|
87
|
-
name: str
|
|
88
84
|
type: str
|
|
89
85
|
factory: str
|
|
90
86
|
arguments: dict
|
|
91
87
|
|
|
92
88
|
@classmethod
|
|
93
89
|
def from_dict(cls, kwargs: dict):
|
|
94
|
-
all_keys = [c.
|
|
90
|
+
all_keys = [c.PARAMETER_TYPE_KEY, c.PARAMETER_FACTORY_KEY, c.PARAMETER_ARGS_KEY]
|
|
95
91
|
cls._validate_required(kwargs, all_keys, c.PARAMETERS_KEY)
|
|
96
92
|
args = {key: kwargs[key] for key in all_keys}
|
|
97
93
|
return cls(**args)
|
|
@@ -150,7 +146,7 @@ class DatasetsConfig(ManifestComponentConfig):
|
|
|
150
146
|
model: str
|
|
151
147
|
scope: DatasetScope
|
|
152
148
|
parameters: Optional[list[str]]
|
|
153
|
-
|
|
149
|
+
traits: dict
|
|
154
150
|
|
|
155
151
|
@classmethod
|
|
156
152
|
def from_dict(cls, kwargs: dict):
|
|
@@ -166,8 +162,8 @@ class DatasetsConfig(ManifestComponentConfig):
|
|
|
166
162
|
raise u.ConfigurationError(f'Scope not found for dataset "{name}". Scope must be one of {scope_list}') from e
|
|
167
163
|
|
|
168
164
|
parameters = kwargs.get(c.DATASET_PARAMETERS_KEY)
|
|
169
|
-
|
|
170
|
-
return cls(name, label, model, scope, parameters,
|
|
165
|
+
traits = kwargs.get(c.DATASET_TRAITS_KEY, {})
|
|
166
|
+
return cls(name, label, model, scope, parameters, traits)
|
|
171
167
|
|
|
172
168
|
|
|
173
169
|
@dataclass
|
|
@@ -175,7 +171,7 @@ class _ManifestConfig:
|
|
|
175
171
|
project_variables: ProjectVarsConfig
|
|
176
172
|
packages: list[PackageConfig]
|
|
177
173
|
connections: dict[str, DbConnConfig]
|
|
178
|
-
parameters:
|
|
174
|
+
parameters: list[ParametersConfig]
|
|
179
175
|
selection_test_sets: dict[str, TestSetsConfig]
|
|
180
176
|
dbviews: dict[str, DbviewConfig]
|
|
181
177
|
federates: dict[str, FederateConfig]
|
|
@@ -209,7 +205,7 @@ class _ManifestConfig:
|
|
|
209
205
|
all_package_dirs.add(package.directory)
|
|
210
206
|
|
|
211
207
|
db_conns = cls._create_configs_as_dict(DbConnConfig, kwargs, c.DB_CONNECTIONS_KEY, c.DB_CONN_NAME_KEY)
|
|
212
|
-
params =
|
|
208
|
+
params = [ParametersConfig.from_dict(x) for x in kwargs.get(c.PARAMETERS_KEY, [])]
|
|
213
209
|
|
|
214
210
|
test_sets = cls._create_configs_as_dict(TestSetsConfig, kwargs, c.TEST_SETS_KEY, c.TEST_SET_NAME_KEY)
|
|
215
211
|
default_test_set: str = settings.get(c.TEST_SET_DEFAULT_USED_SETTING, c.DEFAULT_TEST_SET_NAME)
|