squirrels 0.4.1__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of squirrels might be problematic. Click here for more details.

Files changed (125) hide show
  1. dateutils/__init__.py +6 -0
  2. dateutils/_enums.py +25 -0
  3. squirrels/dateutils.py → dateutils/_implementation.py +58 -111
  4. dateutils/types.py +6 -0
  5. squirrels/__init__.py +13 -11
  6. squirrels/_api_routes/__init__.py +5 -0
  7. squirrels/_api_routes/auth.py +271 -0
  8. squirrels/_api_routes/base.py +165 -0
  9. squirrels/_api_routes/dashboards.py +150 -0
  10. squirrels/_api_routes/data_management.py +145 -0
  11. squirrels/_api_routes/datasets.py +257 -0
  12. squirrels/_api_routes/oauth2.py +298 -0
  13. squirrels/_api_routes/project.py +252 -0
  14. squirrels/_api_server.py +256 -450
  15. squirrels/_arguments/__init__.py +0 -0
  16. squirrels/_arguments/init_time_args.py +108 -0
  17. squirrels/_arguments/run_time_args.py +147 -0
  18. squirrels/_auth.py +960 -0
  19. squirrels/_command_line.py +126 -45
  20. squirrels/_compile_prompts.py +147 -0
  21. squirrels/_connection_set.py +48 -26
  22. squirrels/_constants.py +68 -38
  23. squirrels/_dashboards.py +160 -0
  24. squirrels/_data_sources.py +570 -0
  25. squirrels/_dataset_types.py +84 -0
  26. squirrels/_exceptions.py +29 -0
  27. squirrels/_initializer.py +177 -80
  28. squirrels/_logging.py +115 -0
  29. squirrels/_manifest.py +208 -79
  30. squirrels/_model_builder.py +69 -0
  31. squirrels/_model_configs.py +74 -0
  32. squirrels/_model_queries.py +52 -0
  33. squirrels/_models.py +926 -367
  34. squirrels/_package_data/base_project/.env +42 -0
  35. squirrels/_package_data/base_project/.env.example +42 -0
  36. squirrels/_package_data/base_project/assets/expenses.db +0 -0
  37. squirrels/_package_data/base_project/connections.yml +16 -0
  38. squirrels/_package_data/base_project/dashboards/dashboard_example.py +34 -0
  39. squirrels/_package_data/base_project/dashboards/dashboard_example.yml +22 -0
  40. squirrels/{package_data → _package_data}/base_project/docker/.dockerignore +5 -2
  41. squirrels/{package_data → _package_data}/base_project/docker/Dockerfile +3 -3
  42. squirrels/{package_data → _package_data}/base_project/docker/compose.yml +1 -1
  43. squirrels/_package_data/base_project/duckdb_init.sql +10 -0
  44. squirrels/{package_data/base_project/.gitignore → _package_data/base_project/gitignore} +3 -2
  45. squirrels/_package_data/base_project/macros/macros_example.sql +17 -0
  46. squirrels/_package_data/base_project/models/builds/build_example.py +26 -0
  47. squirrels/_package_data/base_project/models/builds/build_example.sql +16 -0
  48. squirrels/_package_data/base_project/models/builds/build_example.yml +57 -0
  49. squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +12 -0
  50. squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +26 -0
  51. squirrels/_package_data/base_project/models/federates/federate_example.py +37 -0
  52. squirrels/_package_data/base_project/models/federates/federate_example.sql +19 -0
  53. squirrels/_package_data/base_project/models/federates/federate_example.yml +65 -0
  54. squirrels/_package_data/base_project/models/sources.yml +38 -0
  55. squirrels/{package_data → _package_data}/base_project/parameters.yml +56 -40
  56. squirrels/_package_data/base_project/pyconfigs/connections.py +14 -0
  57. squirrels/{package_data → _package_data}/base_project/pyconfigs/context.py +21 -40
  58. squirrels/_package_data/base_project/pyconfigs/parameters.py +141 -0
  59. squirrels/_package_data/base_project/pyconfigs/user.py +44 -0
  60. squirrels/_package_data/base_project/seeds/seed_categories.yml +15 -0
  61. squirrels/_package_data/base_project/seeds/seed_subcategories.csv +15 -0
  62. squirrels/_package_data/base_project/seeds/seed_subcategories.yml +21 -0
  63. squirrels/_package_data/base_project/squirrels.yml.j2 +61 -0
  64. squirrels/_package_data/templates/dataset_results.html +112 -0
  65. squirrels/_package_data/templates/oauth_login.html +271 -0
  66. squirrels/_package_data/templates/squirrels_studio.html +20 -0
  67. squirrels/_package_loader.py +8 -4
  68. squirrels/_parameter_configs.py +104 -103
  69. squirrels/_parameter_options.py +348 -0
  70. squirrels/_parameter_sets.py +57 -47
  71. squirrels/_parameters.py +1664 -0
  72. squirrels/_project.py +721 -0
  73. squirrels/_py_module.py +7 -5
  74. squirrels/_schemas/__init__.py +0 -0
  75. squirrels/_schemas/auth_models.py +167 -0
  76. squirrels/_schemas/query_param_models.py +75 -0
  77. squirrels/{_api_response_models.py → _schemas/response_models.py} +126 -47
  78. squirrels/_seeds.py +35 -16
  79. squirrels/_sources.py +110 -0
  80. squirrels/_utils.py +248 -73
  81. squirrels/_version.py +1 -1
  82. squirrels/arguments.py +7 -0
  83. squirrels/auth.py +4 -0
  84. squirrels/connections.py +3 -0
  85. squirrels/dashboards.py +2 -81
  86. squirrels/data_sources.py +14 -631
  87. squirrels/parameter_options.py +13 -348
  88. squirrels/parameters.py +14 -1266
  89. squirrels/types.py +16 -0
  90. squirrels-0.5.0.dist-info/METADATA +113 -0
  91. squirrels-0.5.0.dist-info/RECORD +97 -0
  92. {squirrels-0.4.1.dist-info → squirrels-0.5.0.dist-info}/WHEEL +1 -1
  93. squirrels-0.5.0.dist-info/entry_points.txt +3 -0
  94. {squirrels-0.4.1.dist-info → squirrels-0.5.0.dist-info/licenses}/LICENSE +1 -1
  95. squirrels/_authenticator.py +0 -85
  96. squirrels/_dashboards_io.py +0 -61
  97. squirrels/_environcfg.py +0 -84
  98. squirrels/arguments/init_time_args.py +0 -40
  99. squirrels/arguments/run_time_args.py +0 -208
  100. squirrels/package_data/assets/favicon.ico +0 -0
  101. squirrels/package_data/assets/index.css +0 -1
  102. squirrels/package_data/assets/index.js +0 -58
  103. squirrels/package_data/base_project/assets/expenses.db +0 -0
  104. squirrels/package_data/base_project/connections.yml +0 -7
  105. squirrels/package_data/base_project/dashboards/dashboard_example.py +0 -32
  106. squirrels/package_data/base_project/dashboards.yml +0 -10
  107. squirrels/package_data/base_project/env.yml +0 -29
  108. squirrels/package_data/base_project/models/dbviews/dbview_example.py +0 -47
  109. squirrels/package_data/base_project/models/dbviews/dbview_example.sql +0 -22
  110. squirrels/package_data/base_project/models/federates/federate_example.py +0 -21
  111. squirrels/package_data/base_project/models/federates/federate_example.sql +0 -3
  112. squirrels/package_data/base_project/pyconfigs/auth.py +0 -45
  113. squirrels/package_data/base_project/pyconfigs/connections.py +0 -19
  114. squirrels/package_data/base_project/pyconfigs/parameters.py +0 -95
  115. squirrels/package_data/base_project/seeds/seed_subcategories.csv +0 -15
  116. squirrels/package_data/base_project/squirrels.yml.j2 +0 -94
  117. squirrels/package_data/templates/index.html +0 -18
  118. squirrels/project.py +0 -378
  119. squirrels/user_base.py +0 -55
  120. squirrels-0.4.1.dist-info/METADATA +0 -117
  121. squirrels-0.4.1.dist-info/RECORD +0 -60
  122. squirrels-0.4.1.dist-info/entry_points.txt +0 -4
  123. /squirrels/{package_data → _package_data}/base_project/assets/weather.db +0 -0
  124. /squirrels/{package_data → _package_data}/base_project/seeds/seed_categories.csv +0 -0
  125. /squirrels/{package_data → _package_data}/base_project/tmp/.gitignore +0 -0
