squirrels 0.1.1.post1__py3-none-any.whl → 0.2.0.dev0__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 (74) hide show
  1. squirrels/__init__.py +10 -16
  2. squirrels/_api_server.py +234 -80
  3. squirrels/_authenticator.py +84 -0
  4. squirrels/_command_line.py +60 -72
  5. squirrels/_connection_set.py +96 -0
  6. squirrels/_constants.py +114 -33
  7. squirrels/_environcfg.py +77 -0
  8. squirrels/_initializer.py +126 -67
  9. squirrels/_manifest.py +195 -168
  10. squirrels/_models.py +495 -0
  11. squirrels/_package_loader.py +26 -0
  12. squirrels/_parameter_configs.py +401 -0
  13. squirrels/_parameter_sets.py +188 -0
  14. squirrels/_py_module.py +60 -0
  15. squirrels/_timer.py +36 -0
  16. squirrels/_utils.py +81 -49
  17. squirrels/_version.py +2 -2
  18. squirrels/arguments/init_time_args.py +32 -0
  19. squirrels/arguments/run_time_args.py +82 -0
  20. squirrels/data_sources.py +380 -155
  21. squirrels/dateutils.py +86 -57
  22. squirrels/package_data/base_project/Dockerfile +15 -0
  23. squirrels/package_data/base_project/connections.yml +7 -0
  24. squirrels/package_data/base_project/database/{sample_database.db → expenses.db} +0 -0
  25. squirrels/package_data/base_project/environcfg.yml +29 -0
  26. squirrels/package_data/base_project/ignores/.dockerignore +8 -0
  27. squirrels/package_data/base_project/ignores/.gitignore +7 -0
  28. squirrels/package_data/base_project/models/dbviews/database_view1.py +36 -0
  29. squirrels/package_data/base_project/models/dbviews/database_view1.sql +15 -0
  30. squirrels/package_data/base_project/models/federates/dataset_example.py +20 -0
  31. squirrels/package_data/base_project/models/federates/dataset_example.sql +3 -0
  32. squirrels/package_data/base_project/parameters.yml +109 -0
  33. squirrels/package_data/base_project/pyconfigs/auth.py +47 -0
  34. squirrels/package_data/base_project/pyconfigs/connections.py +28 -0
  35. squirrels/package_data/base_project/pyconfigs/context.py +45 -0
  36. squirrels/package_data/base_project/pyconfigs/parameters.py +55 -0
  37. squirrels/package_data/base_project/seeds/mocks/category.csv +3 -0
  38. squirrels/package_data/base_project/seeds/mocks/max_filter.csv +2 -0
  39. squirrels/package_data/base_project/seeds/mocks/subcategory.csv +6 -0
  40. squirrels/package_data/base_project/squirrels.yml.j2 +57 -0
  41. squirrels/package_data/base_project/tmp/.gitignore +2 -0
  42. squirrels/package_data/static/script.js +159 -63
  43. squirrels/package_data/static/style.css +79 -15
  44. squirrels/package_data/static/widgets.js +133 -0
  45. squirrels/package_data/templates/index.html +65 -23
  46. squirrels/package_data/templates/index2.html +22 -0
  47. squirrels/parameter_options.py +216 -119
  48. squirrels/parameters.py +407 -478
  49. squirrels/user_base.py +58 -0
  50. squirrels-0.2.0.dev0.dist-info/METADATA +126 -0
  51. squirrels-0.2.0.dev0.dist-info/RECORD +56 -0
  52. {squirrels-0.1.1.post1.dist-info → squirrels-0.2.0.dev0.dist-info}/WHEEL +1 -2
  53. squirrels-0.2.0.dev0.dist-info/entry_points.txt +3 -0
  54. squirrels/_credentials_manager.py +0 -87
  55. squirrels/_module_loader.py +0 -37
  56. squirrels/_parameter_set.py +0 -151
  57. squirrels/_renderer.py +0 -286
  58. squirrels/_timed_imports.py +0 -37
  59. squirrels/connection_set.py +0 -126
  60. squirrels/package_data/base_project/.gitignore +0 -4
  61. squirrels/package_data/base_project/connections.py +0 -20
  62. squirrels/package_data/base_project/datasets/sample_dataset/context.py +0 -22
  63. squirrels/package_data/base_project/datasets/sample_dataset/database_view1.py +0 -29
  64. squirrels/package_data/base_project/datasets/sample_dataset/database_view1.sql.j2 +0 -12
  65. squirrels/package_data/base_project/datasets/sample_dataset/final_view.py +0 -11
  66. squirrels/package_data/base_project/datasets/sample_dataset/final_view.sql.j2 +0 -3
  67. squirrels/package_data/base_project/datasets/sample_dataset/parameters.py +0 -47
  68. squirrels/package_data/base_project/datasets/sample_dataset/selections.cfg +0 -9
  69. squirrels/package_data/base_project/squirrels.yaml +0 -22
  70. squirrels-0.1.1.post1.dist-info/METADATA +0 -67
  71. squirrels-0.1.1.post1.dist-info/RECORD +0 -40
  72. squirrels-0.1.1.post1.dist-info/entry_points.txt +0 -2
  73. squirrels-0.1.1.post1.dist-info/top_level.txt +0 -1
  74. {squirrels-0.1.1.post1.dist-info → squirrels-0.2.0.dev0.dist-info}/LICENSE +0 -0
