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.
- squirrels/__init__.py +10 -16
- squirrels/_api_server.py +234 -80
- squirrels/_authenticator.py +84 -0
- squirrels/_command_line.py +60 -72
- squirrels/_connection_set.py +96 -0
- squirrels/_constants.py +114 -33
- squirrels/_environcfg.py +77 -0
- squirrels/_initializer.py +126 -67
- squirrels/_manifest.py +195 -168
- squirrels/_models.py +495 -0
- squirrels/_package_loader.py +26 -0
- squirrels/_parameter_configs.py +401 -0
- squirrels/_parameter_sets.py +188 -0
- squirrels/_py_module.py +60 -0
- squirrels/_timer.py +36 -0
- squirrels/_utils.py +81 -49
- squirrels/_version.py +2 -2
- squirrels/arguments/init_time_args.py +32 -0
- squirrels/arguments/run_time_args.py +82 -0
- squirrels/data_sources.py +380 -155
- squirrels/dateutils.py +86 -57
- squirrels/package_data/base_project/Dockerfile +15 -0
- squirrels/package_data/base_project/connections.yml +7 -0
- squirrels/package_data/base_project/database/{sample_database.db → expenses.db} +0 -0
- squirrels/package_data/base_project/environcfg.yml +29 -0
- squirrels/package_data/base_project/ignores/.dockerignore +8 -0
- squirrels/package_data/base_project/ignores/.gitignore +7 -0
- squirrels/package_data/base_project/models/dbviews/database_view1.py +36 -0
- squirrels/package_data/base_project/models/dbviews/database_view1.sql +15 -0
- squirrels/package_data/base_project/models/federates/dataset_example.py +20 -0
- squirrels/package_data/base_project/models/federates/dataset_example.sql +3 -0
- squirrels/package_data/base_project/parameters.yml +109 -0
- squirrels/package_data/base_project/pyconfigs/auth.py +47 -0
- squirrels/package_data/base_project/pyconfigs/connections.py +28 -0
- squirrels/package_data/base_project/pyconfigs/context.py +45 -0
- squirrels/package_data/base_project/pyconfigs/parameters.py +55 -0
- squirrels/package_data/base_project/seeds/mocks/category.csv +3 -0
- squirrels/package_data/base_project/seeds/mocks/max_filter.csv +2 -0
- squirrels/package_data/base_project/seeds/mocks/subcategory.csv +6 -0
- squirrels/package_data/base_project/squirrels.yml.j2 +57 -0
- squirrels/package_data/base_project/tmp/.gitignore +2 -0
- squirrels/package_data/static/script.js +159 -63
- squirrels/package_data/static/style.css +79 -15
- squirrels/package_data/static/widgets.js +133 -0
- squirrels/package_data/templates/index.html +65 -23
- squirrels/package_data/templates/index2.html +22 -0
- squirrels/parameter_options.py +216 -119
- squirrels/parameters.py +407 -478
- squirrels/user_base.py +58 -0
- squirrels-0.2.0.dev0.dist-info/METADATA +126 -0
- squirrels-0.2.0.dev0.dist-info/RECORD +56 -0
- {squirrels-0.1.1.post1.dist-info → squirrels-0.2.0.dev0.dist-info}/WHEEL +1 -2
- squirrels-0.2.0.dev0.dist-info/entry_points.txt +3 -0
- squirrels/_credentials_manager.py +0 -87
- squirrels/_module_loader.py +0 -37
- squirrels/_parameter_set.py +0 -151
- squirrels/_renderer.py +0 -286
- squirrels/_timed_imports.py +0 -37
- squirrels/connection_set.py +0 -126
- squirrels/package_data/base_project/.gitignore +0 -4
- squirrels/package_data/base_project/connections.py +0 -20
- squirrels/package_data/base_project/datasets/sample_dataset/context.py +0 -22
- squirrels/package_data/base_project/datasets/sample_dataset/database_view1.py +0 -29
- squirrels/package_data/base_project/datasets/sample_dataset/database_view1.sql.j2 +0 -12
- squirrels/package_data/base_project/datasets/sample_dataset/final_view.py +0 -11
- squirrels/package_data/base_project/datasets/sample_dataset/final_view.sql.j2 +0 -3
- squirrels/package_data/base_project/datasets/sample_dataset/parameters.py +0 -47
- squirrels/package_data/base_project/datasets/sample_dataset/selections.cfg +0 -9
- squirrels/package_data/base_project/squirrels.yaml +0 -22
- squirrels-0.1.1.post1.dist-info/METADATA +0 -67
- squirrels-0.1.1.post1.dist-info/RECORD +0 -40
- squirrels-0.1.1.post1.dist-info/entry_points.txt +0 -2
- squirrels-0.1.1.post1.dist-info/top_level.txt +0 -1
- {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
|
|
4
|
-
from squirrels._version import major_version
|
|
4
|
+
from . import _constants as c, _utils as u
|
|
5
5
|
|
|
6
|
-
base_proj_dir =
|
|
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 =
|
|
24
|
+
src_path = u.join_paths(base_proj_dir, src_folder, filepath)
|
|
26
25
|
shutil.copy(src_path, filepath)
|
|
27
26
|
|
|
28
|
-
def
|
|
29
|
-
self._copy_file(
|
|
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
|
|
32
|
-
self._copy_file(
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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 = [
|
|
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(
|
|
48
|
-
|
|
49
|
-
|
|
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(
|
|
54
|
+
if answers.get(CORE, False):
|
|
54
55
|
conditional_questions = [
|
|
55
|
-
inquirer.List(
|
|
56
|
-
message="
|
|
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(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
inquirer.
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
2
|
-
from
|
|
3
|
-
from
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
4
|
import yaml
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
self._parms = parms
|
|
14
|
-
|
|
11
|
+
@dataclass
|
|
12
|
+
class ManifestComponentConfig:
|
|
15
13
|
@classmethod
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
|
28
|
-
return self.
|
|
33
|
+
def get_name(self) -> str:
|
|
34
|
+
return str(self.data[c.PROJECT_NAME_KEY])
|
|
29
35
|
|
|
30
|
-
def
|
|
31
|
-
return self.
|
|
36
|
+
def get_label(self) -> str:
|
|
37
|
+
return str(self.data.get(c.PROJECT_LABEL_KEY, self.get_name()))
|
|
32
38
|
|
|
33
|
-
def
|
|
34
|
-
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def
|
|
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
|
-
|
|
155
|
+
scope = DatasetScope[str(scope_raw).upper()] if scope_raw is not None else DatasetScope.PUBLIC
|
|
68
156
|
except KeyError as e:
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
+
proj_vars = ProjectVarsConfig(kwargs[c.PROJ_VARS_KEY])
|
|
74
183
|
except KeyError as e:
|
|
75
|
-
raise ConfigurationError(f'
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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)
|