@@ -1,5 +1,6 @@
1
1
  from argparse import ArgumentParser, _SubParsersAction
2
- import sys, asyncio, traceback, io
2
+ from pathlib import Path
3
+ import sys, asyncio, traceback, io, subprocess
3
4
 
4
5
  sys.path.append('.')
5
6
 
@@ -7,101 +8,185 @@ from ._version import __version__
7
8
  from ._api_server import ApiServer
8
9
  from ._initializer import Initializer
9
10
  from ._package_loader import PackageLoaderIO
10
- from .project import SquirrelsProject
11
- from . import _constants as c
11
+ from ._project import SquirrelsProject
12
+ from . import _constants as c, _utils as u
13
+ from ._compile_prompts import prompt_compile_options
14
+
15
+
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 []
26
+ command = ['duckdb']
27
+ if ui:
28
+ command.append('-ui')
29
+ if init_args:
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
+
38
+ try:
39
+ subprocess.run(command, check=True)
40
+ except FileNotFoundError:
41
+ print("DuckDB CLI not found. Please install it from: https://duckdb.org/docs/installation/")
42
+ except subprocess.CalledProcessError:
43
+ pass # ignore errors that occured on duckdb shell commands
12
44
 
13
45
 
14
46
  def main():
15
47
  """
16
48
  Main entry point for the squirrels command line utilities.
17
49
  """
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))
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])
23
58
  parser.add_argument('-V', '--version', action='store_true', help='Show the version and exit')
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')
27
59
  subparsers = parser.add_subparsers(title='commands', dest='command')