squirrels/_initializer.py CHANGED
@@ -1,10 +1,9 @@
1
+ from typing import Optional
1
2
  import inquirer, os, shutil
2
3
 
3
- from squirrels import _constants as c, _utils
4
- from squirrels._version import major_version
4
+ from . import _constants as c, _utils as u
5
5
 
6
- base_proj_dir = _utils.join_paths(os.path.dirname(__file__), 'package_data', 'base_project')
7
- dataset_dir = _utils.join_paths('datasets', 'sample_dataset')
6
+ base_proj_dir = u.join_paths(os.path.dirname(__file__), c.PACKAGE_DATA_FOLDER, c.BASE_PROJECT_FOLDER)
8
7
 
9
8
 
10
9
  class Initializer:
@@ -17,94 +16,154 @@ class Initializer:
17
16
  return True
18
17
  return False
19
18
 
20
- def _copy_file(self, filepath: str):
19
+ def _copy_file(self, filepath: str, *, src_folder: str = ""):
21
20
  if not self._path_exists(filepath):
22
21
  dest_dir = os.path.dirname(filepath)
23
- if dest_dir != '':
22
+ if dest_dir != "":
24
23
  os.makedirs(dest_dir, exist_ok=True)
25
- src_path = _utils.join_paths(base_proj_dir, filepath)
24
+ src_path = u.join_paths(base_proj_dir, src_folder, filepath)
26
25
  shutil.copy(src_path, filepath)
27
26
 
28
- def _copy_dataset_file(self, filepath: str):
29
- self._copy_file(_utils.join_paths(dataset_dir, filepath))
27
+ def _copy_dbview_file(self, filepath: str):
28
+ self._copy_file(u.join_paths(c.MODELS_FOLDER, c.DBVIEWS_FOLDER, filepath))
30
29
 
31
- def _copy_database_file(self, filepath: str):
32
- self._copy_file(_utils.join_paths('database', filepath))
30
+ def _copy_federate_file(self, filepath: str):
31
+ self._copy_file(u.join_paths(c.MODELS_FOLDER, c.FEDERATES_FOLDER, filepath))
33
32
 
34
- def _create_requirements_txt(self):
35
- filename = 'requirements.txt'
36
- if not self._path_exists(filename):
37
- next_major_version = int(major_version) + 1
38
- content = f'squirrels<{next_major_version}'
39
- with open(filename, 'w') as f:
40
- f.write(content)
33
+ def _copy_database_file(self, filepath: str):
34
+ self._copy_file(u.join_paths(c.DATABASE_FOLDER, filepath))
35
+
36
+ def _copy_pyconfigs_file(self, filepath: str):
37
+ self._copy_file(u.join_paths(c.PYCONFIG_FOLDER, filepath))
41
38
 
42
39
  def init_project(self, args):
43
- options = ['core', 'db_view', 'connections', 'context', 'selections_cfg', 'final_view', 'sample_db']
40
+ options = ["core", "connections", "parameters", "dbview", "federate", "auth", "sample_db"]
41
+ CORE, CONNECTIONS, PARAMETERS, DBVIEW, FEDERATE, AUTH, SAMPLE_DB = options
42
+ TMP_FOLDER = "tmp"
43
+ IGNORES_FOLDER = "ignores"
44
+
44
45
  answers = { x: getattr(args, x) for x in options }
45
46
  if not any(answers.values()):
46
47
  core_questions = [
47
- inquirer.Confirm('core',
48
- message="Include all core project files?",
49
- default=True)
48
+ inquirer.Confirm(CORE,
49
+ message="Include all core project files?",
50
+ default=True)
50
51
  ]
51
52
  answers = inquirer.prompt(core_questions)
52
53
 
