squirrels 0.5.0rc0__py3-none-any.whl → 0.5.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.
- 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 +10 -12
- squirrels/_api_routes/__init__.py +5 -0
- squirrels/_api_routes/auth.py +271 -0
- squirrels/_api_routes/base.py +171 -0
- squirrels/_api_routes/dashboards.py +158 -0
- squirrels/_api_routes/data_management.py +148 -0
- squirrels/_api_routes/datasets.py +265 -0
- squirrels/_api_routes/oauth2.py +298 -0
- squirrels/_api_routes/project.py +252 -0
- squirrels/_api_server.py +245 -781
- squirrels/_arguments/__init__.py +0 -0
- squirrels/{arguments → _arguments}/init_time_args.py +7 -2
- squirrels/{arguments → _arguments}/run_time_args.py +13 -35
- squirrels/_auth.py +720 -212
- squirrels/_command_line.py +81 -41
- squirrels/_compile_prompts.py +147 -0
- squirrels/_connection_set.py +16 -7
- squirrels/_constants.py +29 -9
- squirrels/{_dashboards_io.py → _dashboards.py} +87 -6
- squirrels/_data_sources.py +570 -0
- squirrels/{dataset_result.py → _dataset_types.py} +2 -4
- squirrels/_exceptions.py +9 -37
- squirrels/_initializer.py +83 -59
- squirrels/_logging.py +117 -0
- squirrels/_manifest.py +129 -62
- squirrels/_model_builder.py +10 -52
- squirrels/_model_configs.py +3 -3
- squirrels/_model_queries.py +1 -1
- squirrels/_models.py +249 -118
- squirrels/{package_data → _package_data}/base_project/.env +16 -4
- squirrels/{package_data → _package_data}/base_project/.env.example +15 -3
- squirrels/{package_data → _package_data}/base_project/connections.yml +4 -3
- squirrels/{package_data → _package_data}/base_project/dashboards/dashboard_example.py +4 -4
- squirrels/_package_data/base_project/dashboards/dashboard_example.yml +22 -0
- squirrels/{package_data → _package_data}/base_project/duckdb_init.sql +1 -0
- squirrels/_package_data/base_project/macros/macros_example.sql +17 -0
- squirrels/{package_data → _package_data}/base_project/models/builds/build_example.py +2 -2
- squirrels/{package_data → _package_data}/base_project/models/builds/build_example.sql +1 -1
- squirrels/{package_data → _package_data}/base_project/models/builds/build_example.yml +2 -0
- squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +17 -0
- squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +32 -0
- squirrels/_package_data/base_project/models/federates/federate_example.py +48 -0
- squirrels/_package_data/base_project/models/federates/federate_example.sql +21 -0
- squirrels/{package_data → _package_data}/base_project/models/federates/federate_example.yml +7 -7
- squirrels/{package_data → _package_data}/base_project/models/sources.yml +5 -6
- squirrels/{package_data → _package_data}/base_project/parameters.yml +32 -45
- squirrels/_package_data/base_project/pyconfigs/connections.py +18 -0
- squirrels/{package_data → _package_data}/base_project/pyconfigs/context.py +31 -22
- squirrels/_package_data/base_project/pyconfigs/parameters.py +141 -0
- squirrels/_package_data/base_project/pyconfigs/user.py +44 -0
- squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.yml +1 -1
- squirrels/{package_data → _package_data}/base_project/seeds/seed_subcategories.yml +1 -1
- 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/_parameter_configs.py +76 -55
- squirrels/_parameter_options.py +348 -0
- squirrels/_parameter_sets.py +53 -45
- squirrels/_parameters.py +1664 -0
- squirrels/_project.py +403 -242
- squirrels/_py_module.py +3 -2
- squirrels/_request_context.py +33 -0
- 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} +48 -18
- squirrels/_seeds.py +1 -1
- squirrels/_sources.py +23 -19
- squirrels/_utils.py +121 -39
- 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 -563
- squirrels/parameter_options.py +13 -348
- squirrels/parameters.py +14 -1266
- squirrels/types.py +16 -0
- {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/METADATA +42 -30
- squirrels-0.5.1.dist-info/RECORD +98 -0
- squirrels/package_data/base_project/dashboards/dashboard_example.yml +0 -22
- squirrels/package_data/base_project/macros/macros_example.sql +0 -15
- squirrels/package_data/base_project/models/dbviews/dbview_example.sql +0 -12
- squirrels/package_data/base_project/models/dbviews/dbview_example.yml +0 -26
- squirrels/package_data/base_project/models/federates/federate_example.py +0 -44
- squirrels/package_data/base_project/models/federates/federate_example.sql +0 -17
- squirrels/package_data/base_project/pyconfigs/connections.py +0 -14
- squirrels/package_data/base_project/pyconfigs/parameters.py +0 -93
- squirrels/package_data/base_project/pyconfigs/user.py +0 -23
- squirrels/package_data/base_project/squirrels.yml.j2 +0 -71
- squirrels-0.5.0rc0.dist-info/RECORD +0 -70
- /squirrels/{package_data → _package_data}/base_project/assets/expenses.db +0 -0
- /squirrels/{package_data → _package_data}/base_project/assets/weather.db +0 -0
- /squirrels/{package_data → _package_data}/base_project/docker/.dockerignore +0 -0
- /squirrels/{package_data → _package_data}/base_project/docker/Dockerfile +0 -0
- /squirrels/{package_data → _package_data}/base_project/docker/compose.yml +0 -0
- /squirrels/{package_data/base_project/.gitignore → _package_data/base_project/gitignore} +0 -0
- /squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.csv +0 -0
- /squirrels/{package_data → _package_data}/base_project/seeds/seed_subcategories.csv +0 -0
- /squirrels/{package_data → _package_data}/base_project/tmp/.gitignore +0 -0
- {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/WHEEL +0 -0
- {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/entry_points.txt +0 -0
- {squirrels-0.5.0rc0.dist-info → squirrels-0.5.1.dist-info}/licenses/LICENSE +0 -0
squirrels/_command_line.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from argparse import ArgumentParser, _SubParsersAction
|
|
2
|
-
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sys, asyncio, traceback, io, subprocess
|
|
3
4
|
|
|
4
5
|
sys.path.append('.')
|
|
5
6
|
|
|
@@ -9,16 +10,31 @@ from ._initializer import Initializer
|
|
|
9
10
|
from ._package_loader import PackageLoaderIO
|
|
10
11
|
from ._project import SquirrelsProject
|
|
11
12
|
from . import _constants as c, _utils as u
|
|
13
|
+
from ._compile_prompts import prompt_compile_options
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
def _run_duckdb_cli(project: SquirrelsProject):
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
def _run_duckdb_cli(project: SquirrelsProject, ui: bool):
|
|
17
|
+
init_sql = u._read_duckdb_init_sql(datalake_db_path=project._datalake_db_path)
|
|
18
|
+
|
|
19
|
+
target_init_path = None
|
|
20
|
+
if init_sql:
|
|
21
|
+
target_init_path = Path(c.TARGET_FOLDER, c.DUCKDB_INIT_FILE)
|
|
22
|
+
target_init_path.parent.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
target_init_path.write_text(init_sql)
|
|
24
|
+
|
|
25
|
+
init_args = ["-init", str(target_init_path)] if target_init_path else []
|
|
17
26
|
command = ['duckdb']
|
|
27
|
+
if ui:
|
|
28
|
+
command.append('-ui')
|
|
18
29
|
if init_args:
|
|
19
|
-
command.extend(init_args
|
|
20
|
-
|
|
21
|
-
print(
|
|
30
|
+
command.extend(init_args)
|
|
31
|
+
|
|
32
|
+
print("Starting DuckDB CLI with command:")
|
|
33
|
+
print(f"$ {' '.join(command)}")
|
|
34
|
+
print()
|
|
35
|
+
print("To exit the DuckDB CLI, enter '.exit'")
|
|
36
|
+
print()
|
|
37
|
+
|
|
22
38
|
try:
|
|
23
39
|
subprocess.run(command, check=True)
|
|
24
40
|
except FileNotFoundError:
|
|
@@ -31,38 +47,39 @@ def main():
|
|
|
31
47
|
"""
|
|
32
48
|
Main entry point for the squirrels command line utilities.
|
|
33
49
|
"""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
# Create a parent parser with common logging options
|
|
51
|
+
parent_parser = ArgumentParser(add_help=False)
|
|
52
|
+
parent_parser.add_argument('-h', '--help', action="help", help="Show this help message and exit")
|
|
53
|
+
parent_parser.add_argument('--log-level', type=str, choices=["DEBUG", "INFO", "WARNING"], help='Level of logging to use. Default is from SQRL_LOGGING__LOG_LEVEL environment variable or INFO.')
|
|
54
|
+
parent_parser.add_argument('--log-format', type=str, choices=["text", "json"], help='Format of the log records. Default is from SQRL_LOGGING__LOG_FORMAT environment variable or text.')
|
|
55
|
+
parent_parser.add_argument('--log-to-file', action='store_true', help='Enable logging to file(s) in the "logs/" folder with rotation and retention policies.')
|
|
56
|
+
|
|
57
|
+
parser = ArgumentParser(description="Command line utilities from the squirrels python package", add_help=False, parents=[parent_parser])
|
|
39
58
|
parser.add_argument('-V', '--version', action='store_true', help='Show the version and exit')
|
|
40
|
-
parser.add_argument('--log-level', type=str, choices=["DEBUG", "INFO", "WARNING"], default="INFO", help='Level of logging to use')
|
|
41
|
-
parser.add_argument('--log-format', type=str, choices=["text", "json"], default="text", help='Format of the log records')
|
|
42
|
-
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')
|
|
43
59
|
subparsers = parser.add_subparsers(title='commands', dest='command')
|
|
44
60
|
|
|
45
61
|
def add_subparser(subparsers: _SubParsersAction, cmd: str, help_text: str):
|
|
46
|
-
subparser =
|
|
62
|
+
subparser: ArgumentParser = subparsers.add_parser(cmd, description=help_text, help=help_text, add_help=False, parents=[parent_parser])
|
|
47
63
|
return subparser
|
|
48
64
|
|
|
49
65
|
init_parser = add_subparser(subparsers, c.INIT_CMD, 'Create a new squirrels project')
|
|
50
66
|
|
|
51
|
-
init_parser.add_argument('name', nargs='?', type=str, help='The name of the project')
|
|
52
|
-
init_parser.add_argument('-
|
|
53
|
-
init_parser.add_argument('--
|
|
67
|
+
init_parser.add_argument('name', nargs='?', type=str, help='The name of the project folder to create. Ignored if --curr-dir is used')
|
|
68
|
+
init_parser.add_argument('--curr-dir', action='store_true', help='Create the project in the current directory')
|
|
69
|
+
init_parser.add_argument('--use-defaults', action='store_true', help='Use default values for unspecified options (except project folder name) instead of prompting for input')
|
|
54
70
|
init_parser.add_argument('--connections', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure database connections as yaml (default) or python')
|
|
55
71
|
init_parser.add_argument('--parameters', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure parameters as python (default) or yaml')
|
|
56
72
|
init_parser.add_argument('--build', type=str, choices=c.FILE_TYPE_CHOICES, help='Create build model as sql (default) or python file')
|
|
57
73
|
init_parser.add_argument('--federate', type=str, choices=c.FILE_TYPE_CHOICES, help='Create federated model as sql (default) or python file')
|
|
58
|
-
init_parser.add_argument('--dashboard',
|
|
74
|
+
init_parser.add_argument('--dashboard', type=str, choices=['y', 'n'], help=f'Include (y) or exclude (n, default) a sample dashboard file')
|
|
75
|
+
init_parser.add_argument('--admin-password', type=str, help='The password for the admin user. If --use-defaults is used, then a random password is generated')
|
|
59
76
|
|
|
60
77
|
def with_file_format_options(parser: ArgumentParser):
|
|
61
78
|
help_text = "Create model as sql (default) or python file"
|
|
62
79
|
parser.add_argument('--format', type=str, choices=c.FILE_TYPE_CHOICES, default=c.SQL_FILE_TYPE, help=help_text)
|
|
63
80
|
return parser
|
|
64
81
|
|
|
65
|
-
get_file_help_text = "Get a sample file for the squirrels project. If the file name already exists, it will be
|
|
82
|
+
get_file_help_text = "Get a sample file for the squirrels project. If the file name already exists, it will be suffixed with a timestamp."
|
|
66
83
|
get_file_parser = add_subparser(subparsers, c.GET_FILE_CMD, get_file_help_text)
|
|
67
84
|
get_file_subparsers = get_file_parser.add_subparsers(title='file_name', dest='file_name')
|
|
68
85
|
add_subparser(get_file_subparsers, c.DOTENV_FILE, f'Get sample {c.DOTENV_FILE} and {c.DOTENV_FILE}.example files')
|
|
@@ -89,24 +106,26 @@ def main():
|
|
|
89
106
|
deps_parser = add_subparser(subparsers, c.DEPS_CMD, f'Load all packages specified in {c.MANIFEST_FILE} (from git)')
|
|
90
107
|
|
|
91
108
|
compile_parser = add_subparser(subparsers, c.COMPILE_CMD, 'Create rendered SQL files in the folder "./target/compile"')
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
109
|
+
compile_parser.add_argument('-y', '--yes', action='store_true', help='Disable prompts and assume the default for all settings not overridden by CLI options')
|
|
110
|
+
compile_parser.add_argument('-c', '--clear', action='store_true', help='Clear the "target/compile/" folder before compiling')
|
|
111
|
+
compile_scope_group = compile_parser.add_mutually_exclusive_group(required=False)
|
|
112
|
+
compile_scope_group.add_argument('--buildtime-only', action='store_true', help='Compile only buildtime models')
|
|
113
|
+
compile_scope_group.add_argument('--runtime-only', action='store_true', help='Compile only runtime models')
|
|
95
114
|
compile_test_set_group = compile_parser.add_mutually_exclusive_group(required=False)
|
|
96
|
-
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
|
|
97
|
-
compile_test_set_group.add_argument('-T', '--all-test-sets', action="store_true", help="Compile models for all selection test sets")
|
|
98
|
-
compile_parser.add_argument('-s', '--select', type=str, help="Select single model to compile. If not specified, all models
|
|
99
|
-
compile_parser.add_argument('-r', '--runquery', action='store_true', help='
|
|
100
|
-
|
|
101
|
-
build_parser = add_subparser(subparsers, c.BUILD_CMD, 'Build the
|
|
115
|
+
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). Not applicable for buildtime models")
|
|
116
|
+
compile_test_set_group.add_argument('-T', '--all-test-sets', action="store_true", help="Compile models for all selection test sets. Not applicable for buildtime models")
|
|
117
|
+
compile_parser.add_argument('-s', '--select', type=str, help="Select single model to compile. If not specified, all models are compiled")
|
|
118
|
+
compile_parser.add_argument('-r', '--runquery', action='store_true', help='Run runtime models and write CSV outputs too. Does not apply to buildtime models')
|
|
119
|
+
|
|
120
|
+
build_parser = add_subparser(subparsers, c.BUILD_CMD, 'Build the Virtual Data Lake (VDL) for the project')
|
|
102
121
|
build_parser.add_argument('-f', '--full-refresh', action='store_true', help='Drop all tables before building')
|
|
103
122
|
build_parser.add_argument('-s', '--select', type=str, help="Select one static model to build. If not specified, all models are built")
|
|
104
|
-
build_parser.add_argument('--stage', type=str, help='If the venv file is in use, stage the duckdb file to replace the venv later')
|
|
105
123
|
|
|
106
124
|
duckdb_parser = add_subparser(subparsers, c.DUCKDB_CMD, 'Run the duckdb command line tool')
|
|
125
|
+
duckdb_parser.add_argument('--ui', action='store_true', help='Run the duckdb local UI')
|
|
107
126
|
|
|
108
127
|
run_parser = add_subparser(subparsers, c.RUN_CMD, 'Run the API server')
|
|
109
|
-
run_parser.add_argument('--build', action='store_true', help='Build the
|
|
128
|
+
run_parser.add_argument('--build', action='store_true', help='Build the VDL first (without full refresh) before running the API server')
|
|
110
129
|
run_parser.add_argument('--no-cache', action='store_true', help='Do not cache any api results')
|
|
111
130
|
run_parser.add_argument('--host', type=str, default='127.0.0.1', help="The host to run on")
|
|
112
131
|
run_parser.add_argument('--port', type=int, default=4465, help="The port to run on")
|
|
@@ -116,22 +135,22 @@ def main():
|
|
|
116
135
|
if args.version:
|
|
117
136
|
print(__version__)
|
|
118
137
|
elif args.command == c.INIT_CMD:
|
|
119
|
-
Initializer(project_name=args.name,
|
|
138
|
+
Initializer(project_name=args.name, use_curr_dir=args.curr_dir).init_project(args)
|
|
120
139
|
elif args.command == c.GET_FILE_CMD:
|
|
121
140
|
Initializer().get_file(args)
|
|
122
141
|
elif args.command is None:
|
|
123
142
|
print(f'Command is missing. Enter "squirrels -h" for help.')
|
|
124
143
|
else:
|
|
125
|
-
project = SquirrelsProject(log_level=args.log_level, log_format=args.log_format,
|
|
144
|
+
project = SquirrelsProject(load_dotenv_globally=True, log_level=args.log_level, log_format=args.log_format, log_to_file=args.log_to_file)
|
|
126
145
|
try:
|
|
127
146
|
if args.command == c.DEPS_CMD:
|
|
128
147
|
PackageLoaderIO.load_packages(project._logger, project._manifest_cfg, reload=True)
|
|
129
148
|
elif args.command == c.BUILD_CMD:
|
|
130
|
-
task = project.build(full_refresh=args.full_refresh, select=args.select
|
|
149
|
+
task = project.build(full_refresh=args.full_refresh, select=args.select)
|
|
131
150
|
asyncio.run(task)
|
|
132
151
|
print()
|
|
133
152
|
elif args.command == c.DUCKDB_CMD:
|
|
134
|
-
_run_duckdb_cli(project)
|
|
153
|
+
_run_duckdb_cli(project, args.ui)
|
|
135
154
|
elif args.command == c.RUN_CMD:
|
|
136
155
|
if args.build:
|
|
137
156
|
task = project.build(full_refresh=True)
|
|
@@ -139,11 +158,32 @@ def main():
|
|
|
139
158
|
server = ApiServer(args.no_cache, project)
|
|
140
159
|
server.run(args)
|
|
141
160
|
elif args.command == c.COMPILE_CMD:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
161
|
+
# Derive final options with optional interactive prompts (unless --yes is provided)
|
|
162
|
+
buildtime_only = args.buildtime_only
|
|
163
|
+
runtime_only = args.runtime_only
|
|
164
|
+
test_set = args.test_set
|
|
165
|
+
do_all_test_sets = args.all_test_sets
|
|
166
|
+
selected_model = args.select
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
if not args.yes:
|
|
170
|
+
buildtime_only, runtime_only, test_set, do_all_test_sets, selected_model = prompt_compile_options(
|
|
171
|
+
project,
|
|
172
|
+
buildtime_only=buildtime_only,
|
|
173
|
+
runtime_only=runtime_only,
|
|
174
|
+
test_set=test_set,
|
|
175
|
+
do_all_test_sets=do_all_test_sets,
|
|
176
|
+
selected_model=selected_model,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
task = project.compile(
|
|
180
|
+
selected_model=selected_model, test_set=test_set, do_all_test_sets=do_all_test_sets, runquery=args.runquery,
|
|
181
|
+
clear=args.clear, buildtime_only=buildtime_only, runtime_only=runtime_only
|
|
182
|
+
)
|
|
183
|
+
asyncio.run(task)
|
|
184
|
+
|
|
185
|
+
except KeyError:
|
|
186
|
+
pass
|
|
147
187
|
else:
|
|
148
188
|
print(f'Error: No such command "{args.command}". Enter "squirrels -h" for help.')
|
|
149
189
|
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from typing import Tuple, Optional
|
|
2
|
+
import inquirer
|
|
3
|
+
|
|
4
|
+
from ._project import SquirrelsProject
|
|
5
|
+
from . import _constants as c, _models as m
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def prompt_compile_options(
|
|
9
|
+
project: SquirrelsProject,
|
|
10
|
+
*,
|
|
11
|
+
buildtime_only: bool,
|
|
12
|
+
runtime_only: bool,
|
|
13
|
+
test_set: Optional[str],
|
|
14
|
+
do_all_test_sets: bool,
|
|
15
|
+
selected_model: Optional[str],
|
|
16
|
+
) -> Tuple[bool, bool, Optional[str], bool, Optional[str]]:
|
|
17
|
+
"""
|
|
18
|
+
Interactive prompts to derive compile options when --yes is not provided.
|
|
19
|
+
|
|
20
|
+
Returns updated values for:
|
|
21
|
+
(buildtime_only, runtime_only, test_set, do_all_test_sets, selected_model)
|
|
22
|
+
"""
|
|
23
|
+
# 1) Scope prompt (skip if explicit flags provided)
|
|
24
|
+
if not buildtime_only and not runtime_only:
|
|
25
|
+
scope_answer = inquirer.prompt([
|
|
26
|
+
inquirer.List(
|
|
27
|
+
'scope',
|
|
28
|
+
message='Select scope',
|
|
29
|
+
choices=[
|
|
30
|
+
('All models', 'all'),
|
|
31
|
+
('Buildtime models only', 'buildtime'),
|
|
32
|
+
('Runtime models only', 'runtime'),
|
|
33
|
+
],
|
|
34
|
+
default='all',
|
|
35
|
+
)
|
|
36
|
+
]) or {}
|
|
37
|
+
scope_val = scope_answer['scope']
|
|
38
|
+
if scope_val == 'buildtime':
|
|
39
|
+
buildtime_only, runtime_only = True, False
|
|
40
|
+
elif scope_val == 'runtime':
|
|
41
|
+
buildtime_only, runtime_only = False, True
|
|
42
|
+
else:
|
|
43
|
+
buildtime_only, runtime_only = False, False
|
|
44
|
+
|
|
45
|
+
# 2) Runtime test set prompt (only if runtime is included and -t/-T not provided)
|
|
46
|
+
runtime_included, buildtime_included = not buildtime_only, not runtime_only
|
|
47
|
+
if runtime_included and not test_set and not do_all_test_sets:
|
|
48
|
+
test_mode_answer = inquirer.prompt([
|
|
49
|
+
inquirer.List(
|
|
50
|
+
'ts_mode',
|
|
51
|
+
message='Runtime selections for parameters, configurables, and user attributes?',
|
|
52
|
+
choices=[
|
|
53
|
+
('Use default selections', 'default'),
|
|
54
|
+
('Use a custom test set', 'custom'),
|
|
55
|
+
('Use all test sets (including default selections)', 'all'),
|
|
56
|
+
],
|
|
57
|
+
default='default',
|
|
58
|
+
)
|
|
59
|
+
]) or {}
|
|
60
|
+
ts_mode = test_mode_answer['ts_mode']
|
|
61
|
+
if ts_mode == 'all':
|
|
62
|
+
do_all_test_sets = True
|
|
63
|
+
elif ts_mode == 'custom':
|
|
64
|
+
# Build available test set list
|
|
65
|
+
ts_names = list(project._manifest_cfg.selection_test_sets.keys())
|
|
66
|
+
ts_answer = inquirer.prompt([
|
|
67
|
+
inquirer.List(
|
|
68
|
+
'test_set',
|
|
69
|
+
message='Pick a selection test set',
|
|
70
|
+
choices=ts_names if ts_names else [c.DEFAULT_TEST_SET_NAME],
|
|
71
|
+
default=c.DEFAULT_TEST_SET_NAME,
|
|
72
|
+
)
|
|
73
|
+
]) or {}
|
|
74
|
+
test_set = ts_answer['test_set']
|
|
75
|
+
|
|
76
|
+
# 3) Model selection prompt (only if -s not provided)
|
|
77
|
+
if not selected_model:
|
|
78
|
+
# Ask whether to compile all or a specific model
|
|
79
|
+
model_mode_answer = inquirer.prompt([
|
|
80
|
+
inquirer.List(
|
|
81
|
+
'model_mode',
|
|
82
|
+
message='Compile all models in scope or just a specific model?',
|
|
83
|
+
choices=[
|
|
84
|
+
('All models', 'all'),
|
|
85
|
+
('Select a specific model', 'one'),
|
|
86
|
+
],
|
|
87
|
+
default='all',
|
|
88
|
+
)
|
|
89
|
+
]) or {}
|
|
90
|
+
model_mode = model_mode_answer['model_mode']
|
|
91
|
+
|
|
92
|
+
if model_mode == 'one':
|
|
93
|
+
# Build list of runtime query models (dbviews + federates) and build models if included
|
|
94
|
+
models_dict = project._get_models_dict(always_python_df=False)
|
|
95
|
+
|
|
96
|
+
valid_model_types: list[m.ModelType] = []
|
|
97
|
+
if runtime_included:
|
|
98
|
+
valid_model_types.extend([m.ModelType.DBVIEW, m.ModelType.FEDERATE])
|
|
99
|
+
if buildtime_included:
|
|
100
|
+
valid_model_types.append(m.ModelType.BUILD)
|
|
101
|
+
|
|
102
|
+
runtime_names = sorted([
|
|
103
|
+
(f"({model.model_type.value}) {name}", name) for name, model in models_dict.items()
|
|
104
|
+
if isinstance(model, m.QueryModel) and model.model_type in valid_model_types
|
|
105
|
+
])
|
|
106
|
+
if runtime_names:
|
|
107
|
+
model_answer = inquirer.prompt([
|
|
108
|
+
inquirer.List(
|
|
109
|
+
'selected_model',
|
|
110
|
+
message='Pick a runtime model to compile',
|
|
111
|
+
choices=runtime_names,
|
|
112
|
+
)
|
|
113
|
+
]) or {}
|
|
114
|
+
selected_model = model_answer['selected_model']
|
|
115
|
+
|
|
116
|
+
# Tips and equivalent command without prompts
|
|
117
|
+
def _maybe_quote(val: str) -> str:
|
|
118
|
+
return f'"{val}"' if any(ch.isspace() for ch in val) else val
|
|
119
|
+
|
|
120
|
+
parts = ["sqrl", "compile", "-y"]
|
|
121
|
+
if buildtime_only:
|
|
122
|
+
parts.append("--buildtime-only")
|
|
123
|
+
elif runtime_only:
|
|
124
|
+
parts.append("--runtime-only")
|
|
125
|
+
if do_all_test_sets:
|
|
126
|
+
parts.append("-T")
|
|
127
|
+
elif test_set:
|
|
128
|
+
parts.extend(["-t", _maybe_quote(test_set)])
|
|
129
|
+
if selected_model:
|
|
130
|
+
parts.extend(["-s", _maybe_quote(selected_model)])
|
|
131
|
+
|
|
132
|
+
# Pretty tips block
|
|
133
|
+
tips_header = " Compile tips "
|
|
134
|
+
border = "=" * 80
|
|
135
|
+
print(border)
|
|
136
|
+
print(tips_header)
|
|
137
|
+
print("-" * len(border))
|
|
138
|
+
print("Equivalent command (no prompts):")
|
|
139
|
+
print(f" $ {' '.join(parts)}")
|
|
140
|
+
print()
|
|
141
|
+
print("You can also:")
|
|
142
|
+
print(" - Add -c/--clear to clear the 'target/compile/' folder before compiling")
|
|
143
|
+
print(" - Add -r/--runquery to generate CSVs for runtime models")
|
|
144
|
+
print(border)
|
|
145
|
+
print()
|
|
146
|
+
|
|
147
|
+
return buildtime_only, runtime_only, test_set, do_all_test_sets, selected_model
|
squirrels/_connection_set.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from sqlalchemy import Engine
|
|
4
|
-
import time, polars as pl
|
|
4
|
+
import time, polars as pl, duckdb
|
|
5
5
|
|
|
6
6
|
from . import _utils as u, _constants as c, _py_module as pm
|
|
7
|
-
from .
|
|
8
|
-
from ._manifest import ManifestConfig, ConnectionProperties,
|
|
7
|
+
from ._arguments.init_time_args import ConnectionsArgs
|
|
8
|
+
from ._manifest import ManifestConfig, ConnectionProperties, ConnectionTypeEnum
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@dataclass
|
|
@@ -31,13 +31,20 @@ class ConnectionSet:
|
|
|
31
31
|
def run_sql_query_from_conn_name(self, query: str, conn_name: str, placeholders: dict = {}) -> pl.DataFrame:
|
|
32
32
|
conn = self.get_connection(conn_name)
|
|
33
33
|
try:
|
|
34
|
-
if isinstance(conn, ConnectionProperties) and (conn.type ==
|
|
34
|
+
if isinstance(conn, ConnectionProperties) and (conn.type == ConnectionTypeEnum.CONNECTORX or conn.type == ConnectionTypeEnum.ADBC):
|
|
35
35
|
if len(placeholders) > 0:
|
|
36
36
|
raise u.ConfigurationError(f"Connection '{conn_name}' is a ConnectorX or ADBC connection, which does not support placeholders")
|
|
37
37
|
df = pl.read_database_uri(query, conn.uri, engine=conn.type.value)
|
|
38
|
-
elif isinstance(conn, ConnectionProperties) and conn.type ==
|
|
38
|
+
elif isinstance(conn, ConnectionProperties) and conn.type == ConnectionTypeEnum.SQLALCHEMY:
|
|
39
39
|
with conn.engine.connect() as connection:
|
|
40
40
|
df = pl.read_database(query, connection, execute_options={"parameters": placeholders})
|
|
41
|
+
elif isinstance(conn, ConnectionProperties) and conn.type == ConnectionTypeEnum.DUCKDB:
|
|
42
|
+
path = conn.uri # .replace("duckdb://", "")
|
|
43
|
+
dd_conn = duckdb.connect(path)
|
|
44
|
+
try:
|
|
45
|
+
df = dd_conn.sql(query, params=placeholders).pl()
|
|
46
|
+
finally:
|
|
47
|
+
dd_conn.close()
|
|
41
48
|
else:
|
|
42
49
|
df = pl.read_database(query, conn, execute_options={"parameters": placeholders}) # type: ignore
|
|
43
50
|
return df
|
|
@@ -52,7 +59,7 @@ class ConnectionSet:
|
|
|
52
59
|
if isinstance(conn, Engine):
|
|
53
60
|
conn.dispose()
|
|
54
61
|
elif isinstance(conn, ConnectionProperties):
|
|
55
|
-
if conn.type ==
|
|
62
|
+
if conn.type == ConnectionTypeEnum.SQLALCHEMY:
|
|
56
63
|
conn.engine.dispose()
|
|
57
64
|
elif hasattr(conn, 'close'):
|
|
58
65
|
conn.close()
|
|
@@ -83,7 +90,9 @@ class ConnectionSetIO:
|
|
|
83
90
|
connections: dict[str, ConnectionProperties | Any] = {}
|
|
84
91
|
|
|
85
92
|
for config in manifest_cfg.connections.values():
|
|
86
|
-
connections[config.name] = ConnectionProperties(
|
|
93
|
+
connections[config.name] = ConnectionProperties(
|
|
94
|
+
label=config.label, type=config.type, uri=config.uri, sa_create_engine_args=config.sa_create_engine_args
|
|
95
|
+
)
|
|
87
96
|
|
|
88
97
|
pm.run_pyconfig_main(base_path, c.CONNECTIONS_FILE, {"connections": connections, "sqrl": conn_args})
|
|
89
98
|
|
squirrels/_constants.py
CHANGED
|
@@ -13,27 +13,39 @@ SQRL_SECRET_ADMIN_PASSWORD = 'SQRL_SECRET__ADMIN_PASSWORD'
|
|
|
13
13
|
|
|
14
14
|
SQRL_AUTH_DB_FILE_PATH = 'SQRL_AUTH__DB_FILE_PATH'
|
|
15
15
|
SQRL_AUTH_TOKEN_EXPIRE_MINUTES = 'SQRL_AUTH__TOKEN_EXPIRE_MINUTES'
|
|
16
|
+
SQRL_AUTH_CREDENTIAL_ORIGINS = 'SQRL_AUTH__ALLOWED_ORIGINS_FOR_COOKIES'
|
|
16
17
|
|
|
17
18
|
SQRL_PARAMETERS_CACHE_SIZE = 'SQRL_PARAMETERS__CACHE_SIZE'
|
|
18
19
|
SQRL_PARAMETERS_CACHE_TTL_MINUTES = 'SQRL_PARAMETERS__CACHE_TTL_MINUTES'
|
|
20
|
+
SQRL_PARAMETERS_DATASOURCE_REFRESH_MINUTES = 'SQRL_PARAMETERS__DATASOURCE_REFRESH_MINUTES'
|
|
19
21
|
|
|
20
22
|
SQRL_DATASETS_CACHE_SIZE = 'SQRL_DATASETS__CACHE_SIZE'
|
|
21
23
|
SQRL_DATASETS_CACHE_TTL_MINUTES = 'SQRL_DATASETS__CACHE_TTL_MINUTES'
|
|
24
|
+
SQRL_DATASETS_MAX_ROWS_FOR_AI = 'SQRL_DATASETS__MAX_ROWS_FOR_AI'
|
|
22
25
|
|
|
23
26
|
SQRL_DASHBOARDS_CACHE_SIZE = 'SQRL_DASHBOARDS__CACHE_SIZE'
|
|
24
27
|
SQRL_DASHBOARDS_CACHE_TTL_MINUTES = 'SQRL_DASHBOARDS__CACHE_TTL_MINUTES'
|
|
25
28
|
|
|
29
|
+
SQRL_PERMISSIONS_ELEVATED_ACCESS_LEVEL = 'SQRL_PERMISSIONS__ELEVATED_ACCESS_LEVEL'
|
|
30
|
+
|
|
26
31
|
SQRL_SEEDS_INFER_SCHEMA = 'SQRL_SEEDS__INFER_SCHEMA'
|
|
27
32
|
SQRL_SEEDS_NA_VALUES = 'SQRL_SEEDS__NA_VALUES'
|
|
28
33
|
|
|
29
|
-
SQRL_TEST_SETS_DEFAULT_NAME_USED = 'SQRL_TEST_SETS__DEFAULT_NAME_USED'
|
|
30
|
-
|
|
31
34
|
SQRL_CONNECTIONS_DEFAULT_NAME_USED = 'SQRL_CONNECTIONS__DEFAULT_NAME_USED'
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
SQRL_VDL_CATALOG_DB_PATH = 'SQRL_VDL__CATALOG_DB_PATH'
|
|
37
|
+
SQRL_VDL_DATA_PATH = 'SQRL_VDL__DATA_PATH'
|
|
38
|
+
|
|
39
|
+
SQRL_STUDIO_BASE_URL = 'SQRL_STUDIO__BASE_URL'
|
|
40
|
+
|
|
41
|
+
SQRL_LOGGING_LOG_LEVEL = 'SQRL_LOGGING__LOG_LEVEL'
|
|
42
|
+
SQRL_LOGGING_LOG_FORMAT = 'SQRL_LOGGING__LOG_FORMAT'
|
|
43
|
+
SQRL_LOGGING_LOG_TO_FILE = 'SQRL_LOGGING__LOG_TO_FILE'
|
|
44
|
+
SQRL_LOGGING_LOG_FILE_SIZE_MB = 'SQRL_LOGGING__LOG_FILE_SIZE_MB'
|
|
45
|
+
SQRL_LOGGING_LOG_FILE_BACKUP_COUNT = 'SQRL_LOGGING__LOG_FILE_BACKUP_COUNT'
|
|
34
46
|
|
|
35
47
|
# Folder/File names
|
|
36
|
-
PACKAGE_DATA_FOLDER = '
|
|
48
|
+
PACKAGE_DATA_FOLDER = '_package_data'
|
|
37
49
|
BASE_PROJECT_FOLDER = 'base_project'
|
|
38
50
|
|
|
39
51
|
GLOBAL_ENV_FOLDER = '.squirrels'
|
|
@@ -52,6 +64,7 @@ LOGS_FILE = 'squirrels.log'
|
|
|
52
64
|
|
|
53
65
|
DATABASE_FOLDER = 'assets'
|
|
54
66
|
PACKAGES_FOLDER = 'sqrl_packages'
|
|
67
|
+
PUBLIC_FOLDER = 'public'
|
|
55
68
|
|
|
56
69
|
MACROS_FOLDER = 'macros'
|
|
57
70
|
MACROS_FILE = 'macros_example.sql'
|
|
@@ -77,17 +90,19 @@ USER_FILE = 'user.py'
|
|
|
77
90
|
ADMIN_USERNAME = 'admin'
|
|
78
91
|
|
|
79
92
|
TARGET_FOLDER = 'target'
|
|
80
|
-
DB_FILE = 'auth.sqlite'
|
|
81
93
|
COMPILE_FOLDER = 'compile'
|
|
82
|
-
|
|
94
|
+
COMPILE_BUILDTIME_FOLDER = 'buildtime'
|
|
95
|
+
COMPILE_RUNTIME_FOLDER = 'runtime'
|
|
96
|
+
DB_FILE = 'auth.sqlite'
|
|
97
|
+
DEFAULT_VDL_CATALOG_DB_PATH = 'ducklake:{project_path}/target/vdl_catalog.duckdb'
|
|
98
|
+
DEFAULT_VDL_DATA_PATH = '{project_path}/target/vdl_data/'
|
|
83
99
|
|
|
84
100
|
SEEDS_FOLDER = 'seeds'
|
|
85
101
|
SEED_CATEGORY_FILE_STEM = 'seed_categories'
|
|
86
102
|
SEED_SUBCATEGORY_FILE_STEM = 'seed_subcategories'
|
|
87
103
|
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
PARAMETERS_SECTION = 'parameters'
|
|
104
|
+
# Test sets
|
|
105
|
+
DEFAULT_TEST_SET_NAME = 'default'
|
|
91
106
|
|
|
92
107
|
# Init Command Choices
|
|
93
108
|
SQL_FILE_TYPE = 'sql'
|
|
@@ -114,3 +129,8 @@ MAIN_FUNC = "main"
|
|
|
114
129
|
# Regex
|
|
115
130
|
DATE_REGEX = r"^\d{4}\-\d{2}\-\d{2}$"
|
|
116
131
|
COLOR_REGEX = r"^#[0-9a-fA-F]{6}$"
|
|
132
|
+
|
|
133
|
+
# OAuth2
|
|
134
|
+
SUPPORTED_SCOPES = ['read']
|
|
135
|
+
SUPPORTED_GRANT_TYPES = ['authorization_code', 'refresh_token']
|
|
136
|
+
SUPPORTED_RESPONSE_TYPES = ['code']
|
|
@@ -2,15 +2,96 @@ from typing import Type, TypeVar, Callable, Coroutine, Any
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
|
-
import
|
|
5
|
+
import matplotlib.figure as figure
|
|
6
|
+
import os, time, io, abc, typing
|
|
6
7
|
|
|
7
|
-
from .
|
|
8
|
+
from ._arguments.run_time_args import DashboardArgs
|
|
8
9
|
from ._py_module import PyModule
|
|
9
10
|
from ._manifest import AnalyticsOutputConfig
|
|
10
11
|
from ._exceptions import InvalidInputError, ConfigurationError, FileExecutionError
|
|
11
|
-
from . import _constants as c, _utils as u
|
|
12
|
+
from . import _constants as c, _utils as u
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
|
|
15
|
+
class Dashboard(metaclass=abc.ABCMeta):
|
|
16
|
+
"""
|
|
17
|
+
Abstract parent class for all Dashboard classes.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
@abc.abstractmethod
|
|
22
|
+
def _content(self) -> bytes | str:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
@abc.abstractmethod
|
|
27
|
+
def _format(self) -> str:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PngDashboard(Dashboard):
|
|
32
|
+
"""
|
|
33
|
+
Instantiate a Dashboard in PNG format from a matplotlib figure or bytes
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, content: figure.Figure | io.BytesIO | bytes) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Constructor for PngDashboard
|
|
39
|
+
|
|
40
|
+
Arguments:
|
|
41
|
+
content: The content of the dashboard as a matplotlib.figure.Figure or bytes
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(content, figure.Figure):
|
|
44
|
+
buffer = io.BytesIO()
|
|
45
|
+
content.savefig(buffer, format=c.PNG)
|
|
46
|
+
content = buffer.getvalue()
|
|
47
|
+
|
|
48
|
+
if isinstance(content, io.BytesIO):
|
|
49
|
+
content = content.getvalue()
|
|
50
|
+
|
|
51
|
+
self.__content = content
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def _content(self) -> bytes:
|
|
55
|
+
return self.__content
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def _format(self) -> typing.Literal['png']:
|
|
59
|
+
return c.PNG
|
|
60
|
+
|
|
61
|
+
def _repr_png_(self):
|
|
62
|
+
return self._content
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class HtmlDashboard(Dashboard):
|
|
66
|
+
"""
|
|
67
|
+
Instantiate a Dashboard from an HTML string
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, content: io.StringIO | str) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Constructor for HtmlDashboard
|
|
73
|
+
|
|
74
|
+
Arguments:
|
|
75
|
+
content: The content of the dashboard as HTML string
|
|
76
|
+
"""
|
|
77
|
+
if isinstance(content, io.StringIO):
|
|
78
|
+
content = content.getvalue()
|
|
79
|
+
|
|
80
|
+
self.__content = content
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def _content(self) -> str:
|
|
84
|
+
return self.__content
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def _format(self) -> typing.Literal['html']:
|
|
88
|
+
return c.HTML
|
|
89
|
+
|
|
90
|
+
def _repr_html_(self):
|
|
91
|
+
return self._content
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
T = TypeVar('T', bound=Dashboard)
|
|
14
95
|
|
|
15
96
|
|
|
16
97
|
class DashboardFormat(Enum):
|
|
@@ -34,7 +115,7 @@ class DashboardDefinition:
|
|
|
34
115
|
config: DashboardConfig
|
|
35
116
|
|
|
36
117
|
@property
|
|
37
|
-
def dashboard_func(self) -> Callable[[DashboardArgs], Coroutine[Any, Any,
|
|
118
|
+
def dashboard_func(self) -> Callable[[DashboardArgs], Coroutine[Any, Any, Dashboard]]:
|
|
38
119
|
if not hasattr(self, '_dashboard_func'):
|
|
39
120
|
module = PyModule(self.filepath)
|
|
40
121
|
self._dashboard_func = module.get_func_or_class(c.MAIN_FUNC)
|
|
@@ -43,7 +124,7 @@ class DashboardDefinition:
|
|
|
43
124
|
def get_dashboard_format(self) -> str:
|
|
44
125
|
return self.config.format.value
|
|
45
126
|
|
|
46
|
-
async def get_dashboard(self, args: DashboardArgs, *, dashboard_type: Type[T] =
|
|
127
|
+
async def get_dashboard(self, args: DashboardArgs, *, dashboard_type: Type[T] = Dashboard) -> T:
|
|
47
128
|
try:
|
|
48
129
|
dashboard = await self.dashboard_func(args)
|
|
49
130
|
assert isinstance(dashboard, dashboard_type), f"Function does not return expected Dashboard type: {dashboard_type}"
|