28
60
 
29
61
  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))
62
+ subparser: ArgumentParser = subparsers.add_parser(cmd, description=help_text, help=help_text, add_help=False, parents=[parent_parser])
31
63
  return subparser
32
64
 
33
- init_parser = add_subparser(subparsers, c.INIT_CMD, 'Initialize a squirrels project')
34
- init_parser.add_argument('-o', '--overwrite', action='store_true', help="Overwrite files that already exist")
35
- init_parser.add_argument('--core', action='store_true', help='Include all core files')
65
+ init_parser = add_subparser(subparsers, c.INIT_CMD, 'Create a new squirrels project')
66
+
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')
36
70
  init_parser.add_argument('--connections', type=str, choices=c.CONF_FORMAT_CHOICES, help=f'Configure database connections as yaml (default) or python')
37
71
  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')
72
+ init_parser.add_argument('--build', type=str, choices=c.FILE_TYPE_CHOICES, help='Create build model as sql (default) or python file')
39
73
  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')
41
- init_parser.add_argument('--auth', action='store_true', help=f'Include the {c.AUTH_FILE} file')
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')
42
76
 
43
77
  def with_file_format_options(parser: ArgumentParser):
44
78
  help_text = "Create model as sql (default) or python file"
45
79
  parser.add_argument('--format', type=str, choices=c.FILE_TYPE_CHOICES, default=c.SQL_FILE_TYPE, help=help_text)
46
80
  return parser
47
81
 
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."
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."
49
83
  get_file_parser = add_subparser(subparsers, c.GET_FILE_CMD, get_file_help_text)
50
84
  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')
85
+ add_subparser(get_file_subparsers, c.DOTENV_FILE, f'Get sample {c.DOTENV_FILE} and {c.DOTENV_FILE}.example files')
86
+ add_subparser(get_file_subparsers, c.GITIGNORE_FILE, f'Get a sample {c.GITIGNORE_FILE} file')
52
87
  manifest_parser = add_subparser(get_file_subparsers, c.MANIFEST_FILE, f'Get a sample {c.MANIFEST_FILE} file')