53
- if answers.get('core', False):
54
+ if answers.get(CORE, False):
54
55
  conditional_questions = [
55
- inquirer.List('db_view',
56
- message="What's the file format for the database view?",
56
+ inquirer.List(CONNECTIONS,
57
+ message=f"How would you like to configure the database connections?" ,
58
+ choices=c.CONF_FORMAT_CHOICES),
59
+ inquirer.List(PARAMETERS,
60
+ message=f"How would you like to configure the parameters?" ,
61
+ choices=c.CONF_FORMAT_CHOICES2),
62
+ inquirer.List(DBVIEW,
63
+ message="What's the file format for the database view model?",
64
+ choices=c.FILE_TYPE_CHOICES),
65
+ inquirer.List(FEDERATE,
66
+ message="What's the file format for the federated model?",
57
67
  choices=c.FILE_TYPE_CHOICES),
58
68
  ]
59
69
  answers.update(inquirer.prompt(conditional_questions))
60
70
 
61
71
  remaining_questions = [
62
- inquirer.Confirm('connections',
63
- message=f"Do you want to add a '{c.CONNECTIONS_FILE}' file?" ,
64
- default=False),
65
- inquirer.Confirm('context',
66
- message=f"Do you want to add a '{c.CONTEXT_FILE}' file?" ,
67
- default=False),
68
- inquirer.Confirm('selections_cfg',
69
- message=f"Do you want to add a '{c.SELECTIONS_CFG_FILE}' file?" ,
70
- default=False),
71
- inquirer.List('final_view',
72
- message="What's the file format for the final view (if any)?",
73
- choices=['none'] + c.FILE_TYPE_CHOICES),
74
- inquirer.List('sample_db',
75
- message="What sample sqlite database do you wish to use (if any)?",
76
- choices=['none'] + c.DATABASE_CHOICES)
72
+ inquirer.Confirm(AUTH,
73
+ message=f"Do you want to add the '{c.AUTH_FILE}' file?" ,
74
+ default=False),
75
+ inquirer.List(SAMPLE_DB,
76
+ message="What sample sqlite database do you wish to use (if any)?",
77
+ choices=["none"] + c.DATABASE_CHOICES)
77
78
  ]
78
79
  answers.update(inquirer.prompt(remaining_questions))
80
+
81
+ if answers.get(CONNECTIONS) is None:
82
+ answers[CONNECTIONS] = c.YML_FORMAT
83
+ if answers.get(PARAMETERS) is None:
84
+ answers[PARAMETERS] = c.PYTHON_FORMAT
85
+ if answers.get(DBVIEW) is None:
86
+ answers[DBVIEW] = c.SQL_FILE_TYPE
87
+ if answers.get(FEDERATE) is None:
88
+ answers[FEDERATE] = c.SQL_FILE_TYPE
89
+
90
+ if answers.get(CORE, False):
91
+ connections_format = answers.get(CONNECTIONS)
92
+ connections_use_yaml = (connections_format == c.YML_FORMAT)
93
+ connections_use_py = (connections_format == c.PYTHON_FORMAT)
94
+
95
+ parameters_format = answers.get(PARAMETERS)
96
+ parameters_use_yaml = (parameters_format == c.YML_FORMAT)
97
+ parameters_use_py = (parameters_format == c.PYTHON_FORMAT)
79
98
 
80
- if answers.get('core', False):
81
- self._copy_file('.gitignore')
82
- self._copy_file(c.MANIFEST_FILE)
83
- self._create_requirements_txt()
84
- self._copy_dataset_file(c.PARAMETERS_FILE)
85
- if answers.get('db_view') == 'py':
86
- self._copy_dataset_file(c.DATABASE_VIEW_PY_FILE)
99
+ db_view_format = answers.get(DBVIEW)
100
+ if db_view_format == c.SQL_FILE_TYPE:
101
+ db_view_file = c.DATABASE_VIEW_SQL_FILE
102
+ elif db_view_format == c.PYTHON_FILE_TYPE:
103
+ db_view_file = c.DATABASE_VIEW_PY_FILE
87
104
  else:
88
- self._copy_dataset_file(c.DATABASE_VIEW_SQL_FILE)
89
-
90
- if answers.get('connections', False):
91
- self._copy_file(c.CONNECTIONS_FILE)
92
-
93
- if answers.get('context', False):
94
- self._copy_dataset_file(c.CONTEXT_FILE)
105
+ raise NotImplementedError(f"Database view format '{db_view_format}' not supported")
95
106
 
96
- if answers.get('selections_cfg', False):
97
- self._copy_dataset_file(c.SELECTIONS_CFG_FILE)
107
+ federate_format = answers.get(FEDERATE)
108
+ if federate_format == c.SQL_FILE_TYPE:
109
+ federate_file = c.FEDERATE_SQL_NAME
110
+ elif federate_format == c.PYTHON_FILE_TYPE:
111
+ federate_file = c.FEDERATE_PY_NAME
112
+ else:
113
+ raise NotImplementedError(f"Dataset format '{federate_format}' not supported")
114
+
115
+ def create_manifest_file():
116
+ def get_content(file_name: Optional[str]) -> str:
117
+ if file_name is None:
118
+ return ""
119
+
120
+ yaml_path = u.join_paths(base_proj_dir, file_name)
121
+ return u.read_file(yaml_path)
122
+
123
+ file_name_dict = {
124
+ "parameters": c.PARAMETERS_YML_FILE if parameters_use_yaml else None,
125
+ "connections": c.CONNECTIONS_YML_FILE if connections_use_yaml else None
126
+ }
127
+ substitutions = {key: get_content(val) for key, val in file_name_dict.items()}
128
+ substitutions["db_view_file"] = db_view_file
129
+ substitutions["final_view_file"] = federate_file
130
+
131
+ manifest_template = get_content(c.MANIFEST_JINJA_FILE)
132
+ manifest_content = u.render_string(manifest_template, substitutions)
133
+ output_path = u.join_paths(base_proj_dir, TMP_FOLDER, c.MANIFEST_FILE)
134
+ with open(u.join_paths(output_path), "w") as f:
135
+ f.write(manifest_content)
136
+
137
+ create_manifest_file()
138
+
139
+ self._copy_file(".gitignore", src_folder=IGNORES_FOLDER)
140
+ self._copy_file(c.MANIFEST_FILE, src_folder=TMP_FOLDER)
141
+
142
+ if connections_use_py:
143
+ self._copy_pyconfigs_file(c.CONNECTIONS_FILE)
144
+ elif not connections_use_yaml:
145
+ raise NotImplementedError(f"Format '{connections_format}' not supported for configuring database connections")
146
+
147
+ if parameters_use_py:
148
+ self._copy_pyconfigs_file(c.PARAMETERS_FILE)
149
+ elif not parameters_use_yaml:
150
+ raise NotImplementedError(f"Format '{parameters_format}' not supported for configuring widget parameters")
151
+
152
+ self._copy_pyconfigs_file(c.CONTEXT_FILE)
153
+ self._copy_file(c.ENVIRON_CONFIG_FILE)
154
+
155
+ self._copy_dbview_file(db_view_file)
156
+ self._copy_federate_file(federate_file)
98
157
 
99
- final_view_format = answers.get('final_view')
100
- if final_view_format == 'py':
101
- self._copy_dataset_file(c.FINAL_VIEW_PY_NAME)
102
- elif final_view_format == 'sql':
103
- self._copy_dataset_file(c.FINAL_VIEW_SQL_NAME)
104
-
105
- sample_db = answers.get('sample_db')
106
- if sample_db == 'sample_database':
107
- self._copy_database_file('sample_database.db')
108
- elif sample_db == 'seattle_weather':
109
- self._copy_database_file('seattle_weather.db')
158
+ if answers.get(AUTH, False):
159
+ self._copy_pyconfigs_file(c.AUTH_FILE)
160
+
161
+ sample_db = answers.get(SAMPLE_DB)
162
+ if sample_db is not None and sample_db != "none":
163
+ if sample_db == c.EXPENSES_DB_NAME:
164
+ self._copy_database_file(c.EXPENSES_DB_NAME+".db")
165
+ elif sample_db == c.WEATHER_DB_NAME:
166
+ self._copy_database_file(c.WEATHER_DB_NAME+".db")
167
+ else:
168
+ raise NotImplementedError(f"No database found called '{sample_db}'")
110
169
 
squirrels/_manifest.py CHANGED
@@ -1,187 +1,214 @@
1
- from typing import List, Dict, Any, Optional, Union
2
- from pathlib import Path
3
- from sqlalchemy import Engine, create_engine
1
+ from typing import Optional
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
4
  import yaml
5
5
 
6
- from squirrels import _constants as c, _utils
7
- from squirrels._credentials_manager import Credential, squirrels_config_io
8
- from squirrels._utils import ConfigurationError, InvalidInputError
6
+ from . import _constants as c, _utils as u
7
+ from ._environcfg import EnvironConfigIO
8
+ from ._timer import timer, time
9
9
 
10
10
 
11
- class Manifest:
12
- def __init__(self, parms: Dict) -> None:
13
- self._parms = parms
14
-
11
+ @dataclass
12
+ class ManifestComponentConfig:
15
13
  @classmethod
16
- def from_yaml_str(cls, parms_str: str):
17
- parms = yaml.safe_load(parms_str)
18
- return cls(parms)
14
+ def _validate_required(cls, data: dict, required_keys: list[str], section: str):
15
+ for key in required_keys:
16
+ if key not in data:
17
+ raise u.ConfigurationError(f'In {c.MANIFEST_FILE}, required field missing in {section}: {key}')
19
18
 
20
- @classmethod
21
- def from_file(cls, manifest_path: str):
22
- with open(manifest_path, 'r') as f:
23
- parms_str = f.read()
19
+
20
+ @dataclass
21
+ class ProjectVarsConfig(ManifestComponentConfig):
22
+ data: dict
23
+
24
+ def __post_init__(self):
25
+ required_keys = [c.PROJECT_NAME_KEY, c.MAJOR_VERSION_KEY, c.MINOR_VERSION_KEY]
26
+ self._validate_required(self.data, required_keys, c.PROJ_VARS_KEY)
24
27
 
25
- return Manifest.from_yaml_str(parms_str)
28
+ integer_keys = [c.MAJOR_VERSION_KEY, c.MINOR_VERSION_KEY]
29
+ for key in integer_keys:
30
+ if key in self.data and not isinstance(self.data[key], int):
31
+ raise u.ConfigurationError(f'Project variable "{key}" must be an integer')
26
32
 
27
- def get_proj_vars(self) -> Dict[str, Any]:
28
- return self._parms.get(c.PROJ_VARS_KEY, dict())
33
+ def get_name(self) -> str:
34
+ return str(self.data[c.PROJECT_NAME_KEY])
29
35
 
30
- def get_modules(self) -> List[str]:
31
- return self._parms.get(c.MODULES_KEY, list())
36
+ def get_label(self) -> str:
37
+ return str(self.data.get(c.PROJECT_LABEL_KEY, self.get_name()))
32
38
 
33
- def _get_required_field(self, key: str) -> Any:
34
- try:
35
- return self._parms[key]
36
- except KeyError as e:
37
- raise ConfigurationError(f'Field "{key}" not found in squirrels.yaml') from e
39
+ def get_major_version(self) -> int:
40
+ return self.data[c.MAJOR_VERSION_KEY]
38
41
 
39
- def get_base_path(self) -> str:
40
- project_vars = self.get_proj_vars()
41
- try:
42
- product = project_vars[c.PRODUCT_KEY]
43
- major_version = project_vars[c.MAJOR_VERSION_KEY]
44
- except KeyError as e:
45
- raise ConfigurationError("Could not construct API endpoint as 'product' and 'major_version'" +
46
- "were not specified in project variables") from e
47
- base_path = f"/{product}/v{major_version}"
48
- return base_path
49
-
50
- def get_db_connections(self, test_creds: Dict[str, Credential] = None) -> Dict[str, Engine]:
51
- configs: Dict[str, Dict[str, str]] = self._parms.get(c.DB_CONNECTIONS_KEY, {})
52
- output = {}
53
- for key, config in configs.items():
54
- cred_key = config.get(c.DB_CREDENTIALS_KEY)
55
- if cred_key is None:
56
- cred = Credential("", "")
57
- elif test_creds is not None:
58
- cred = test_creds[cred_key]
59
- else:
60
- cred = squirrels_config_io.get_credential(cred_key)
61
- url = config[c.URL_KEY].replace("${username}", cred.username).replace("${password}", cred.password)
62
- output[key] = create_engine(url)
63
- return output
64
-
65
- def _get_dataset_parms(self, dataset: str) -> Dict[str, Any]:
42
+ def get_minor_version(self) -> int:
43
+ return self.data[c.MINOR_VERSION_KEY]
44
+
45
+
46
+ @dataclass
47
+ class PackageConfig(ManifestComponentConfig):
48
+ git_url: str
49
+ directory: str
50
+ revision: str
51
+
52
+ @classmethod
53
+ def from_dict(cls, kwargs: dict):
54
+ cls._validate_required(kwargs, [c.PACKAGE_GIT_KEY, c.PACKAGE_REVISION_KEY], c.PACKAGES_KEY)
55
+ git_url = str(kwargs[c.PACKAGE_GIT_KEY])
56
+ directory_raw = kwargs.get(c.PACKAGE_DIRECTORY_KEY)
57
+ directory = git_url.split('/')[-1].removesuffix('.git') if directory_raw is None else str(directory_raw)
58
+ revision = str(kwargs[c.PACKAGE_REVISION_KEY])
59
+ return cls(git_url, directory, revision)
60
+
61
+
62
+ @dataclass
63
+ class DbConnConfig(ManifestComponentConfig):
64
+ connection_name: str
65
+ url: str
66
+
67
+ @classmethod
68
+ def from_dict(cls, kwargs: dict):
69
+ cls._validate_required(kwargs, [c.DB_CONN_NAME_KEY, c.DB_CONN_URL_KEY], c.DB_CONNECTIONS_KEY)
70
+ connection_name = str(kwargs[c.DB_CONN_NAME_KEY])
71
+ credential_key = kwargs.get(c.CREDENTIALS_KEY)
72
+ username, password = EnvironConfigIO.obj.get_credential(credential_key)
73
+ url = str(kwargs[c.DB_CONN_URL_KEY]).format(username=username, password=password)
74
+ return cls(connection_name, url)
75
+
76
+
77
+ @dataclass
78
+ class ParametersConfig(ManifestComponentConfig):
79
+ name: str
80
+ type: str
81
+ factory: str
82
+ arguments: dict
83
+
84
+ @classmethod
85
+ def from_dict(cls, kwargs: dict):
86
+ all_keys = [c.PARAMETER_NAME_KEY, c.PARAMETER_TYPE_KEY, c.PARAMETER_FACTORY_KEY, c.PARAMETER_ARGS_KEY]
87
+ cls._validate_required(kwargs, all_keys, c.PARAMETERS_KEY)
88
+ args = {key: kwargs[key] for key in all_keys}
89
+ return cls(**args)
90
+
91
+
92
+ @dataclass
93
+ class TestSetsConfig(ManifestComponentConfig):
94
+ name: str
95
+ user_attributes: dict
96
+ parameters: dict
97
+
98
+ @classmethod
99
+ def from_dict(cls, kwargs: dict):
100
+ cls._validate_required(kwargs, [c.TEST_SET_NAME_KEY], c.TEST_SETS_KEY)
101
+ name = str(kwargs[c.TEST_SET_NAME_KEY])
102
+ user_attributes = kwargs.get(c.TEST_SET_USER_ATTR_KEY, {})
103
+ parameters = kwargs.get(c.TEST_SET_PARAMETERS_KEY, {})
104
+ return cls(name, user_attributes, parameters)
105
+
106
+
107
+ @dataclass
108
+ class DbviewConfig(ManifestComponentConfig):
109
+ name: str
110
+ connection_name: Optional[str]
111
+
112
+ @classmethod
113
+ def from_dict(cls, kwargs: dict):
114
+ cls._validate_required(kwargs, [c.DBVIEW_NAME_KEY], c.DBVIEWS_KEY)
115
+ name = str(kwargs[c.DBVIEW_NAME_KEY])
116
+ connection_name = str(kwargs.get(c.DBVIEW_CONN_KEY))
117
+ return cls(name, connection_name)
118
+
119
+
120
+ @dataclass
121
+ class FederateConfig(ManifestComponentConfig):
122
+ name: str
123
+ materialized: Optional[str]
124
+
125
+ @classmethod
126
+ def from_dict(cls, kwargs: dict):
127
+ cls._validate_required(kwargs, [c.FEDERATE_NAME_KEY], c.FEDERATES_KEY)
128
+ name = str(kwargs[c.FEDERATE_NAME_KEY])
129
+ materialized = str(kwargs.get(c.MATERIALIZED_KEY))
130
+ return cls(name, materialized)
131
+
132
+
133
+ class DatasetScope(Enum):
134
+ PUBLIC = 0
135
+ PROTECTED = 1
136
+ PRIVATE = 2
137
+
138
+ @dataclass
139
+ class DatasetsConfig(ManifestComponentConfig):
140
+ name: str
141
+ label: str
142
+ model: str
143
+ scope: DatasetScope
144
+ parameters: Optional[list[str]]
145
+ args: dict
146
+
147
+ @classmethod
148
+ def from_dict(cls, kwargs: dict):
149
+ cls._validate_required(kwargs, [c.DATASET_NAME_KEY], c.DATASETS_KEY)
150
+ name = str(kwargs[c.DATASET_NAME_KEY])
151
+ label = str(kwargs.get(c.DATASET_LABEL_KEY, name))
152
+ model = str(kwargs.get(c.DATASET_MODEL_KEY, name))
153
+ scope_raw = kwargs.get(c.DATASET_SCOPE_KEY)
66
154
  try:
67
- return self._get_required_field(c.DATASETS_KEY)[dataset]
155
+ scope = DatasetScope[str(scope_raw).upper()] if scope_raw is not None else DatasetScope.PUBLIC
68
156
  except KeyError as e:
69
- raise InvalidInputError(f'No such dataset named "{dataset}" exists') from e
157
+ scope_list = [scope.name.lower() for scope in DatasetScope]
158
+ raise u.ConfigurationError(f'Scope not found for dataset "{name}". Scope must be one of {scope_list}') from e
70
159
 
71
- def _get_required_field_from_dataset_parms(self, dataset: str, key: str):
160
+ parameters = kwargs.get(c.DATASET_PARAMETERS_KEY)
161
+ args = kwargs.get(c.DATASET_ARGS_KEY, {})
162
+ return cls(name, label, model, scope, parameters, args)
163
+
164
+
165
+ @dataclass
166
+ class _ManifestConfig:
167
+ project_variables: ProjectVarsConfig
168
+ packages: list[PackageConfig]
169
+ db_connections: list[DbConnConfig]
170
+ parameters: list[ParametersConfig]
171
+ selection_test_sets: dict[str, TestSetsConfig]
172
+ dbviews: dict[str, DbviewConfig]
173
+ federates: dict[str, FederateConfig]
174
+ datasets: dict[str, DatasetsConfig]
175
+ settings: dict
176
+
177
+ @classmethod
178
+ def from_dict(cls, kwargs: dict):
179
+ settings: dict = kwargs.get(c.SETTINGS_KEY, {})
180
+
72
181
  try:
73
- return self._get_dataset_parms(dataset)[key]
182
+ proj_vars = ProjectVarsConfig(kwargs[c.PROJ_VARS_KEY])
74
183
  except KeyError as e:
75
- raise ConfigurationError(f'The "{key}" field is not defined for dataset "{dataset}"') from e
76
-
77
- def _get_all_database_view_parms(self, dataset: str) -> Dict[str, Dict[str, str]]:
78
- return self._get_required_field_from_dataset_parms(dataset, c.DATABASE_VIEWS_KEY)
79
-
80
- def get_all_dataset_names(self) -> str:
81
- datasets: Dict[str, Any] = self._get_required_field(c.DATASETS_KEY)
82
- return list(datasets.keys())
83
-
84
- def get_dataset_folder(self, dataset: str) -> Path:
85
- return _utils.join_paths(c.DATASETS_FOLDER, dataset)
86
-
87
- def get_dataset_args(self, dataset: str) -> Dict[str, Any]:
88
- dataset_args = self._get_dataset_parms(dataset).get("args", {})
89
- full_args = {**self.get_proj_vars(), **dataset_args}
90
- return full_args
91
-
92
- def get_all_database_view_names(self, dataset: str) -> List[str]:
93
- all_database_views = self._get_all_database_view_parms(dataset)
94
- return list(all_database_views.keys())
95
-
96
- def get_database_view_file(self, dataset: str, database_view: str) -> Path:
97
- database_view_parms = self._get_all_database_view_parms(dataset)[database_view]
98
- if isinstance(database_view_parms, str):
99
- db_view_file = database_view_parms
100
- else:
101
- try:
102
- db_view_file = database_view_parms[c.FILE_KEY]
103
- except KeyError as e:
104
- raise ConfigurationError(f'The "{c.FILE_KEY}" field is not defined for "{database_view}" in dataset "{dataset}"') from e
105
- dataset_folder = self.get_dataset_folder(dataset)
106
- return _utils.join_paths(dataset_folder, db_view_file)
107
-
108
- def get_view_args(self, dataset: str, database_view: str = None) -> Dict[str, Any]:
109
- dataset_args = self.get_dataset_args(dataset)
110
- if database_view is None:
111
- view_parms: Dict[str, Any] = self._get_required_field_from_dataset_parms(dataset, c.FINAL_VIEW_KEY)
112
- else:
113
- view_parms: Dict[str, Any] = self._get_all_database_view_parms(dataset)[database_view]
114
- view_args: Dict[str, Any] = {} if isinstance(view_parms, str) else view_parms.get("args", {})
115
- full_args = {**dataset_args, **view_args}
116
- return full_args
117
-
118
- def get_database_view_db_connection(self, dataset: str, database_view: str) -> Optional[str]:
119
- database_view_parms = self._get_all_database_view_parms(dataset)[database_view]
120
- if isinstance(database_view_parms, str):
121
- db_connection = c.DEFAULT_DB_CONN
122
- else:
123
- db_connection = database_view_parms.get(c.DB_CONNECTION_KEY, c.DEFAULT_DB_CONN)
124
- return db_connection
125
-
126
- def get_dataset_label(self, dataset: str) -> str:
127
- return self._get_required_field_from_dataset_parms(dataset, c.DATASET_LABEL_KEY)
128
-
129
- def get_dataset_final_view_file(self, dataset: str) -> Union[str, Path]:
130
- final_view_parms: Dict[str, Any] = self._get_required_field_from_dataset_parms(dataset, c.FINAL_VIEW_KEY)
131
- if isinstance(final_view_parms, str):
132
- final_view_file = final_view_parms
133
- else:
134
- try:
135
- final_view_file = final_view_parms[c.FILE_KEY]
136
- except KeyError as e:
137
- raise ConfigurationError(f'The "{c.FILE_KEY}" field is not defined for the final view') from e
184
+ raise u.ConfigurationError(f'In {c.MANIFEST_FILE}, section for {c.PROJ_VARS_KEY} is required') from e
138
185
 
139
- database_views = self.get_all_database_view_names(dataset)
140
- if final_view_file in database_views:
141
- return final_view_file
142
- else:
143
- dataset_path = self.get_dataset_folder(dataset)
144
- return _utils.join_paths(dataset_path, final_view_file)
145
-
146
- def get_setting(self, key: str, default: Any) -> Any:
147
- settings: Dict[str, Any] = self._parms.get(c.SETTINGS_KEY, dict())
148
- return settings.get(key, default)
149
-
150
- def get_catalog(self, parameters_path: str, results_path: str) -> Any:
151
- """
152
- Gets the component of the catalog API response that's generated by this manifest
186
+ packages = [PackageConfig.from_dict(x) for x in kwargs.get(c.PACKAGES_KEY, [])]
187
+ db_conns = [DbConnConfig.from_dict(x) for x in kwargs.get(c.DB_CONNECTIONS_KEY, [])]
188
+ params = [ParametersConfig.from_dict(x) for x in kwargs.get(c.PARAMETERS_KEY, [])]
153
189
 
154
- Parameters:
155
- parameters_path: The path to the parameters API endpoint
156
- results_path: The path to the results API endpoint
157
-
158
- Returns:
159
- A JSON response for the catalog API
160
- """
161
- datasets_info = []
162
- for dataset in self.get_all_dataset_names():
163
- dataset_normalized = _utils.normalize_name_for_api(dataset)
164
- datasets_info.append({
165
- 'name': dataset,
166
- 'label': self.get_dataset_label(dataset),
167
- 'parameters_path': parameters_path.format(dataset=dataset_normalized),
168
- 'result_path': results_path.format(dataset=dataset_normalized),
169
- 'first_minor_version': 0
170
- })
190
+ test_sets = {x[c.TEST_SET_NAME_KEY]: TestSetsConfig.from_dict(x) for x in kwargs.get(c.TEST_SETS_KEY, [])}
191
+ default_test_set: str = settings.get(c.TEST_SET_DEFAULT_USED_SETTING, c.DEFAULT_TEST_SET_NAME)
192
+ test_sets.setdefault(default_test_set, TestSetsConfig.from_dict({c.TEST_SET_NAME_KEY: default_test_set}))
193
+
194
+ dbviews = {x[c.DBVIEW_NAME_KEY]: DbviewConfig.from_dict(x) for x in kwargs.get(c.DBVIEWS_KEY, [])}
195
+ federates = {x[c.FEDERATE_NAME_KEY]: FederateConfig.from_dict(x) for x in kwargs.get(c.FEDERATES_KEY, [])}
196
+ datasets = {x[c.DATASET_NAME_KEY]: DatasetsConfig.from_dict(x) for x in kwargs.get(c.DATASETS_KEY, [])}
197
+
198
+ return cls(proj_vars, packages, db_conns, params, test_sets, dbviews, federates, datasets, settings)
199
+
200
+
201
+ class ManifestIO:
202
+ obj: _ManifestConfig
203
+
204
+ @classmethod
205
+ def LoadFromFile(cls) -> None:
206
+ EnvironConfigIO.LoadFromFile()
171
207
 
172
- project_vars = self.get_proj_vars()
173
- return {
174
- 'response_version': 0,
175
- 'products': [{
176
- 'name': project_vars[c.PRODUCT_KEY],
177
- 'versions': [{
178
- 'major_version': project_vars[c.MAJOR_VERSION_KEY],
179
- 'latest_minor_version': project_vars[c.MINOR_VERSION_KEY],
180
- 'datasets': datasets_info
181
- }]
182
- }]
183
- }
184
-
185
-
186
- def _from_file():
187
- return Manifest.from_file(c.MANIFEST_FILE)
208
+ start = time.time()
209
+ raw_content = u.read_file(c.MANIFEST_FILE)
210
+ env_config = EnvironConfigIO.obj.get_all_env_vars()
211
+ content = u.render_string(raw_content, env_config)
212
+ proj_config = yaml.safe_load(content)
213
+ cls.obj = _ManifestConfig.from_dict(proj_config)
214
+ timer.add_activity_time(f"loading {c.MANIFEST_FILE} file", start)