squirrels 0.3.3__py3-none-any.whl → 0.4.1__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.
- squirrels/__init__.py +7 -3
- squirrels/_api_response_models.py +96 -72
- squirrels/_api_server.py +375 -201
- squirrels/_authenticator.py +23 -22
- squirrels/_command_line.py +70 -46
- squirrels/_connection_set.py +23 -25
- squirrels/_constants.py +29 -78
- squirrels/_dashboards_io.py +61 -0
- squirrels/_environcfg.py +53 -50
- squirrels/_initializer.py +184 -141
- squirrels/_manifest.py +168 -195
- squirrels/_models.py +159 -292
- squirrels/_package_loader.py +7 -8
- squirrels/_parameter_configs.py +173 -141
- squirrels/_parameter_sets.py +49 -38
- squirrels/_py_module.py +7 -7
- squirrels/_seeds.py +13 -12
- squirrels/_utils.py +114 -54
- squirrels/_version.py +1 -1
- squirrels/arguments/init_time_args.py +16 -10
- squirrels/arguments/run_time_args.py +89 -24
- squirrels/dashboards.py +82 -0
- squirrels/data_sources.py +212 -232
- squirrels/dateutils.py +29 -26
- squirrels/package_data/assets/index.css +1 -1
- squirrels/package_data/assets/index.js +27 -18
- squirrels/package_data/base_project/.gitignore +2 -2
- squirrels/package_data/base_project/connections.yml +1 -1
- squirrels/package_data/base_project/dashboards/dashboard_example.py +32 -0
- squirrels/package_data/base_project/dashboards.yml +10 -0
- squirrels/package_data/base_project/docker/.dockerignore +9 -4
- squirrels/package_data/base_project/docker/Dockerfile +7 -6
- squirrels/package_data/base_project/docker/compose.yml +1 -1
- squirrels/package_data/base_project/env.yml +2 -2
- squirrels/package_data/base_project/models/dbviews/{database_view1.py → dbview_example.py} +2 -1
- squirrels/package_data/base_project/models/dbviews/{database_view1.sql → dbview_example.sql} +3 -2
- squirrels/package_data/base_project/models/federates/{dataset_example.py → federate_example.py} +6 -6
- squirrels/package_data/base_project/models/federates/{dataset_example.sql → federate_example.sql} +1 -1
- squirrels/package_data/base_project/parameters.yml +6 -4
- squirrels/package_data/base_project/pyconfigs/auth.py +1 -1
- squirrels/package_data/base_project/pyconfigs/connections.py +1 -1
- squirrels/package_data/base_project/pyconfigs/context.py +38 -10
- squirrels/package_data/base_project/pyconfigs/parameters.py +15 -7
- squirrels/package_data/base_project/squirrels.yml.j2 +14 -7
- squirrels/package_data/templates/index.html +3 -3
- squirrels/parameter_options.py +103 -106
- squirrels/parameters.py +347 -195
- squirrels/project.py +378 -0
- squirrels/user_base.py +14 -6
- {squirrels-0.3.3.dist-info → squirrels-0.4.1.dist-info}/METADATA +9 -21
- squirrels-0.4.1.dist-info/RECORD +60 -0
- squirrels/_timer.py +0 -23
- squirrels-0.3.3.dist-info/RECORD +0 -56
- {squirrels-0.3.3.dist-info → squirrels-0.4.1.dist-info}/LICENSE +0 -0
- {squirrels-0.3.3.dist-info → squirrels-0.4.1.dist-info}/WHEEL +0 -0
- {squirrels-0.3.3.dist-info → squirrels-0.4.1.dist-info}/entry_points.txt +0 -0
squirrels/_authenticator.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Type, Optional
|
|
2
2
|
from datetime import datetime, timedelta, timezone
|
|
3
3
|
from jwt.exceptions import InvalidTokenError
|
|
4
4
|
import secrets, jwt
|
|
@@ -7,34 +7,36 @@ from . import _utils as u, _constants as c
|
|
|
7
7
|
from .arguments.run_time_args import AuthArgs
|
|
8
8
|
from ._py_module import PyModule
|
|
9
9
|
from .user_base import User, WrongPassword
|
|
10
|
-
from ._environcfg import
|
|
10
|
+
from ._environcfg import EnvironConfig
|
|
11
11
|
from ._manifest import DatasetScope
|
|
12
|
-
from ._connection_set import
|
|
12
|
+
from ._connection_set import ConnectionsArgs, ConnectionSet
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class Authenticator:
|
|
16
|
-
|
|
17
|
-
@classmethod
|
|
18
|
-
def get_auth_helper(cls, default_auth_helper = None):
|
|
19
|
-
auth_module_path = u.join_paths(c.PYCONFIGS_FOLDER, c.AUTH_FILE)
|
|
20
|
-
return PyModule(auth_module_path, default_class=default_auth_helper)
|
|
21
16
|
|
|
22
|
-
def __init__(self, token_expiry_minutes: int, auth_helper = None) -> None:
|
|
17
|
+
def __init__(self, base_path: str, env_cfg: EnvironConfig, conn_args: ConnectionsArgs, conn_set: ConnectionSet, token_expiry_minutes: int, *, auth_helper = None) -> None:
|
|
18
|
+
self.env_cfg = env_cfg
|
|
19
|
+
self.conn_args = conn_args
|
|
20
|
+
self.conn_set = conn_set
|
|
23
21
|
self.token_expiry_minutes = token_expiry_minutes
|
|
24
|
-
self.auth_helper = self.
|
|
22
|
+
self.auth_helper = self._get_auth_helper(base_path, default_auth_helper=auth_helper)
|
|
23
|
+
self.user_cls: Type[User] = self.auth_helper.get_func_or_class("User", default_attr=User)
|
|
25
24
|
self.secret_key = self._get_secret_key()
|
|
26
25
|
self.algorithm = "HS256"
|
|
26
|
+
|
|
27
|
+
def _get_auth_helper(self, base_path: str, *, default_auth_helper = None):
|
|
28
|
+
auth_module_path = u.Path(base_path, c.PYCONFIGS_FOLDER, c.AUTH_FILE)
|
|
29
|
+
return PyModule(auth_module_path, default_class=default_auth_helper)
|
|
27
30
|
|
|
28
|
-
def _get_secret_key(self):
|
|
29
|
-
secret_key =
|
|
30
|
-
return secret_key
|
|
31
|
+
def _get_secret_key(self) -> str:
|
|
32
|
+
secret_key = self.env_cfg.get_secret(c.JWT_SECRET_KEY, default_factory=lambda: secrets.token_hex(32))
|
|
33
|
+
return str(secret_key)
|
|
31
34
|
|
|
32
35
|
def _get_auth_args(self, username: str, password: str):
|
|
33
|
-
|
|
34
|
-
return AuthArgs(conn_args.proj_vars, conn_args.env_vars, conn_args._get_credential, connections, username, password)
|
|
36
|
+
connections = self.conn_set.get_engines_as_dict()
|
|
37
|
+
return AuthArgs(self.conn_args.proj_vars, self.conn_args.env_vars, self.conn_args._get_credential, connections, username, password)
|
|
35
38
|
|
|
36
39
|
def authenticate_user(self, username: str, password: str) -> Optional[User]:
|
|
37
|
-
user_cls: type[User] = self.auth_helper.get_func_or_class("User", default_attr=User)
|
|
38
40
|
get_user = self.auth_helper.get_func_or_class(c.GET_USER_FUNC, is_required=False)
|
|
39
41
|
try:
|
|
40
42
|
real_user = get_user(self._get_auth_args(username, password)) if get_user is not None else None
|
|
@@ -45,13 +47,13 @@ class Authenticator:
|
|
|
45
47
|
return real_user
|
|
46
48
|
|
|
47
49
|
if not isinstance(real_user, WrongPassword):
|
|
48
|
-
fake_users =
|
|
49
|
-
if username in fake_users and secrets.compare_digest(fake_users[username]
|
|
50
|
-
fake_user = fake_users[username].
|
|
50
|
+
fake_users = self.env_cfg.get_users()
|
|
51
|
+
if username in fake_users and secrets.compare_digest(fake_users[username].password, password):
|
|
52
|
+
fake_user = fake_users[username].model_dump()
|
|
51
53
|
fake_user.pop("username", "")
|
|
52
54
|
is_internal = fake_user.pop("is_internal", False)
|
|
53
55
|
try:
|
|
54
|
-
return user_cls.Create(username, is_internal=is_internal, **fake_user)
|
|
56
|
+
return self.user_cls.Create(username, is_internal=is_internal, **fake_user)
|
|
55
57
|
except Exception as e:
|
|
56
58
|
raise u.FileExecutionError(f'Failed to create user from User model in {c.AUTH_FILE}', e) from e
|
|
57
59
|
|
|
@@ -68,8 +70,7 @@ class Authenticator:
|
|
|
68
70
|
try:
|
|
69
71
|
payload: dict = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
|
|
70
72
|
payload.pop("exp")
|
|
71
|
-
|
|
72
|
-
return user_cls._FromDict(payload)
|
|
73
|
+
return self.user_cls._FromDict(payload)
|
|
73
74
|
except InvalidTokenError:
|
|
74
75
|
return None
|
|
75
76
|
|
squirrels/_command_line.py
CHANGED
|
@@ -1,93 +1,117 @@
|
|
|
1
|
-
from argparse import ArgumentParser
|
|
2
|
-
import sys,
|
|
1
|
+
from argparse import ArgumentParser, _SubParsersAction
|
|
2
|
+
import sys, asyncio, traceback, io
|
|
3
|
+
|
|
3
4
|
sys.path.append('.')
|
|
4
5
|
|
|
6
|
+
from ._version import __version__
|
|
7
|
+
from ._api_server import ApiServer
|
|
8
|
+
from ._initializer import Initializer
|
|
9
|
+
from ._package_loader import PackageLoaderIO
|
|
10
|
+
from .project import SquirrelsProject
|
|
5
11
|
from . import _constants as c
|
|
6
|
-
from ._timer import timer
|
|
7
12
|
|
|
8
13
|
|
|
9
14
|
def main():
|
|
10
15
|
"""
|
|
11
16
|
Main entry point for the squirrels command line utilities.
|
|
12
17
|
"""
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
def with_help(parser: ArgumentParser):
|
|
19
|
+
parser.add_argument('-h', '--help', action="help", help="Show this help message and exit")
|
|
20
|
+
return parser
|
|
21
|
+
|
|
22
|
+
parser = with_help(ArgumentParser(description="Command line utilities from the squirrels python package", add_help=False))
|
|
16
23
|
parser.add_argument('-V', '--version', action='store_true', help='Show the version and exit')
|
|
17
|
-
parser.add_argument('-
|
|
24
|
+
parser.add_argument('--log-level', type=str, choices=["DEBUG", "INFO", "WARNING"], default="INFO", help='Level of logging to use')
|
|
25
|
+
parser.add_argument('--log-format', type=str, choices=["text", "json"], default="text", help='Format of the log records')
|
|
26
|
+
parser.add_argument('--log-file', type=str, default=c.LOGS_FILE, help=f'Name of log file to write to in the "logs/" folder. Default is {c.LOGS_FILE}. If name is empty, then file logging is disabled')
|
|
18
27
|
subparsers = parser.add_subparsers(title='commands', dest='command')
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
def add_subparser(subparsers: _SubParsersAction, cmd: str, help_text: str):
|
|
30
|
+
subparser = with_help(subparsers.add_parser(cmd, description=help_text, help=help_text, add_help=False))
|
|
31
|
+
return subparser
|
|
32
|
+
|
|
33
|
+
init_parser = add_subparser(subparsers, c.INIT_CMD, 'Initialize a squirrels project')
|
|
22
34
|
init_parser.add_argument('-o', '--overwrite', action='store_true', help="Overwrite files that already exist")
|
|
23
35
|
init_parser.add_argument('--core', action='store_true', help='Include all core files')
|
|
24
|
-
init_parser.add_argument('--connections', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure database connections as yaml (default) or python
|
|
25
|
-
init_parser.add_argument('--parameters', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure parameters as python (default) or yaml
|
|
26
|
-
init_parser.add_argument('--dbview', type=str, choices=c.FILE_TYPE_CHOICES, help='Create database view model as sql (default) or python file
|
|
27
|
-
init_parser.add_argument('--federate', type=str, choices=c.FILE_TYPE_CHOICES, help='Create federated model as sql (default) or python file
|
|
36
|
+
init_parser.add_argument('--connections', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure database connections as yaml (default) or python')
|
|
37
|
+
init_parser.add_argument('--parameters', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure parameters as python (default) or yaml')
|
|
38
|
+
init_parser.add_argument('--dbview', type=str, choices=c.FILE_TYPE_CHOICES, help='Create database view model as sql (default) or python file')
|
|
39
|
+
init_parser.add_argument('--federate', type=str, choices=c.FILE_TYPE_CHOICES, help='Create federated model as sql (default) or python file')
|
|
40
|
+
init_parser.add_argument('--dashboard', action='store_true', help=f'Include a sample dashboard file')
|
|
28
41
|
init_parser.add_argument('--auth', action='store_true', help=f'Include the {c.AUTH_FILE} file')
|
|
29
|
-
init_parser.add_argument('--sample-db', type=str, choices=c.DATABASE_CHOICES, help='Sample sqlite database to include')
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
def with_file_format_options(parser: ArgumentParser):
|
|
44
|
+
help_text = "Create model as sql (default) or python file"
|
|
45
|
+
parser.add_argument('--format', type=str, choices=c.FILE_TYPE_CHOICES, default=c.SQL_FILE_TYPE, help=help_text)
|
|
46
|
+
return parser
|
|
47
|
+
|
|
48
|
+
get_file_help_text = "Get a sample file for the squirrels project. If the file name already exists, it will be prefixed with a timestamp."
|
|
49
|
+
get_file_parser = add_subparser(subparsers, c.GET_FILE_CMD, get_file_help_text)
|
|
50
|
+
get_file_subparsers = get_file_parser.add_subparsers(title='file_name', dest='file_name')
|
|
51
|
+
add_subparser(get_file_subparsers, c.ENV_CONFIG_FILE, f'Get a sample {c.ENV_CONFIG_FILE} file')
|
|
52
|
+
manifest_parser = add_subparser(get_file_subparsers, c.MANIFEST_FILE, f'Get a sample {c.MANIFEST_FILE} file')
|
|
53
|
+
manifest_parser.add_argument("--no-connections", action='store_true', help=f'Exclude the connections section')
|
|
54
|
+
manifest_parser.add_argument("--parameters", action='store_true', help=f'Include the parameters section')
|
|
55
|
+
manifest_parser.add_argument("--dashboards", action='store_true', help=f'Include the dashboards section')
|
|
56
|
+
add_subparser(get_file_subparsers, c.AUTH_FILE, f'Get a sample {c.AUTH_FILE} file')
|
|
57
|
+
add_subparser(get_file_subparsers, c.CONNECTIONS_FILE, f'Get a sample {c.CONNECTIONS_FILE} file')
|
|
58
|
+
add_subparser(get_file_subparsers, c.PARAMETERS_FILE, f'Get a sample {c.PARAMETERS_FILE} file')
|
|
59
|
+
add_subparser(get_file_subparsers, c.CONTEXT_FILE, f'Get a sample {c.CONTEXT_FILE} file')
|
|
60
|
+
with_file_format_options(add_subparser(get_file_subparsers, c.DBVIEW_FILE_STEM, f'Get a sample dbview model file'))
|
|
61
|
+
with_file_format_options(add_subparser(get_file_subparsers, c.FEDERATE_FILE_STEM, f'Get a sample federate model file'))
|
|
62
|
+
add_subparser(get_file_subparsers, c.DASHBOARD_FILE_STEM, f'Get a sample dashboard file')
|
|
63
|
+
add_subparser(get_file_subparsers, c.EXPENSES_DB, f'Get the sample SQLite database on expenses')
|
|
64
|
+
add_subparser(get_file_subparsers, c.WEATHER_DB, f'Get the sample SQLite database on weather')
|
|
65
|
+
|
|
66
|
+
add_subparser(subparsers, c.DEPS_CMD, f'Load all packages specified in {c.MANIFEST_FILE} (from git)')
|
|
33
67
|
|
|
34
|
-
compile_parser = subparsers
|
|
68
|
+
compile_parser = add_subparser(subparsers, c.COMPILE_CMD, 'Create rendered SQL files in the folder "./target/compile"')
|
|
35
69
|
compile_dataset_group = compile_parser.add_mutually_exclusive_group(required=True)
|
|
36
|
-
compile_test_set_group = compile_parser.add_mutually_exclusive_group(required=False)
|
|
37
|
-
compile_parser.add_argument('-h', '--help', action="help", help="Show this help message and exit")
|
|
38
|
-
|
|
39
70
|
compile_dataset_group.add_argument('-d', '--dataset', type=str, help="Select dataset to use for dataset traits. Is required, unless using --all-datasets")
|
|
40
71
|
compile_dataset_group.add_argument('-D', '--all-datasets', action="store_true", help="Compile models for all datasets. Only required if --dataset is not specified")
|
|
72
|
+
compile_test_set_group = compile_parser.add_mutually_exclusive_group(required=False)
|
|
41
73
|
compile_test_set_group.add_argument('-t', '--test-set', type=str, help="The selection test set to use. If not specified, default selections are used, unless using --all-test-sets")
|
|
42
74
|
compile_test_set_group.add_argument('-T', '--all-test-sets', action="store_true", help="Compile models for all selection test sets")
|
|
43
|
-
|
|
44
75
|
compile_parser.add_argument('-s', '--select', type=str, help="Select single model to compile. If not specified, all models for the dataset are compiled. Ignored if using --all-datasets")
|
|
45
76
|
compile_parser.add_argument('-r', '--runquery', action='store_true', help='Runs all target models, and produce the results as csv files')
|
|
46
77
|
|
|
47
|
-
run_parser = subparsers
|
|
48
|
-
run_parser.add_argument('-h', '--help', action="help", help="Show this help message and exit")
|
|
78
|
+
run_parser = add_subparser(subparsers, c.RUN_CMD, 'Run the API server')
|
|
49
79
|
run_parser.add_argument('--no-cache', action='store_true', help='Do not cache any api results')
|
|
50
80
|
run_parser.add_argument('--host', type=str, default='127.0.0.1', help="The host to run on")
|
|
51
81
|
run_parser.add_argument('--port', type=int, default=4465, help="The port to run on")
|
|
52
82
|
|
|
53
83
|
args, _ = parser.parse_known_args()
|
|
54
|
-
|
|
55
|
-
timer.add_activity_time('parsing arguments', start)
|
|
84
|
+
project = SquirrelsProject(log_level=args.log_level, log_format=args.log_format, log_file=args.log_file)
|
|
56
85
|
|
|
57
|
-
from . import __version__
|
|
58
|
-
from ._api_server import ApiServer
|
|
59
|
-
from ._models import ModelsIO
|
|
60
|
-
from ._initializer import Initializer
|
|
61
|
-
from ._manifest import ManifestIO
|
|
62
|
-
from ._package_loader import PackageLoaderIO
|
|
63
|
-
from ._connection_set import ConnectionSetIO
|
|
64
|
-
from ._parameter_sets import ParameterConfigsSetIO
|
|
65
|
-
from ._seeds import SeedsIO
|
|
66
|
-
|
|
67
86
|
if args.version:
|
|
68
87
|
print(__version__)
|
|
69
88
|
elif args.command == c.INIT_CMD:
|
|
70
|
-
Initializer(args.overwrite).init_project(args)
|
|
89
|
+
Initializer(overwrite=args.overwrite).init_project(args)
|
|
90
|
+
elif args.command == c.GET_FILE_CMD:
|
|
91
|
+
Initializer().get_file(args)
|
|
71
92
|
elif args.command == c.DEPS_CMD:
|
|
72
|
-
|
|
73
|
-
PackageLoaderIO.LoadPackages(reload=True)
|
|
93
|
+
PackageLoaderIO.load_packages(project._logger, project._manifest_cfg, reload=True)
|
|
74
94
|
elif args.command in [c.RUN_CMD, c.COMPILE_CMD]:
|
|
75
|
-
ManifestIO.LoadFromFile()
|
|
76
|
-
SeedsIO.LoadFiles()
|
|
77
|
-
ConnectionSetIO.LoadFromFile()
|
|
78
95
|
try:
|
|
79
|
-
ParameterConfigsSetIO.LoadFromFile()
|
|
80
|
-
ModelsIO.LoadFiles()
|
|
81
96
|
if args.command == c.RUN_CMD:
|
|
82
|
-
server = ApiServer(args.no_cache)
|
|
97
|
+
server = ApiServer(args.no_cache, project)
|
|
83
98
|
server.run(args)
|
|
84
99
|
elif args.command == c.COMPILE_CMD:
|
|
85
|
-
task =
|
|
100
|
+
task = project.compile(
|
|
101
|
+
dataset=args.dataset, do_all_datasets=args.all_datasets, selected_model=args.select, test_set=args.test_set,
|
|
102
|
+
do_all_test_sets=args.all_test_sets, runquery=args.runquery
|
|
103
|
+
)
|
|
86
104
|
asyncio.run(task)
|
|
87
105
|
except KeyboardInterrupt:
|
|
88
106
|
pass
|
|
107
|
+
except Exception as e:
|
|
108
|
+
buffer = io.StringIO()
|
|
109
|
+
traceback.print_exception(e, file=buffer)
|
|
110
|
+
err_msg = buffer.getvalue()
|
|
111
|
+
print(err_msg)
|
|
112
|
+
project._logger.error(err_msg)
|
|
89
113
|
finally:
|
|
90
|
-
|
|
114
|
+
project.close()
|
|
91
115
|
elif args.command is None:
|
|
92
116
|
print(f'Command is missing. Enter "squirrels -h" for help.')
|
|
93
117
|
else:
|
squirrels/_connection_set.py
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from sqlalchemy import Engine, create_engine
|
|
3
|
-
import pandas as pd
|
|
3
|
+
import time, pandas as pd
|
|
4
4
|
|
|
5
5
|
from . import _utils as u, _constants as c, _py_module as pm
|
|
6
6
|
from .arguments.init_time_args import ConnectionsArgs
|
|
7
|
-
from ._environcfg import
|
|
8
|
-
from ._manifest import
|
|
9
|
-
from ._timer import timer, time
|
|
7
|
+
from ._environcfg import EnvironConfig
|
|
8
|
+
from ._manifest import ManifestConfig
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
@dataclass
|
|
@@ -37,7 +36,7 @@ class ConnectionSet:
|
|
|
37
36
|
except Exception as e:
|
|
38
37
|
raise RuntimeError(e) from e
|
|
39
38
|
|
|
40
|
-
def
|
|
39
|
+
def dispose(self) -> None:
|
|
41
40
|
"""
|
|
42
41
|
Disposes of all the engines in this ConnectionSet
|
|
43
42
|
"""
|
|
@@ -47,11 +46,20 @@ class ConnectionSet:
|
|
|
47
46
|
|
|
48
47
|
|
|
49
48
|
class ConnectionSetIO:
|
|
50
|
-
args: ConnectionsArgs
|
|
51
|
-
obj: ConnectionSet
|
|
52
49
|
|
|
53
50
|
@classmethod
|
|
54
|
-
def
|
|
51
|
+
def load_conn_py_args(cls, logger: u.Logger, env_cfg: EnvironConfig, manifest_cfg: ManifestConfig) -> ConnectionsArgs:
|
|
52
|
+
start = time.time()
|
|
53
|
+
|
|
54
|
+
proj_vars = manifest_cfg.project_variables.model_dump()
|
|
55
|
+
env_vars = env_cfg.get_all_env_vars()
|
|
56
|
+
conn_args = ConnectionsArgs(proj_vars, env_vars, env_cfg.get_credential)
|
|
57
|
+
|
|
58
|
+
logger.log_activity_time("setting up arguments for connections.py", start)
|
|
59
|
+
return conn_args
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def load_from_file(cls, logger: u.Logger, base_path: str, manifest_cfg: ManifestConfig, conn_args: ConnectionsArgs) -> ConnectionSet:
|
|
55
63
|
"""
|
|
56
64
|
Takes the DB connection engines from both the squirrels.yml and connections.py files and merges them
|
|
57
65
|
into a single ConnectionSet
|
|
@@ -61,22 +69,12 @@ class ConnectionSetIO:
|
|
|
61
69
|
"""
|
|
62
70
|
start = time.time()
|
|
63
71
|
engines: dict[str, Engine] = {}
|
|
64
|
-
cls.obj = ConnectionSet(engines)
|
|
65
|
-
try:
|
|
66
|
-
for config in ManifestIO.obj.connections.values():
|
|
67
|
-
engines[config.name] = create_engine(config.url)
|
|
68
|
-
|
|
69
|
-
proj_vars = ManifestIO.obj.project_variables
|
|
70
|
-
env_vars = EnvironConfigIO.obj.get_all_env_vars()
|
|
71
|
-
get_credential = EnvironConfigIO.obj.get_credential
|
|
72
|
-
cls.args = ConnectionsArgs(proj_vars, env_vars, get_credential)
|
|
73
|
-
pm.run_pyconfig_main(c.CONNECTIONS_FILE, {"connections": engines, "sqrl": cls.args})
|
|
74
|
-
except Exception as e:
|
|
75
|
-
cls.Dispose()
|
|
76
|
-
raise e
|
|
77
72
|
|
|
78
|
-
|
|
73
|
+
for config in manifest_cfg.connections.values():
|
|
74
|
+
engines[config.name] = create_engine(config.url)
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
pm.run_pyconfig_main(base_path, c.CONNECTIONS_FILE, {"connections": engines, "sqrl": conn_args})
|
|
77
|
+
conn_set = ConnectionSet(engines)
|
|
78
|
+
|
|
79
|
+
logger.log_activity_time("creating sqlalchemy engines", start)
|
|
80
|
+
return conn_set
|
squirrels/_constants.py
CHANGED
|
@@ -1,75 +1,11 @@
|
|
|
1
1
|
# Squirrels CLI commands
|
|
2
2
|
INIT_CMD = 'init'
|
|
3
|
+
GET_FILE_CMD = 'get-file'
|
|
3
4
|
DEPS_CMD = 'deps'
|
|
4
5
|
COMPILE_CMD = 'compile'
|
|
5
6
|
RUN_CMD = 'run'
|
|
6
7
|
|
|
7
|
-
# Manifest file keys
|
|
8
|
-
PROJ_VARS_KEY = 'project_variables'
|
|
9
|
-
PROJECT_NAME_KEY = 'name'
|
|
10
|
-
PROJECT_LABEL_KEY = 'label'
|
|
11
|
-
MAJOR_VERSION_KEY = 'major_version'
|
|
12
|
-
|
|
13
|
-
PACKAGES_KEY = 'packages'
|
|
14
|
-
PACKAGE_GIT_KEY = 'git'
|
|
15
|
-
PACKAGE_DIRECTORY_KEY = 'directory'
|
|
16
|
-
PACKAGE_REVISION_KEY = 'revision'
|
|
17
|
-
|
|
18
|
-
DB_CONNECTIONS_KEY = 'connections'
|
|
19
|
-
DB_CONN_NAME_KEY = 'name'
|
|
20
|
-
DB_CONN_CRED_KEY = 'credential'
|
|
21
|
-
DB_CONN_URL_KEY = 'url'
|
|
22
|
-
|
|
23
|
-
DBVIEWS_KEY = 'dbviews'
|
|
24
|
-
DBVIEW_NAME_KEY = 'name'
|
|
25
|
-
DBVIEW_CONN_KEY = 'connection_name'
|
|
26
|
-
DEFAULT_DB_CONN = 'default'
|
|
27
|
-
|
|
28
|
-
FEDERATES_KEY = 'federates'
|
|
29
|
-
FEDERATE_NAME_KEY = 'name'
|
|
30
|
-
MATERIALIZED_KEY = 'materialized'
|
|
31
|
-
DEFAULT_TABLE_MATERIALIZE = 'table'
|
|
32
|
-
|
|
33
|
-
PARAMETERS_KEY = 'parameters'
|
|
34
|
-
PARAMETER_NAME_KEY = 'name'
|
|
35
|
-
PARAMETER_TYPE_KEY = 'type'
|
|
36
|
-
PARAMETER_FACTORY_KEY = 'factory'
|
|
37
|
-
PARAMETER_ARGS_KEY = 'arguments'
|
|
38
|
-
|
|
39
|
-
TEST_SETS_KEY = 'selection_test_sets'
|
|
40
|
-
TEST_SET_NAME_KEY = 'name'
|
|
41
|
-
DEFAULT_TEST_SET_NAME = 'default'
|
|
42
|
-
TEST_SET_DATASETS_KEY = 'datasets'
|
|
43
|
-
TEST_SET_USER_ATTR_KEY = 'user_attributes'
|
|
44
|
-
TEST_SET_PARAMETERS_KEY = 'parameters'
|
|
45
|
-
|
|
46
|
-
DATASETS_KEY = 'datasets'
|
|
47
|
-
DATASET_NAME_KEY = 'name'
|
|
48
|
-
DATASET_LABEL_KEY = 'label'
|
|
49
|
-
DATASET_MODEL_KEY = 'model'
|
|
50
|
-
DATASET_PARAMETERS_KEY = 'parameters'
|
|
51
|
-
DATASET_TRAITS_KEY = 'traits'
|
|
52
|
-
DATASET_DEFAULT_TEST_SET_KEY = 'default_test_set'
|
|
53
|
-
|
|
54
|
-
DATASET_SCOPE_KEY = 'scope'
|
|
55
|
-
PUBLIC_SCOPE = 'public'
|
|
56
|
-
PROTECTED_SCOPE = 'protected'
|
|
57
|
-
PRIVATE_SCOPE = 'private'
|
|
58
|
-
|
|
59
|
-
SETTINGS_KEY = 'settings'
|
|
60
|
-
|
|
61
8
|
# Environment config keys
|
|
62
|
-
USERS_KEY = 'users'
|
|
63
|
-
USER_NAME_KEY = 'username'
|
|
64
|
-
USER_PWD_KEY = 'password'
|
|
65
|
-
|
|
66
|
-
ENV_VARS_KEY = 'env_vars'
|
|
67
|
-
|
|
68
|
-
CREDENTIALS_KEY = 'credentials'
|
|
69
|
-
USERNAME_KEY = 'username'
|
|
70
|
-
PASSWORD_KEY = 'password'
|
|
71
|
-
|
|
72
|
-
SECRETS_KEY = 'secrets'
|
|
73
9
|
JWT_SECRET_KEY = 'jwt_secret'
|
|
74
10
|
|
|
75
11
|
# Folder/File names
|
|
@@ -78,24 +14,29 @@ BASE_PROJECT_FOLDER = 'base_project'
|
|
|
78
14
|
ASSETS_FOLDER = 'assets'
|
|
79
15
|
TEMPLATES_FOLDER = 'templates'
|
|
80
16
|
|
|
81
|
-
ENVIRON_CONFIG_FILE = 'environcfg.yml'
|
|
82
17
|
ENV_CONFIG_FILE = 'env.yml'
|
|
83
18
|
MANIFEST_JINJA_FILE = 'squirrels.yml.j2'
|
|
84
19
|
CONNECTIONS_YML_FILE = 'connections.yml'
|
|
85
20
|
PARAMETERS_YML_FILE = 'parameters.yml'
|
|
21
|
+
DASHBOARDS_YML_FILE = 'dashboards.yml'
|
|
86
22
|
MANIFEST_FILE = 'squirrels.yml'
|
|
87
|
-
|
|
23
|
+
|
|
24
|
+
LOGS_FOLDER = 'logs'
|
|
25
|
+
LOGS_FILE = 'squirrels.log'
|
|
88
26
|
|
|
89
27
|
DATABASE_FOLDER = 'assets'
|
|
90
28
|
PACKAGES_FOLDER = 'sqrl_packages'
|
|
91
29
|
|
|
30
|
+
MACROS_FOLDER = 'macros'
|
|
31
|
+
|
|
92
32
|
MODELS_FOLDER = 'models'
|
|
93
33
|
DBVIEWS_FOLDER = 'dbviews'
|
|
94
|
-
|
|
95
|
-
DATABASE_VIEW_PY_FILE = 'database_view1.py'
|
|
34
|
+
DBVIEW_FILE_STEM = 'dbview_example'
|
|
96
35
|
FEDERATES_FOLDER = 'federates'
|
|
97
|
-
|
|
98
|
-
|
|
36
|
+
FEDERATE_FILE_STEM = 'federate_example'
|
|
37
|
+
|
|
38
|
+
DASHBOARDS_FOLDER = 'dashboards'
|
|
39
|
+
DASHBOARD_FILE_STEM = 'dashboard_example'
|
|
99
40
|
|
|
100
41
|
PYCONFIGS_FOLDER = 'pyconfigs'
|
|
101
42
|
AUTH_FILE = 'auth.py'
|
|
@@ -114,11 +55,18 @@ SUBCATEGORY_SEED_FILE = 'seed_subcategories.csv'
|
|
|
114
55
|
AUTH_TOKEN_EXPIRE_SETTING = 'auth.token.expire_minutes'
|
|
115
56
|
PARAMETERS_CACHE_SIZE_SETTING = 'parameters.cache.size'
|
|
116
57
|
PARAMETERS_CACHE_TTL_SETTING = 'parameters.cache.ttl_minutes'
|
|
117
|
-
RESULTS_CACHE_SIZE_SETTING = 'results.cache.size'
|
|
118
|
-
RESULTS_CACHE_TTL_SETTING = 'results.cache.ttl_minutes'
|
|
58
|
+
RESULTS_CACHE_SIZE_SETTING = 'results.cache.size' # deprecated
|
|
59
|
+
RESULTS_CACHE_TTL_SETTING = 'results.cache.ttl_minutes' # deprecated
|
|
60
|
+
DATASETS_CACHE_SIZE_SETTING = 'datasets.cache.size'
|
|
61
|
+
DATASETS_CACHE_TTL_SETTING = 'datasets.cache.ttl_minutes'
|
|
62
|
+
DASHBOARDS_CACHE_SIZE_SETTING = 'dashboards.cache.size'
|
|
63
|
+
DASHBOARDS_CACHE_TTL_SETTING = 'dashboards.cache.ttl_minutes'
|
|
119
64
|
TEST_SET_DEFAULT_USED_SETTING = 'selection_test_sets.default_name_used'
|
|
65
|
+
DEFAULT_TEST_SET_NAME = 'default'
|
|
120
66
|
DB_CONN_DEFAULT_USED_SETTING = 'connections.default_name_used'
|
|
67
|
+
DEFAULT_DB_CONN = 'default'
|
|
121
68
|
DEFAULT_MATERIALIZE_SETTING = 'defaults.federates.materialized'
|
|
69
|
+
DEFAULT_MATERIALIZE = 'table'
|
|
122
70
|
SEEDS_INFER_SCHEMA_SETTING = 'seeds.infer_schema'
|
|
123
71
|
SEEDS_NA_VALUES_SETTING = 'seeds.na_values'
|
|
124
72
|
IN_MEMORY_DB_SETTING = 'in_memory_database'
|
|
@@ -141,10 +89,12 @@ CONF_FORMAT_CHOICES = [YML_FORMAT, PYTHON_FORMAT]
|
|
|
141
89
|
PYTHON_FORMAT2 = 'py (recommended)'
|
|
142
90
|
CONF_FORMAT_CHOICES2 = [(PYTHON_FORMAT2, PYTHON_FORMAT), YML_FORMAT]
|
|
143
91
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
92
|
+
EXPENSES_DB = 'expenses.db'
|
|
93
|
+
WEATHER_DB = 'weather.db'
|
|
94
|
+
|
|
95
|
+
# Dashboard formats
|
|
96
|
+
PNG = "png"
|
|
97
|
+
HTML = "html"
|
|
148
98
|
|
|
149
99
|
# Function names
|
|
150
100
|
GET_USER_FUNC = "get_user_if_valid"
|
|
@@ -152,4 +102,5 @@ DEP_FUNC = "dependencies"
|
|
|
152
102
|
MAIN_FUNC = "main"
|
|
153
103
|
|
|
154
104
|
# Regex
|
|
155
|
-
date_regex = r
|
|
105
|
+
date_regex = r"^\d{4}\-\d{2}\-\d{2}$"
|
|
106
|
+
color_regex = r"^#[0-9a-fA-F]{6}$"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from typing import Type, TypeVar, Callable, Coroutine, Any
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
import inspect, os, time
|
|
4
|
+
|
|
5
|
+
from .arguments.run_time_args import DashboardArgs
|
|
6
|
+
from ._py_module import PyModule
|
|
7
|
+
from . import _constants as c, _utils as u, dashboards as d
|
|
8
|
+
|
|
9
|
+
T = TypeVar('T', bound=d.Dashboard)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class DashboardFunction:
|
|
14
|
+
dashboard_name: str
|
|
15
|
+
filepath: str
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def dashboard_func(self) -> Callable[[DashboardArgs], Coroutine[Any, Any, d.Dashboard]]:
|
|
19
|
+
if not hasattr(self, '_dashboard_func'):
|
|
20
|
+
module = PyModule(self.filepath)
|
|
21
|
+
self._dashboard_func = module.get_func_or_class(c.MAIN_FUNC)
|
|
22
|
+
return self._dashboard_func
|
|
23
|
+
|
|
24
|
+
def get_dashboard_format(self) -> str:
|
|
25
|
+
return_type = inspect.signature(self.dashboard_func).return_annotation
|
|
26
|
+
assert issubclass(return_type, d.Dashboard), f"Function must return Dashboard type"
|
|
27
|
+
if return_type == d.PngDashboard:
|
|
28
|
+
return c.PNG
|
|
29
|
+
elif return_type == d.HtmlDashboard:
|
|
30
|
+
return c.HTML
|
|
31
|
+
else:
|
|
32
|
+
raise NotImplementedError(f"Dashboard format {return_type} not supported")
|
|
33
|
+
|
|
34
|
+
async def get_dashboard(self, args: DashboardArgs, *, dashboard_type: Type[T] = d.Dashboard) -> T:
|
|
35
|
+
try:
|
|
36
|
+
dashboard = await self.dashboard_func(args)
|
|
37
|
+
assert isinstance(dashboard, dashboard_type), f"Function does not return expected Dashboard type: {dashboard_type}"
|
|
38
|
+
except (u.InvalidInputError, u.ConfigurationError, u.FileExecutionError) as e:
|
|
39
|
+
raise e
|
|
40
|
+
except Exception as e:
|
|
41
|
+
raise u.FileExecutionError(f'Failed to run "{c.MAIN_FUNC}" function for dashboard "{self.dashboard_name}"', e) from e
|
|
42
|
+
|
|
43
|
+
return dashboard
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DashboardsIO:
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def load_files(cls, logger: u.Logger, base_path: str) -> dict[str, DashboardFunction]:
|
|
50
|
+
start = time.time()
|
|
51
|
+
|
|
52
|
+
dashboards_by_name = {}
|
|
53
|
+
for dp, _, filenames in os.walk(u.Path(base_path, c.DASHBOARDS_FOLDER)):
|
|
54
|
+
for file in filenames:
|
|
55
|
+
filepath = os.path.join(dp, file)
|
|
56
|
+
file_stem, extension = os.path.splitext(file)
|
|
57
|
+
if extension == '.py':
|
|
58
|
+
dashboards_by_name[file_stem] = DashboardFunction(file_stem, filepath)
|
|
59
|
+
|
|
60
|
+
logger.log_activity_time("loading files for dashboards", start)
|
|
61
|
+
return dashboards_by_name
|