53
88
  manifest_parser.add_argument("--no-connections", action='store_true', help=f'Exclude the connections section')
54
89
  manifest_parser.add_argument("--parameters", action='store_true', help=f'Include the parameters section')
55
90
  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')
91
+ add_subparser(get_file_subparsers, c.USER_FILE, f'Get a sample {c.USER_FILE} file')
57
92
  add_subparser(get_file_subparsers, c.CONNECTIONS_FILE, f'Get a sample {c.CONNECTIONS_FILE} file')
58
93
  add_subparser(get_file_subparsers, c.PARAMETERS_FILE, f'Get a sample {c.PARAMETERS_FILE} file')
59
94
  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'))
95
+ add_subparser(get_file_subparsers, c.MACROS_FILE, f'Get a sample {c.MACROS_FILE} file')
96
+ add_subparser(get_file_subparsers, c.SOURCES_FILE, f'Get a sample {c.SOURCES_FILE} file')
97
+ with_file_format_options(add_subparser(get_file_subparsers, c.BUILD_FILE_STEM, f'Get a sample build model file'))
98
+ add_subparser(get_file_subparsers, c.DBVIEW_FILE_STEM, f'Get a sample dbview model file')
61
99
  with_file_format_options(add_subparser(get_file_subparsers, c.FEDERATE_FILE_STEM, f'Get a sample federate model file'))
62
100
  add_subparser(get_file_subparsers, c.DASHBOARD_FILE_STEM, f'Get a sample dashboard file')
63
101
  add_subparser(get_file_subparsers, c.EXPENSES_DB, f'Get the sample SQLite database on expenses')
64
102
  add_subparser(get_file_subparsers, c.WEATHER_DB, f'Get the sample SQLite database on weather')
103
+ add_subparser(get_file_subparsers, c.SEED_CATEGORY_FILE_STEM, f'Get the sample seed files for categories lookup')
104
+ add_subparser(get_file_subparsers, c.SEED_SUBCATEGORY_FILE_STEM, f'Get the sample seed files for subcategories lookup')
65
105
 
66
- add_subparser(subparsers, c.DEPS_CMD, f'Load all packages specified in {c.MANIFEST_FILE} (from git)')
106
+ deps_parser = add_subparser(subparsers, c.DEPS_CMD, f'Load all packages specified in {c.MANIFEST_FILE} (from git)')
67
107
 
68
108
  compile_parser = add_subparser(subparsers, c.COMPILE_CMD, 'Create rendered SQL files in the folder "./target/compile"')
69
- compile_dataset_group = compile_parser.add_mutually_exclusive_group(required=True)
70
- compile_dataset_group.add_argument('-d', '--dataset', type=str, help="Select dataset to use for dataset traits. Is required, unless using --all-datasets")
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")
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')
72
114
  compile_test_set_group = compile_parser.add_mutually_exclusive_group(required=False)
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")
74
- compile_test_set_group.add_argument('-T', '--all-test-sets', action="store_true", help="Compile models for all selection test sets")
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")
76
- compile_parser.add_argument('-r', '--runquery', action='store_true', help='Runs all target models, and produce the results as csv files')
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')
121
+ build_parser.add_argument('-f', '--full-refresh', action='store_true', help='Drop all tables before building')
122
+ build_parser.add_argument('-s', '--select', type=str, help="Select one static model to build. If not specified, all models are built")
123
+
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')
77
126
 
78
127
  run_parser = add_subparser(subparsers, c.RUN_CMD, 'Run the API server')
128
+ run_parser.add_argument('--build', action='store_true', help='Build the VDL first (without full refresh) before running the API server')
79
129
  run_parser.add_argument('--no-cache', action='store_true', help='Do not cache any api results')
80
130
  run_parser.add_argument('--host', type=str, default='127.0.0.1', help="The host to run on")
81
131
  run_parser.add_argument('--port', type=int, default=4465, help="The port to run on")
82
132
 
