squirrels 0.5.0b3__py3-none-any.whl → 0.6.0.post0__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.
Files changed (93) hide show
  1. squirrels/__init__.py +4 -0
  2. squirrels/_api_routes/__init__.py +5 -0
  3. squirrels/_api_routes/auth.py +337 -0
  4. squirrels/_api_routes/base.py +196 -0
  5. squirrels/_api_routes/dashboards.py +156 -0
  6. squirrels/_api_routes/data_management.py +148 -0
  7. squirrels/_api_routes/datasets.py +220 -0
  8. squirrels/_api_routes/project.py +289 -0
  9. squirrels/_api_server.py +440 -792
  10. squirrels/_arguments/__init__.py +0 -0
  11. squirrels/_arguments/{_init_time_args.py → init_time_args.py} +23 -43
  12. squirrels/_arguments/{_run_time_args.py → run_time_args.py} +32 -68
  13. squirrels/_auth.py +590 -264
  14. squirrels/_command_line.py +130 -58
  15. squirrels/_compile_prompts.py +147 -0
  16. squirrels/_connection_set.py +16 -15
  17. squirrels/_constants.py +36 -11
  18. squirrels/_dashboards.py +179 -0
  19. squirrels/_data_sources.py +40 -34
  20. squirrels/_dataset_types.py +16 -11
  21. squirrels/_env_vars.py +209 -0
  22. squirrels/_exceptions.py +9 -37
  23. squirrels/_http_error_responses.py +52 -0
  24. squirrels/_initializer.py +7 -6
  25. squirrels/_logging.py +121 -0
  26. squirrels/_manifest.py +155 -77
  27. squirrels/_mcp_server.py +578 -0
  28. squirrels/_model_builder.py +11 -55
  29. squirrels/_model_configs.py +5 -5
  30. squirrels/_model_queries.py +1 -1
  31. squirrels/_models.py +276 -143
  32. squirrels/_package_data/base_project/.env +1 -24
  33. squirrels/_package_data/base_project/.env.example +31 -17
  34. squirrels/_package_data/base_project/connections.yml +4 -3
  35. squirrels/_package_data/base_project/dashboards/dashboard_example.py +13 -7
  36. squirrels/_package_data/base_project/dashboards/dashboard_example.yml +6 -6
  37. squirrels/_package_data/base_project/docker/Dockerfile +2 -2
  38. squirrels/_package_data/base_project/docker/compose.yml +1 -1
  39. squirrels/_package_data/base_project/duckdb_init.sql +1 -0
  40. squirrels/_package_data/base_project/models/builds/build_example.py +2 -2
  41. squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +7 -2
  42. squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +16 -10
  43. squirrels/_package_data/base_project/models/federates/federate_example.py +27 -17
  44. squirrels/_package_data/base_project/models/federates/federate_example.sql +3 -7
  45. squirrels/_package_data/base_project/models/federates/federate_example.yml +7 -7
  46. squirrels/_package_data/base_project/models/sources.yml +5 -6
  47. squirrels/_package_data/base_project/parameters.yml +24 -38
  48. squirrels/_package_data/base_project/pyconfigs/connections.py +8 -3
  49. squirrels/_package_data/base_project/pyconfigs/context.py +26 -14
  50. squirrels/_package_data/base_project/pyconfigs/parameters.py +124 -81
  51. squirrels/_package_data/base_project/pyconfigs/user.py +48 -15
  52. squirrels/_package_data/base_project/resources/public/.gitkeep +0 -0
  53. squirrels/_package_data/base_project/seeds/seed_categories.yml +1 -1
  54. squirrels/_package_data/base_project/seeds/seed_subcategories.yml +1 -1
  55. squirrels/_package_data/base_project/squirrels.yml.j2 +21 -31
  56. squirrels/_package_data/templates/login_successful.html +53 -0
  57. squirrels/_package_data/templates/squirrels_studio.html +22 -0
  58. squirrels/_parameter_configs.py +43 -22
  59. squirrels/_parameter_options.py +1 -1
  60. squirrels/_parameter_sets.py +41 -30
  61. squirrels/_parameters.py +560 -123
  62. squirrels/_project.py +487 -277
  63. squirrels/_py_module.py +71 -10
  64. squirrels/_request_context.py +33 -0
  65. squirrels/_schemas/__init__.py +0 -0
  66. squirrels/_schemas/auth_models.py +83 -0
  67. squirrels/_schemas/query_param_models.py +70 -0
  68. squirrels/_schemas/request_models.py +26 -0
  69. squirrels/_schemas/response_models.py +286 -0
  70. squirrels/_seeds.py +52 -13
  71. squirrels/_sources.py +29 -23
  72. squirrels/_utils.py +221 -42
  73. squirrels/_version.py +1 -3
  74. squirrels/arguments.py +7 -2
  75. squirrels/auth.py +4 -0
  76. squirrels/connections.py +2 -0
  77. squirrels/dashboards.py +3 -1
  78. squirrels/data_sources.py +6 -0
  79. squirrels/parameter_options.py +5 -0
  80. squirrels/parameters.py +5 -0
  81. squirrels/types.py +10 -3
  82. squirrels-0.6.0.post0.dist-info/METADATA +148 -0
  83. squirrels-0.6.0.post0.dist-info/RECORD +101 -0
  84. {squirrels-0.5.0b3.dist-info → squirrels-0.6.0.post0.dist-info}/WHEEL +1 -1
  85. squirrels/_api_response_models.py +0 -190
  86. squirrels/_dashboard_types.py +0 -82
  87. squirrels/_dashboards_io.py +0 -79
  88. squirrels-0.5.0b3.dist-info/METADATA +0 -110
  89. squirrels-0.5.0b3.dist-info/RECORD +0 -80
  90. /squirrels/_package_data/base_project/{assets → resources}/expenses.db +0 -0
  91. /squirrels/_package_data/base_project/{assets → resources}/weather.db +0 -0
  92. {squirrels-0.5.0b3.dist-info → squirrels-0.6.0.post0.dist-info}/entry_points.txt +0 -0
  93. {squirrels-0.5.0b3.dist-info → squirrels-0.6.0.post0.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,6 @@
1
1
  from argparse import ArgumentParser, _SubParsersAction
2
+ from pathlib import Path
3
+ from update_checker import UpdateChecker
2
4
  import sys, asyncio, traceback, io, subprocess
3
5
 
4
6
  sys.path.append('.')
@@ -9,22 +11,35 @@ from ._initializer import Initializer
9
11
  from ._package_loader import PackageLoaderIO
10
12
  from ._project import SquirrelsProject
11
13
  from . import _constants as c, _utils as u
14
+ from ._compile_prompts import prompt_compile_options
12
15
 
13
16
 
14
17
  def _run_duckdb_cli(project: SquirrelsProject, ui: bool):
15
- _, target_init_path = u._read_duckdb_init_sql()
16
- init_args = f"-init {target_init_path}" if target_init_path else ""
18
+ init_sql = u._read_duckdb_init_sql(datalake_db_path=project._vdl_catalog_db_path)
19
+
20
+ target_init_path = None
21
+ if init_sql:
22
+ target_init_path = Path(c.TARGET_FOLDER, c.DUCKDB_INIT_FILE)
23
+ target_init_path.parent.mkdir(parents=True, exist_ok=True)
24
+ target_init_path.write_text(init_sql)
25
+
26
+ init_args = ["-init", str(target_init_path)] if target_init_path else []
17
27
  command = ['duckdb']
18
28
  if ui:
19
29
  command.append('-ui')
20
30
  if init_args:
21
- command.extend(init_args.split())
22
- command.extend(['-readonly', project._duckdb_venv_path])
23
- print(f'Running command: {" ".join(command)}')
31
+ command.extend(init_args)
32
+
33
+ print("Starting DuckDB CLI with command:")
34
+ print(f"$ {' '.join(command)}")
35
+ print()
36
+ print("To exit the DuckDB CLI, enter '.exit'")
37
+ print()
38
+
24
39
  try:
25
40
  subprocess.run(command, check=True)
26
41
  except FileNotFoundError:
27
- print("DuckDB CLI not found. Please install it from: https://duckdb.org/docs/installation/")
42
+ print("DuckDB CLI not found. Please install it from: https://duckdb.org/install")
28
43
  except subprocess.CalledProcessError:
29
44
  pass # ignore errors that occured on duckdb shell commands
30
45
 
@@ -33,32 +48,45 @@ def main():
33
48
  """
34
49
  Main entry point for the squirrels command line utilities.
35
50
  """
36
- def with_help(parser: ArgumentParser):
37
- parser.add_argument('-h', '--help', action="help", help="Show this help message and exit")
38
- return parser
39
-
40
- parser = with_help(ArgumentParser(description="Command line utilities from the squirrels python package", add_help=False))
51
+ # Create a parent parser with common logging options
52
+ parent_parser = ArgumentParser(add_help=False)
53
+ parent_parser.add_argument('-h', '--help', action="help", help="Show this help message and exit")
54
+ parent_parser.add_argument('--log-level', type=str, choices=["DEBUG", "INFO", "WARNING"], help='Level of logging to use. Default is from SQRL_LOGGING__LEVEL environment variable or INFO.')
55
+ parent_parser.add_argument('--log-format', type=str, choices=["text", "json"], help='Format of the log records. Default is from SQRL_LOGGING__FORMAT environment variable or text.')
56
+ parent_parser.add_argument(
57
+ '--log-to-file', action='store_true',
58
+ help='Enable logging to file(s) in the "logs/" folder (or a custom folder) with rotation and retention policies. '
59
+ 'Default is False unless overridden by the SQRL_LOGGING__TO_FILE environment variable.'
60
+ )
61
+
62
+ parser = ArgumentParser(description="Command line utilities from the squirrels python package", add_help=False, parents=[parent_parser])
41
63
  parser.add_argument('-V', '--version', action='store_true', help='Show the version and exit')
42
- parser.add_argument('--log-level', type=str, choices=["DEBUG", "INFO", "WARNING"], default="INFO", help='Level of logging to use')
43
- parser.add_argument('--log-format', type=str, choices=["text", "json"], default="text", help='Format of the log records')
44
- 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')
45
64
  subparsers = parser.add_subparsers(title='commands', dest='command')
46
65
 
47
66
  def add_subparser(subparsers: _SubParsersAction, cmd: str, help_text: str):
48
- subparser = with_help(subparsers.add_parser(cmd, description=help_text, help=help_text, add_help=False))
67
+ subparser: ArgumentParser = subparsers.add_parser(cmd, description=help_text, help=help_text, add_help=False, parents=[parent_parser])
49
68
  return subparser
50
69
 
51
- init_parser = add_subparser(subparsers, c.INIT_CMD, 'Create a new squirrels project')
70
+ new_parser = add_subparser(subparsers, c.NEW_CMD, 'Create a new squirrels project')
52
71
 
53
- init_parser.add_argument('name', nargs='?', type=str, help='The name of the project folder to create. Ignored if --curr-dir is used')
54
- init_parser.add_argument('--curr-dir', action='store_true', help='Create the project in the current directory')
55
- 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')
72
+ new_parser.add_argument('name', nargs='?', type=str, help='The name of the project folder to create. Ignored if --curr-dir is used')
73
+ new_parser.add_argument('--curr-dir', action='store_true', help='Create the project in the current directory')
74
+ new_parser.add_argument('--use-defaults', action='store_true', help='Use default values for unspecified options (except project folder name) instead of prompting for input')
75
+ new_parser.add_argument('--connections', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure database connections as yaml (default) or python')
76
+ new_parser.add_argument('--parameters', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure parameters as python (default) or yaml')
77
+ new_parser.add_argument('--build', type=str, choices=c.FILE_TYPE_CHOICES, help='Create build model as sql (default) or python file')
78
+ new_parser.add_argument('--federate', type=str, choices=c.FILE_TYPE_CHOICES, help='Create federated model as sql (default) or python file')
79
+ new_parser.add_argument('--dashboard', type=str, choices=['y', 'n'], help=f'Include (y) or exclude (n, default) a sample dashboard file')
80
+ new_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')
81
+
82
+ init_parser = add_subparser(subparsers, c.INIT_CMD, 'Create a new squirrels project in the current directory (alias for "new --curr-dir")')
83
+ init_parser.add_argument('--use-defaults', action='store_true', help='Use default values for unspecified options instead of prompting for input')
56
84
  init_parser.add_argument('--connections', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure database connections as yaml (default) or python')
57
85
  init_parser.add_argument('--parameters', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure parameters as python (default) or yaml')
58
86
  init_parser.add_argument('--build', type=str, choices=c.FILE_TYPE_CHOICES, help='Create build model as sql (default) or python file')
59
87
  init_parser.add_argument('--federate', type=str, choices=c.FILE_TYPE_CHOICES, help='Create federated model as sql (default) or python file')
60
88
  init_parser.add_argument('--dashboard', type=str, choices=['y', 'n'], help=f'Include (y) or exclude (n, default) a sample dashboard file')
61
- 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')
89
+ init_parser.add_argument('--admin-password', type=str, help='The password for the admin user. By default, a random password is generated')
62
90
 
63
91
  def with_file_format_options(parser: ArgumentParser):
64
92
  help_text = "Create model as sql (default) or python file"
@@ -68,70 +96,93 @@ def main():
68
96
  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."
69
97
  get_file_parser = add_subparser(subparsers, c.GET_FILE_CMD, get_file_help_text)
70
98
  get_file_subparsers = get_file_parser.add_subparsers(title='file_name', dest='file_name')
71
- add_subparser(get_file_subparsers, c.DOTENV_FILE, f'Get sample {c.DOTENV_FILE} and {c.DOTENV_FILE}.example files')
72
- add_subparser(get_file_subparsers, c.GITIGNORE_FILE, f'Get a sample {c.GITIGNORE_FILE} file')
73
- manifest_parser = add_subparser(get_file_subparsers, c.MANIFEST_FILE, f'Get a sample {c.MANIFEST_FILE} file')
99
+ add_subparser(get_file_subparsers, c.DOTENV_FILE, f'Creates sample {c.DOTENV_FILE} and {c.DOTENV_FILE}.example files')
100
+ add_subparser(get_file_subparsers, c.GITIGNORE_FILE, f'Creates a sample {c.GITIGNORE_FILE} file')
101
+ manifest_parser = add_subparser(get_file_subparsers, c.MANIFEST_FILE, f'Creates a sample {c.MANIFEST_FILE} file')
74
102
  manifest_parser.add_argument("--no-connections", action='store_true', help=f'Exclude the connections section')
75
103
  manifest_parser.add_argument("--parameters", action='store_true', help=f'Include the parameters section')
76
104
  manifest_parser.add_argument("--dashboards", action='store_true', help=f'Include the dashboards section')
77
- add_subparser(get_file_subparsers, c.USER_FILE, f'Get a sample {c.USER_FILE} file')
78
- add_subparser(get_file_subparsers, c.CONNECTIONS_FILE, f'Get a sample {c.CONNECTIONS_FILE} file')
79
- add_subparser(get_file_subparsers, c.PARAMETERS_FILE, f'Get a sample {c.PARAMETERS_FILE} file')
80
- add_subparser(get_file_subparsers, c.CONTEXT_FILE, f'Get a sample {c.CONTEXT_FILE} file')
81
- add_subparser(get_file_subparsers, c.MACROS_FILE, f'Get a sample {c.MACROS_FILE} file')
82
- add_subparser(get_file_subparsers, c.SOURCES_FILE, f'Get a sample {c.SOURCES_FILE} file')
83
- with_file_format_options(add_subparser(get_file_subparsers, c.BUILD_FILE_STEM, f'Get a sample build model file'))
84
- add_subparser(get_file_subparsers, c.DBVIEW_FILE_STEM, f'Get a sample dbview model file')
85
- with_file_format_options(add_subparser(get_file_subparsers, c.FEDERATE_FILE_STEM, f'Get a sample federate model file'))
86
- add_subparser(get_file_subparsers, c.DASHBOARD_FILE_STEM, f'Get a sample dashboard file')
87
- add_subparser(get_file_subparsers, c.EXPENSES_DB, f'Get the sample SQLite database on expenses')
88
- add_subparser(get_file_subparsers, c.WEATHER_DB, f'Get the sample SQLite database on weather')
89
- add_subparser(get_file_subparsers, c.SEED_CATEGORY_FILE_STEM, f'Get the sample seed files for categories lookup')
90
- add_subparser(get_file_subparsers, c.SEED_SUBCATEGORY_FILE_STEM, f'Get the sample seed files for subcategories lookup')
105
+ add_subparser(get_file_subparsers, c.USER_FILE, f'Creates a sample {c.USER_FILE} file')
106
+ add_subparser(get_file_subparsers, c.CONNECTIONS_FILE, f'Creates a sample {c.CONNECTIONS_FILE} file')
107
+ add_subparser(get_file_subparsers, c.PARAMETERS_FILE, f'Creates a sample {c.PARAMETERS_FILE} file')
108
+ add_subparser(get_file_subparsers, c.CONTEXT_FILE, f'Creates a sample {c.CONTEXT_FILE} file')
109
+ add_subparser(get_file_subparsers, c.MACROS_FILE, f'Creates a sample {c.MACROS_FILE} file')
110
+ add_subparser(get_file_subparsers, c.SOURCES_FILE, f'Creates a sample {c.SOURCES_FILE} file')
111
+ with_file_format_options(add_subparser(get_file_subparsers, c.BUILD_FILE_STEM, f'Creates a sample build model file'))
112
+ add_subparser(get_file_subparsers, c.DBVIEW_FILE_STEM, f'Creates a sample dbview model file')
113
+ with_file_format_options(add_subparser(get_file_subparsers, c.FEDERATE_FILE_STEM, f'Creates a sample federate model file'))
114
+ add_subparser(get_file_subparsers, c.DASHBOARD_FILE_STEM, f'Creates a sample dashboard file')
115
+ add_subparser(get_file_subparsers, c.EXPENSES_DB, f'Creates the sample SQLite database on expenses')
116
+ add_subparser(get_file_subparsers, c.WEATHER_DB, f'Creates the sample SQLite database on weather')
117
+ add_subparser(get_file_subparsers, c.SEED_CATEGORY_FILE_STEM, f'Creates the sample seed files for categories lookup')
118
+ add_subparser(get_file_subparsers, c.SEED_SUBCATEGORY_FILE_STEM, f'Creates the sample seed files for subcategories lookup')
91
119
 
92
120
  deps_parser = add_subparser(subparsers, c.DEPS_CMD, f'Load all packages specified in {c.MANIFEST_FILE} (from git)')
93
121
 
94
122
  compile_parser = add_subparser(subparsers, c.COMPILE_CMD, 'Create rendered SQL files in the folder "./target/compile"')
95
- compile_dataset_group = compile_parser.add_mutually_exclusive_group(required=True)
96
- compile_dataset_group.add_argument('-d', '--dataset', type=str, help="Select dataset to use for dataset traits. Is required, unless using --all-datasets")
97
- compile_dataset_group.add_argument('-D', '--all-datasets', action="store_true", help="Compile models for all datasets. Only required if --dataset is not specified")
123
+ compile_parser.add_argument('-y', '--yes', action='store_true', help='Disable prompts and assume the default for all settings not overridden by CLI options')
124
+ compile_parser.add_argument('-c', '--clear', action='store_true', help='Clear the "target/compile/" folder before compiling')
125
+ compile_scope_group = compile_parser.add_mutually_exclusive_group(required=False)
126
+ compile_scope_group.add_argument('--buildtime-only', action='store_true', help='Compile only buildtime models')
127
+ compile_scope_group.add_argument('--runtime-only', action='store_true', help='Compile only runtime models')
98
128
  compile_test_set_group = compile_parser.add_mutually_exclusive_group(required=False)
99
- 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")
100
- compile_test_set_group.add_argument('-T', '--all-test-sets', action="store_true", help="Compile models for all selection test sets")
101
- 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")
102
- compile_parser.add_argument('-r', '--runquery', action='store_true', help='Runs all target models, and produce the results as csv files')
103
-
104
- build_parser = add_subparser(subparsers, c.BUILD_CMD, 'Build the virtual data environment (with duckdb) for the project')
129
+ 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")
130
+ 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")
131
+ compile_parser.add_argument('-s', '--select', type=str, help="Select single model to compile. If not specified, all models are compiled")
132
+ compile_parser.add_argument('-r', '--runquery', action='store_true', help='Run runtime models and write CSV outputs too. Does not apply to buildtime models')
133
+
134
+ build_parser = add_subparser(subparsers, c.BUILD_CMD, 'Build the Virtual Data Lake (VDL) for the project')
105
135
  build_parser.add_argument('-f', '--full-refresh', action='store_true', help='Drop all tables before building')
106
136
  build_parser.add_argument('-s', '--select', type=str, help="Select one static model to build. If not specified, all models are built")
107
- build_parser.add_argument('--stage', type=str, help='If the venv file is in use, stage the duckdb file to replace the venv later')
108
137
 
109
138
  duckdb_parser = add_subparser(subparsers, c.DUCKDB_CMD, 'Run the duckdb command line tool')
110
139
  duckdb_parser.add_argument('--ui', action='store_true', help='Run the duckdb local UI')
111
140
 
112
141
  run_parser = add_subparser(subparsers, c.RUN_CMD, 'Run the API server')
113
- run_parser.add_argument('--build', action='store_true', help='Build the virtual data environment (with duckdb) first before running the API server')
142
+ run_parser.add_argument('--build', action='store_true', help='Build the VDL first (without full refresh) before running the API server')
114
143
  run_parser.add_argument('--no-cache', action='store_true', help='Do not cache any api results')
115
144
  run_parser.add_argument('--host', type=str, default='127.0.0.1', help="The host to run on")
116
- run_parser.add_argument('--port', type=int, default=4465, help="The port to run on")
145
+ run_parser.add_argument('--port', type=int, default=8000, help="The port to run on")
146
+ run_parser.add_argument(
147
+ '--forwarded-allow-ips', type=str, default=None,
148
+ help="Comma-separated list of IP Addresses, IP Networks, or literals (e.g. UNIX Socket path) to trust with proxy headers. "
149
+ "Defaults to the FORWARDED_ALLOW_IPS environment variable if available, or '127.0.0.1'. The literal '*' means trust everything."
150
+ )
117
151
 
118
152
  args, _ = parser.parse_known_args()
119
153
 
154
+ update_checker_result = UpdateChecker().check("squirrels", __version__)
155
+ if update_checker_result:
156
+ current_version = update_checker_result.running_version
157
+ latest_version = update_checker_result.available_version
158
+ print("\033[1;33mA newer version of Squirrels is available!\033[0m")
159
+ print(f"{current_version} -> \033[1;32m{latest_version}\033[0m (released on {update_checker_result.release_date.date()})")
160
+ print()
161
+ print("See \033[4mhttps://docs.pysquirrels.com/releases\033[0m for release notes.")
162
+ print()
163
+
120
164
  if args.version:
121
- print(__version__)
122
- elif args.command == c.INIT_CMD:
165
+ print(f"Squirrels version: \033[1;32m{__version__}\033[0m")
166
+ elif args.command == c.NEW_CMD:
123
167
  Initializer(project_name=args.name, use_curr_dir=args.curr_dir).init_project(args)
168
+ elif args.command == c.INIT_CMD:
169
+ Initializer(project_name=None, use_curr_dir=True).init_project(args)
124
170
  elif args.command == c.GET_FILE_CMD:
125
171
  Initializer().get_file(args)
126
172
  elif args.command is None:
127
173
  print(f'Command is missing. Enter "squirrels -h" for help.')
128
174
  else:
129
- project = SquirrelsProject(log_level=args.log_level, log_format=args.log_format, log_file=args.log_file)
175
+ project = SquirrelsProject(
176
+ load_dotenv_globally=True,
177
+ log_level=args.log_level,
178
+ log_format=args.log_format,
179
+ log_to_file=args.log_to_file
180
+ )
130
181
  try:
131
182
  if args.command == c.DEPS_CMD:
132
183
  PackageLoaderIO.load_packages(project._logger, project._manifest_cfg, reload=True)
133
184
  elif args.command == c.BUILD_CMD:
134
- task = project.build(full_refresh=args.full_refresh, select=args.select, stage_file=args.stage)
185
+ task = project.build(full_refresh=args.full_refresh, select=args.select)
135
186
  asyncio.run(task)
136
187
  print()
137
188
  elif args.command == c.DUCKDB_CMD:
@@ -143,11 +194,32 @@ def main():
143
194
  server = ApiServer(args.no_cache, project)
144
195
  server.run(args)
145
196
  elif args.command == c.COMPILE_CMD:
146
- task = project.compile(
147
- dataset=args.dataset, do_all_datasets=args.all_datasets, selected_model=args.select, test_set=args.test_set,
148
- do_all_test_sets=args.all_test_sets, runquery=args.runquery
149
- )
150
- asyncio.run(task)
197
+ # Derive final options with optional interactive prompts (unless --yes is provided)
198
+ buildtime_only = args.buildtime_only
199
+ runtime_only = args.runtime_only
200
+ test_set = args.test_set
201
+ do_all_test_sets = args.all_test_sets
202
+ selected_model = args.select
203
+
204
+ try:
205
+ if not args.yes:
206
+ buildtime_only, runtime_only, test_set, do_all_test_sets, selected_model = prompt_compile_options(
207
+ project,
208
+ buildtime_only=buildtime_only,
209
+ runtime_only=runtime_only,
210
+ test_set=test_set,
211
+ do_all_test_sets=do_all_test_sets,
212
+ selected_model=selected_model,
213
+ )
214
+
215
+ task = project.compile(
216
+ selected_model=selected_model, test_set=test_set, do_all_test_sets=do_all_test_sets, runquery=args.runquery,
217
+ clear=args.clear, buildtime_only=buildtime_only, runtime_only=runtime_only
218
+ )
219
+ asyncio.run(task)
220
+
221
+ except KeyError:
222
+ pass
151
223
  else:
152
224
  print(f'Error: No such command "{args.command}". Enter "squirrels -h" for help.')
153
225
 
@@ -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
@@ -1,10 +1,10 @@
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 ._arguments._init_time_args import ConnectionsArgs
7
+ from ._arguments.init_time_args import ConnectionsArgs
8
8
  from ._manifest import ManifestConfig, ConnectionProperties, ConnectionTypeEnum
9
9
 
10
10
 
@@ -38,6 +38,13 @@ class ConnectionSet:
38
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
@@ -61,17 +68,9 @@ class ConnectionSet:
61
68
  class ConnectionSetIO:
62
69
 
63
70
  @classmethod
64
- def load_conn_py_args(cls, logger: u.Logger, base_path: str, env_vars: dict[str, str], manifest_cfg: ManifestConfig) -> ConnectionsArgs:
65
- start = time.time()
66
-
67
- proj_vars = manifest_cfg.project_variables.model_dump()
68
- conn_args = ConnectionsArgs(base_path, proj_vars, env_vars)
69
-
70
- logger.log_activity_time("setting up arguments for connections.py", start)
71
- return conn_args
72
-
73
- @classmethod
74
- def load_from_file(cls, logger: u.Logger, base_path: str, manifest_cfg: ManifestConfig, conn_args: ConnectionsArgs) -> ConnectionSet:
71
+ def load_from_file(
72
+ cls, logger: u.Logger, project_path: str, manifest_cfg: ManifestConfig, conn_args: ConnectionsArgs
73
+ ) -> ConnectionSet:
75
74
  """
76
75
  Takes the DB connection engines from both the squirrels.yml and connections.py files and merges them
77
76
  into a single ConnectionSet
@@ -83,9 +82,11 @@ class ConnectionSetIO:
83
82
  connections: dict[str, ConnectionProperties | Any] = {}
84
83
 
85
84
  for config in manifest_cfg.connections.values():
86
- connections[config.name] = ConnectionProperties(label=config.label, type=config.type, uri=config.uri)
85
+ connections[config.name] = ConnectionProperties(
86
+ label=config.label, type=config.type, uri=config.uri, sa_create_engine_args=config.sa_create_engine_args
87
+ )
87
88
 
88
- pm.run_pyconfig_main(base_path, c.CONNECTIONS_FILE, {"connections": connections, "sqrl": conn_args})
89
+ pm.run_pyconfig_main(project_path, c.CONNECTIONS_FILE, {"connections": connections, "sqrl": conn_args})
89
90
 
90
91
  conn_set = ConnectionSet(connections)
91
92
 
squirrels/_constants.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # Squirrels CLI commands
2
- INIT_CMD = 'new'
2
+ NEW_CMD = 'new'
3
+ INIT_CMD = 'init'
3
4
  GET_FILE_CMD = 'get-file'
4
5
  DEPS_CMD = 'deps'
5
6
  BUILD_CMD = 'build'
@@ -13,12 +14,19 @@ SQRL_SECRET_ADMIN_PASSWORD = 'SQRL_SECRET__ADMIN_PASSWORD'
13
14
 
14
15
  SQRL_AUTH_DB_FILE_PATH = 'SQRL_AUTH__DB_FILE_PATH'
15
16
  SQRL_AUTH_TOKEN_EXPIRE_MINUTES = 'SQRL_AUTH__TOKEN_EXPIRE_MINUTES'
17
+ SQRL_AUTH_CREDENTIAL_ORIGINS = 'SQRL_AUTH__ALLOWED_ORIGINS_FOR_COOKIES'
18
+
19
+ SQRL_PERMISSIONS_ELEVATED_ACCESS_LEVEL = 'SQRL_PERMISSIONS__ELEVATED_ACCESS_LEVEL'
16
20
 
17
21
  SQRL_PARAMETERS_CACHE_SIZE = 'SQRL_PARAMETERS__CACHE_SIZE'
18
22
  SQRL_PARAMETERS_CACHE_TTL_MINUTES = 'SQRL_PARAMETERS__CACHE_TTL_MINUTES'
23
+ SQRL_PARAMETERS_DATASOURCE_REFRESH_MINUTES = 'SQRL_PARAMETERS__DATASOURCE_REFRESH_MINUTES'
19
24
 
20
25
  SQRL_DATASETS_CACHE_SIZE = 'SQRL_DATASETS__CACHE_SIZE'
21
26
  SQRL_DATASETS_CACHE_TTL_MINUTES = 'SQRL_DATASETS__CACHE_TTL_MINUTES'
27
+ SQRL_DATASETS_MAX_ROWS_FOR_AI = 'SQRL_DATASETS__MAX_ROWS_FOR_AI'
28
+ SQRL_DATASETS_MAX_ROWS_OUTPUT = 'SQRL_DATASETS__MAX_ROWS_OUTPUT'
29
+ SQRL_DATASETS_SQL_TIMEOUT_SECONDS = 'SQRL_DATASETS__SQL_TIMEOUT_SECONDS'
22
30
 
23
31
  SQRL_DASHBOARDS_CACHE_SIZE = 'SQRL_DASHBOARDS__CACHE_SIZE'
24
32
  SQRL_DASHBOARDS_CACHE_TTL_MINUTES = 'SQRL_DASHBOARDS__CACHE_TTL_MINUTES'
@@ -26,11 +34,18 @@ SQRL_DASHBOARDS_CACHE_TTL_MINUTES = 'SQRL_DASHBOARDS__CACHE_TTL_MINUTES'
26
34
  SQRL_SEEDS_INFER_SCHEMA = 'SQRL_SEEDS__INFER_SCHEMA'
27
35
  SQRL_SEEDS_NA_VALUES = 'SQRL_SEEDS__NA_VALUES'
28
36
 
29
- SQRL_TEST_SETS_DEFAULT_NAME_USED = 'SQRL_TEST_SETS__DEFAULT_NAME_USED'
30
-
31
37
  SQRL_CONNECTIONS_DEFAULT_NAME_USED = 'SQRL_CONNECTIONS__DEFAULT_NAME_USED'
32
38
 
33
- SQRL_DUCKDB_VENV_DB_FILE_PATH = 'SQRL_DUCKDB_VENV__DB_FILE_PATH'
39
+ SQRL_VDL_CATALOG_DB_PATH = 'SQRL_VDL__CATALOG_DB_PATH'
40
+ SQRL_VDL_DATA_PATH = 'SQRL_VDL__DATA_PATH'
41
+
42
+ SQRL_STUDIO_BASE_URL = 'SQRL_STUDIO__BASE_URL'
43
+
44
+ SQRL_LOGGING_LEVEL = 'SQRL_LOGGING__LEVEL'
45
+ SQRL_LOGGING_FORMAT = 'SQRL_LOGGING__FORMAT'
46
+ SQRL_LOGGING_TO_FILE = 'SQRL_LOGGING__TO_FILE'
47
+ SQRL_LOGGING_FILE_SIZE_MB = 'SQRL_LOGGING__FILE_SIZE_MB'
48
+ SQRL_LOGGING_FILE_BACKUP_COUNT = 'SQRL_LOGGING__FILE_BACKUP_COUNT'
34
49
 
35
50
  # Folder/File names
36
51
  PACKAGE_DATA_FOLDER = '_package_data'
@@ -50,7 +65,9 @@ GITIGNORE_FILE = '.gitignore'
50
65
  LOGS_FOLDER = 'logs'
51
66
  LOGS_FILE = 'squirrels.log'
52
67
 
53
- DATABASE_FOLDER = 'assets'
68
+ RESOURCES_FOLDER = 'resources'
69
+ PUBLIC_FOLDER = 'public'
70
+ LOGOS_FOLDER = 'logos'
54
71
  PACKAGES_FOLDER = 'sqrl_packages'
55
72
 
56
73
  MACROS_FOLDER = 'macros'
@@ -69,7 +86,6 @@ DASHBOARDS_FOLDER = 'dashboards'
69
86
  DASHBOARD_FILE_STEM = 'dashboard_example'
70
87
 
71
88
  PYCONFIGS_FOLDER = 'pyconfigs'
72
- AUTH_FILE = 'auth.py'
73
89
  CONNECTIONS_FILE = 'connections.py'
74
90
  CONTEXT_FILE = 'context.py'
75
91
  PARAMETERS_FILE = 'parameters.py'
@@ -77,17 +93,17 @@ USER_FILE = 'user.py'
77
93
  ADMIN_USERNAME = 'admin'
78
94
 
79
95
  TARGET_FOLDER = 'target'
80
- DB_FILE = 'auth.sqlite'
81
96
  COMPILE_FOLDER = 'compile'
82
- DUCKDB_VENV_FILE = 'venv.duckdb'
97
+ COMPILE_BUILDTIME_FOLDER = 'buildtime'
98
+ COMPILE_RUNTIME_FOLDER = 'runtime'
99
+ DB_FILE = 'auth.sqlite'
83
100
 
84
101
  SEEDS_FOLDER = 'seeds'
85
102
  SEED_CATEGORY_FILE_STEM = 'seed_categories'
86
103
  SEED_SUBCATEGORY_FILE_STEM = 'seed_subcategories'
87
104
 
88
- # Selection cfg sections
89
- USER_ATTRIBUTES_SECTION = 'user_attributes'
90
- PARAMETERS_SECTION = 'parameters'
105
+ # Test sets
106
+ DEFAULT_TEST_SET_NAME = 'default'
91
107
 
92
108
  # Init Command Choices
93
109
  SQL_FILE_TYPE = 'sql'
@@ -103,6 +119,7 @@ CONF_FORMAT_CHOICES2 = [(PYTHON_FORMAT2, PYTHON_FORMAT), YML_FORMAT]
103
119
 
104
120
  EXPENSES_DB = 'expenses.db'
105
121
  WEATHER_DB = 'weather.db'
122
+ GOOGLE_LOGO = 'google.ico'
106
123
 
107
124
  # Dashboard formats
108
125
  PNG = "png"
@@ -114,3 +131,11 @@ MAIN_FUNC = "main"
114
131
  # Regex
115
132
  DATE_REGEX = r"^\d{4}\-\d{2}\-\d{2}$"
116
133
  COLOR_REGEX = r"^#[0-9a-fA-F]{6}$"
134
+
135
+ # OAuth2
136
+ SUPPORTED_SCOPES = ['read']
137
+ SUPPORTED_GRANT_TYPES = ['authorization_code', 'refresh_token']
138
+ SUPPORTED_RESPONSE_TYPES = ['code']
139
+
140
+ # Miscellaneous
141
+ LATEST_API_VERSION_MOUNT_PATH = "/api/0"