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

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

Potentially problematic release.


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

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