83
133
  args, _ = parser.parse_known_args()
84
- project = SquirrelsProject(log_level=args.log_level, log_format=args.log_format, log_file=args.log_file)
85
134
 
86
135
  if args.version:
87
136
  print(__version__)
88
137
  elif args.command == c.INIT_CMD:
89
- Initializer(overwrite=args.overwrite).init_project(args)
138
+ Initializer(project_name=args.name, use_curr_dir=args.curr_dir).init_project(args)
90
139
  elif args.command == c.GET_FILE_CMD:
91
140
  Initializer().get_file(args)
92
- elif args.command == c.DEPS_CMD:
93
- PackageLoaderIO.load_packages(project._logger, project._manifest_cfg, reload=True)
94
- elif args.command in [c.RUN_CMD, c.COMPILE_CMD]:
141
+ elif args.command is None:
142
+ print(f'Command is missing. Enter "squirrels -h" for help.')
143
+ else:
144
+ project = SquirrelsProject(load_dotenv_globally=True, log_level=args.log_level, log_format=args.log_format, log_to_file=args.log_to_file)
95
145
  try:
96
- if args.command == c.RUN_CMD:
146
+ if args.command == c.DEPS_CMD:
147
+ PackageLoaderIO.load_packages(project._logger, project._manifest_cfg, reload=True)
148
+ elif args.command == c.BUILD_CMD:
149
+ task = project.build(full_refresh=args.full_refresh, select=args.select)
150
+ asyncio.run(task)
151
+ print()
152
+ elif args.command == c.DUCKDB_CMD:
153
+ _run_duckdb_cli(project, args.ui)
154
+ elif args.command == c.RUN_CMD:
155
+ if args.build:
156
+ task = project.build(full_refresh=True)
157
+ asyncio.run(task)
97
158
  server = ApiServer(args.no_cache, project)
98
159
  server.run(args)
99
160
  elif args.command == c.COMPILE_CMD:
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
- )
104
- asyncio.run(task)
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
187
+ else:
188
+ print(f'Error: No such command "{args.command}". Enter "squirrels -h" for help.')
189
+
105
190
  except KeyboardInterrupt:
106
191
  pass
107
192
  except Exception as e:
@@ -112,10 +197,6 @@ def main():
112
197
  project._logger.error(err_msg)
113
198
  finally:
114
199
  project.close()
115
- elif args.command is None:
116
- print(f'Command is missing. Enter "squirrels -h" for help.')
117
- else:
118
- print(f'Error: No such command "{args.command}". Enter "squirrels -h" for help.')
119
200
 
120
201
 
121
202
  if __name__ == '__main__':
@@ -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,11 +1,11 @@
1
- from dataclasses import dataclass
2
- from sqlalchemy import Engine, create_engine
3
- import time, pandas as pd
1
+ from typing import Any
2
+ from dataclasses import dataclass, field
3
+ from sqlalchemy import Engine
4
+ import time, polars as pl, duckdb
4
5
 
5
6
  from . import _utils as u, _constants as c, _py_module as pm
6
- from .arguments.init_time_args import ConnectionsArgs
7
- from ._environcfg import EnvironConfig
8
- from ._manifest import ManifestConfig
7
+ from ._arguments.init_time_args import ConnectionsArgs
8
+ from ._manifest import ManifestConfig, ConnectionProperties, ConnectionTypeEnum
9
9
 
10
10
 
11
11
  @dataclass
@@ -16,44 +16,63 @@ class ConnectionSet:
16
16
  Attributes:
17
17
  _engines: A dictionary of connection name to the corresponding sqlalchemy engine
18
18
  """
19
- _engines: dict[str, Engine]
19
+ _connections: dict[str, ConnectionProperties | Any] = field(default_factory=dict)
20
20
 
21
- def get_engines_as_dict(self):
22
- return self._engines.copy()
21
+ def get_connections_as_dict(self):
22
+ return self._connections.copy()
23
23
 
24
- def _get_engine(self, conn_name: str) -> Engine:
24
+ def get_connection(self, conn_name: str) -> ConnectionProperties | Any:
25
25
  try:
26
- connection_pool = self._engines[conn_name]
26
+ connection = self._connections[conn_name]
27
27
  except KeyError as e:
28
28
  raise u.ConfigurationError(f'Connection name "{conn_name}" was not configured') from e
29
- return connection_pool
29
+ return connection
30
30
 
31
- def run_sql_query_from_conn_name(self, query: str, conn_name: str, placeholders: dict = {}) -> pd.DataFrame:
32
- engine = self._get_engine(conn_name)
31
+ def run_sql_query_from_conn_name(self, query: str, conn_name: str, placeholders: dict = {}) -> pl.DataFrame:
32
+ conn = self.get_connection(conn_name)
33
33
  try:
34
- df = pd.read_sql(query, engine, params=placeholders)
34
+ if isinstance(conn, ConnectionProperties) and (conn.type == ConnectionTypeEnum.CONNECTORX or conn.type == ConnectionTypeEnum.ADBC):
35
+ if len(placeholders) > 0:
36
+ raise u.ConfigurationError(f"Connection '{conn_name}' is a ConnectorX or ADBC connection, which does not support placeholders")
37
+ df = pl.read_database_uri(query, conn.uri, engine=conn.type.value)
38
+ elif isinstance(conn, ConnectionProperties) and conn.type == ConnectionTypeEnum.SQLALCHEMY:
39
+ with conn.engine.connect() as connection:
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()
48
+ else:
49
+ df = pl.read_database(query, conn, execute_options={"parameters": placeholders}) # type: ignore
35
50
  return df
36
51
  except Exception as e:
37
52
  raise RuntimeError(e) from e
38
53
 
39
54
  def dispose(self) -> None:
40
55
  """
41
- Disposes of all the engines in this ConnectionSet
56
+ Disposes / closes all the connections in this ConnectionSet
42
57
  """
43
- for pool in self._engines.values():
44
- if isinstance(pool, Engine):
45
- pool.dispose()
58
+ for conn in self._connections.values():
59
+ if isinstance(conn, Engine):
60
+ conn.dispose()
61
+ elif isinstance(conn, ConnectionProperties):
62
+ if conn.type == ConnectionTypeEnum.SQLALCHEMY:
63
+ conn.engine.dispose()
64
+ elif hasattr(conn, 'close'):
65
+ conn.close()
46
66
 
47
67
 
48
68
  class ConnectionSetIO:
49
69
 
50
70
  @classmethod
51
- def load_conn_py_args(cls, logger: u.Logger, env_cfg: EnvironConfig, manifest_cfg: ManifestConfig) -> ConnectionsArgs:
71
+ def load_conn_py_args(cls, logger: u.Logger, base_path: str, env_vars: dict[str, str], manifest_cfg: ManifestConfig) -> ConnectionsArgs:
52
72
  start = time.time()
53
73
 
54
74
  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)
75
+ conn_args = ConnectionsArgs(base_path, proj_vars, env_vars)
57
76
 
58
77
  logger.log_activity_time("setting up arguments for connections.py", start)
59
78
  return conn_args
@@ -68,13 +87,16 @@ class ConnectionSetIO:
68
87
  A ConnectionSet with the DB connections from both squirrels.yml and connections.py
69
88
  """
70
89
  start = time.time()
71
- engines: dict[str, Engine] = {}
90
+ connections: dict[str, ConnectionProperties | Any] = {}
72
91
 
73
92
  for config in manifest_cfg.connections.values():
74
- engines[config.name] = create_engine(config.url)
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
+ )
75
96
 
76
- pm.run_pyconfig_main(base_path, c.CONNECTIONS_FILE, {"connections": engines, "sqrl": conn_args})
77
- conn_set = ConnectionSet(engines)
97
+ pm.run_pyconfig_main(base_path, c.CONNECTIONS_FILE, {"connections": connections, "sqrl": conn_args})
98
+
99
+ conn_set = ConnectionSet(connections)
78
100
 
79
101
  logger.log_activity_time("creating sqlalchemy engines", start)
80
102
  return conn_set