esgvoc 1.0.0__py3-none-any.whl → 1.1.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 esgvoc might be problematic. Click here for more details.
- esgvoc/__init__.py +1 -1
- esgvoc/api/__init__.py +0 -6
- esgvoc/api/data_descriptors/__init__.py +8 -0
- esgvoc/api/data_descriptors/archive.py +5 -0
- esgvoc/api/data_descriptors/citation_url.py +5 -0
- esgvoc/api/data_descriptors/experiment.py +2 -2
- esgvoc/api/data_descriptors/known_branded_variable.py +58 -5
- esgvoc/api/data_descriptors/member_id.py +9 -0
- esgvoc/api/data_descriptors/regex.py +5 -0
- esgvoc/api/data_descriptors/vertical_label.py +2 -2
- esgvoc/api/project_specs.py +48 -130
- esgvoc/api/projects.py +185 -66
- esgvoc/apps/drs/generator.py +103 -85
- esgvoc/apps/drs/validator.py +22 -38
- esgvoc/apps/jsg/json_schema_generator.py +255 -130
- esgvoc/apps/jsg/templates/template.jinja +249 -0
- esgvoc/apps/test_cv/README.md +214 -0
- esgvoc/apps/test_cv/cv_tester.py +1368 -0
- esgvoc/apps/test_cv/example_usage.py +216 -0
- esgvoc/apps/vr/__init__.py +12 -0
- esgvoc/apps/vr/build_variable_registry.py +71 -0
- esgvoc/apps/vr/example_usage.py +60 -0
- esgvoc/apps/vr/vr_app.py +333 -0
- esgvoc/cli/config.py +671 -86
- esgvoc/cli/drs.py +39 -21
- esgvoc/cli/main.py +2 -0
- esgvoc/cli/test_cv.py +257 -0
- esgvoc/core/constants.py +10 -7
- esgvoc/core/data_handler.py +24 -22
- esgvoc/core/db/connection.py +7 -0
- esgvoc/core/db/project_ingestion.py +34 -9
- esgvoc/core/db/universe_ingestion.py +1 -2
- esgvoc/core/service/configuration/setting.py +192 -21
- esgvoc/core/service/data_merger.py +1 -1
- esgvoc/core/service/state.py +18 -2
- {esgvoc-1.0.0.dist-info → esgvoc-1.1.1.dist-info}/METADATA +2 -3
- {esgvoc-1.0.0.dist-info → esgvoc-1.1.1.dist-info}/RECORD +41 -30
- esgvoc/apps/jsg/cmip6_template.json +0 -74
- esgvoc/apps/jsg/cmip6plus_template.json +0 -74
- /esgvoc/apps/{py.typed → test_cv/__init__.py} +0 -0
- {esgvoc-1.0.0.dist-info → esgvoc-1.1.1.dist-info}/WHEEL +0 -0
- {esgvoc-1.0.0.dist-info → esgvoc-1.1.1.dist-info}/entry_points.txt +0 -0
- {esgvoc-1.0.0.dist-info → esgvoc-1.1.1.dist-info}/licenses/LICENSE.txt +0 -0
esgvoc/apps/drs/validator.py
CHANGED
|
@@ -3,10 +3,7 @@ from typing import cast
|
|
|
3
3
|
import esgvoc.api.projects as projects
|
|
4
4
|
import esgvoc.apps.drs.constants as constants
|
|
5
5
|
from esgvoc.api.project_specs import (
|
|
6
|
-
DrsCollection,
|
|
7
|
-
DrsConstant,
|
|
8
6
|
DrsPart,
|
|
9
|
-
DrsPartKind,
|
|
10
7
|
DrsSpecification,
|
|
11
8
|
DrsType,
|
|
12
9
|
ProjectSpecs,
|
|
@@ -44,19 +41,12 @@ class DrsApplication:
|
|
|
44
41
|
project_specs: ProjectSpecs | None = projects.get_project(project_id)
|
|
45
42
|
if not project_specs:
|
|
46
43
|
raise EsgvocNotFoundError(f"unable to find project '{project_id}'")
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
self.file_name_specs: DrsSpecification = specs
|
|
54
|
-
"""The DRS file name specs of the project."""
|
|
55
|
-
case DrsType.DATASET_ID:
|
|
56
|
-
self.dataset_id_specs: DrsSpecification = specs
|
|
57
|
-
"""The DRS dataset id specs of the project."""
|
|
58
|
-
case _:
|
|
59
|
-
raise EsgvocDbError(f"unsupported DRS specs type '{specs.type}'")
|
|
44
|
+
self.directory_specs: DrsSpecification = project_specs.drs_specs[DrsType.DIRECTORY]
|
|
45
|
+
"""The DRS directory specs of the project."""
|
|
46
|
+
self.file_name_specs: DrsSpecification = project_specs.drs_specs[DrsType.FILE_NAME]
|
|
47
|
+
"""The DRS file name specs of the project."""
|
|
48
|
+
self.dataset_id_specs: DrsSpecification = project_specs.drs_specs[DrsType.DATASET_ID]
|
|
49
|
+
"""The DRS dataset id specs of the project."""
|
|
60
50
|
|
|
61
51
|
def _get_full_file_name_extension(self) -> str:
|
|
62
52
|
"""
|
|
@@ -229,21 +219,18 @@ class DrsValidator(DrsApplication):
|
|
|
229
219
|
return sorted(issues, key=lambda issue: issue.column if issue.column else 0)
|
|
230
220
|
|
|
231
221
|
def _validate_term(self, term: str, part: DrsPart) -> bool:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return part_casted.value != term
|
|
245
|
-
case _:
|
|
246
|
-
raise EsgvocDbError(f"unsupported DRS specs part type '{part.kind}'")
|
|
222
|
+
if part.source_collection_term is None:
|
|
223
|
+
matching_terms = projects.valid_term_in_collection(
|
|
224
|
+
term,
|
|
225
|
+
self.project_id,
|
|
226
|
+
part.source_collection)
|
|
227
|
+
if len(matching_terms) > 0:
|
|
228
|
+
return True
|
|
229
|
+
else:
|
|
230
|
+
return False
|
|
231
|
+
else:
|
|
232
|
+
return projects.valid_term(term, self.project_id, part.source_collection,
|
|
233
|
+
part.source_collection_term).validated
|
|
247
234
|
|
|
248
235
|
def _create_report(self,
|
|
249
236
|
type: DrsType,
|
|
@@ -268,13 +255,12 @@ class DrsValidator(DrsApplication):
|
|
|
268
255
|
matching_code_mapping = dict()
|
|
269
256
|
while part_index < part_max_index:
|
|
270
257
|
term = terms[term_index]
|
|
271
|
-
part = specs.parts[part_index]
|
|
258
|
+
part: DrsPart = specs.parts[part_index]
|
|
272
259
|
if self._validate_term(term, part):
|
|
273
260
|
term_index += 1
|
|
274
261
|
part_index += 1
|
|
275
262
|
matching_code_mapping[part.__str__()] = 0
|
|
276
|
-
elif part.
|
|
277
|
-
cast(DrsCollection, part).is_required: # noqa E127
|
|
263
|
+
elif part.is_required:
|
|
278
264
|
issue: ComplianceIssue = InvalidTerm(term=term,
|
|
279
265
|
term_position=term_index+1,
|
|
280
266
|
collection_id_or_constant_value=str(part))
|
|
@@ -298,8 +284,7 @@ class DrsValidator(DrsApplication):
|
|
|
298
284
|
for index in range(part_index, part_max_index):
|
|
299
285
|
part = specs.parts[index]
|
|
300
286
|
issue = MissingTerm(collection_id=str(part), collection_position=index+1)
|
|
301
|
-
if part.
|
|
302
|
-
cast(DrsCollection, part).is_required:
|
|
287
|
+
if part.is_required:
|
|
303
288
|
errors.append(issue)
|
|
304
289
|
else:
|
|
305
290
|
warnings.append(issue)
|
|
@@ -308,8 +293,7 @@ class DrsValidator(DrsApplication):
|
|
|
308
293
|
for index in range(term_index, term_max_index):
|
|
309
294
|
term = terms[index]
|
|
310
295
|
part = specs.parts[part_index]
|
|
311
|
-
if part.
|
|
312
|
-
(not cast(DrsCollection, part).is_required) and \
|
|
296
|
+
if (not part.is_required) and \
|
|
313
297
|
matching_code_mapping[part.__str__()] < 0: # noqa E125
|
|
314
298
|
issue = ExtraTerm(term=term, term_position=index, collection_id=str(part))
|
|
315
299
|
else:
|
|
@@ -1,74 +1,145 @@
|
|
|
1
|
-
import contextlib
|
|
2
1
|
import json
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from itertools import product
|
|
3
4
|
from pathlib import Path
|
|
4
|
-
from typing import
|
|
5
|
+
from typing import Sequence
|
|
5
6
|
|
|
7
|
+
from jinja2 import Environment, FileSystemLoader
|
|
6
8
|
from sqlmodel import Session
|
|
7
9
|
|
|
8
10
|
from esgvoc.api import projects, search
|
|
9
|
-
from esgvoc.api.project_specs import
|
|
10
|
-
GlobalAttributeSpecBase,
|
|
11
|
-
GlobalAttributeSpecSpecific,
|
|
12
|
-
GlobalAttributeVisitor,
|
|
13
|
-
)
|
|
11
|
+
from esgvoc.api.project_specs import CatalogProperty, DrsType
|
|
14
12
|
from esgvoc.core.constants import DRS_SPECS_JSON_KEY, PATTERN_JSON_KEY
|
|
15
|
-
from esgvoc.core.db.models.project import PCollection, TermKind
|
|
16
|
-
from esgvoc.core.
|
|
13
|
+
from esgvoc.core.db.models.project import PCollection, PTerm, TermKind
|
|
14
|
+
from esgvoc.core.db.models.universe import UTerm
|
|
15
|
+
from esgvoc.core.exceptions import EsgvocException, EsgvocNotFoundError, EsgvocNotImplementedError, EsgvocValueError
|
|
17
16
|
|
|
18
17
|
KEY_SEPARATOR = ':'
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
TEMPLATE_DIR_NAME = 'templates'
|
|
19
|
+
TEMPLATE_DIR_PATH = Path(__file__).parent.joinpath(TEMPLATE_DIR_NAME)
|
|
20
|
+
TEMPLATE_FILE_NAME = 'template.jinja'
|
|
21
21
|
JSON_INDENTATION = 2
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
@dataclass
|
|
25
|
+
class _CatalogProperty:
|
|
26
|
+
field_name: str
|
|
27
|
+
field_value: dict
|
|
28
|
+
is_required: bool
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _process_col_plain_terms(collection: PCollection, source_collection_key: str) -> tuple[str, list[str]]:
|
|
32
|
+
property_values: set[str] = set()
|
|
26
33
|
for term in collection.terms:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
property_key, property_value = _process_plain_term(term, source_collection_key)
|
|
35
|
+
property_values.add(property_value)
|
|
36
|
+
return property_key, list(property_values) # type: ignore
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _process_plain_term(term: PTerm, source_collection_key: str) -> tuple[str, str]:
|
|
40
|
+
if source_collection_key in term.specs:
|
|
41
|
+
property_value = term.specs[source_collection_key]
|
|
42
|
+
else:
|
|
43
|
+
raise EsgvocNotFoundError(f'missing key {source_collection_key} for term {term.id} in ' +
|
|
44
|
+
f'collection {term.collection.id}')
|
|
45
|
+
return 'enum', property_value
|
|
34
46
|
|
|
35
47
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
result =
|
|
48
|
+
def _process_col_composite_terms(collection: PCollection, universe_session: Session,
|
|
49
|
+
project_session: Session) -> tuple[str, list[str | dict], bool]:
|
|
50
|
+
result: list[str | dict] = list()
|
|
51
|
+
property_key = ""
|
|
52
|
+
has_pattern = False
|
|
39
53
|
for term in collection.terms:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# As the pattern is a concatenation of plain or regex, multiple ^ and $ can exist.
|
|
50
|
-
# The later, must be removed.
|
|
51
|
-
result = result.replace('^', '').replace('$', '')
|
|
52
|
-
result = f'^{result}$'
|
|
53
|
-
return result
|
|
54
|
+
property_key, property_value, _has_pattern = _process_composite_term(term, universe_session,
|
|
55
|
+
project_session)
|
|
56
|
+
if isinstance(property_value, list):
|
|
57
|
+
result.extend(property_value)
|
|
58
|
+
else:
|
|
59
|
+
result.append(property_value)
|
|
60
|
+
has_pattern |= _has_pattern
|
|
61
|
+
return property_key, result, has_pattern
|
|
62
|
+
|
|
54
63
|
|
|
64
|
+
def _inner_process_composite_term(resolved_term: UTerm | PTerm,
|
|
65
|
+
universe_session: Session,
|
|
66
|
+
project_session: Session) -> tuple[str | list, bool]:
|
|
67
|
+
is_pattern = False
|
|
68
|
+
match resolved_term.kind:
|
|
69
|
+
case TermKind.PLAIN:
|
|
70
|
+
result = resolved_term.specs[DRS_SPECS_JSON_KEY]
|
|
71
|
+
case TermKind.PATTERN:
|
|
72
|
+
result = resolved_term.specs[PATTERN_JSON_KEY].replace('^', '').replace('$', '')
|
|
73
|
+
is_pattern = True
|
|
74
|
+
case TermKind.COMPOSITE:
|
|
75
|
+
_, result, is_pattern = _process_composite_term(resolved_term, universe_session,
|
|
76
|
+
project_session)
|
|
77
|
+
case _:
|
|
78
|
+
msg = f"unsupported term kind '{resolved_term.kind}'"
|
|
79
|
+
raise EsgvocNotImplementedError(msg)
|
|
80
|
+
return result, is_pattern
|
|
55
81
|
|
|
56
|
-
|
|
82
|
+
|
|
83
|
+
def _accumulate_resolved_part(resolved_part: list,
|
|
84
|
+
resolved_term: UTerm | PTerm,
|
|
85
|
+
universe_session: Session,
|
|
86
|
+
project_session: Session) -> bool:
|
|
87
|
+
tmp, has_pattern = _inner_process_composite_term(resolved_term, universe_session,
|
|
88
|
+
project_session)
|
|
89
|
+
if isinstance(tmp, list):
|
|
90
|
+
resolved_part.extend(tmp)
|
|
91
|
+
else:
|
|
92
|
+
resolved_part.append(tmp)
|
|
93
|
+
return has_pattern
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _process_composite_term(term: UTerm | PTerm, universe_session: Session,
|
|
97
|
+
project_session: Session) -> tuple[str, list[str | dict], bool]:
|
|
98
|
+
resolved_parts = list()
|
|
99
|
+
separator, parts = projects._get_composite_term_separator_parts(term)
|
|
100
|
+
has_pattern = False
|
|
101
|
+
for part in parts:
|
|
102
|
+
resolved_term = projects._resolve_composite_term_part(part, universe_session, project_session)
|
|
103
|
+
resolved_part = list()
|
|
104
|
+
if isinstance(resolved_term, Sequence):
|
|
105
|
+
for r_term in resolved_term:
|
|
106
|
+
has_pattern |= _accumulate_resolved_part(resolved_part, r_term, universe_session,
|
|
107
|
+
project_session)
|
|
108
|
+
else:
|
|
109
|
+
has_pattern = _accumulate_resolved_part(resolved_part, resolved_term, universe_session,
|
|
110
|
+
project_session)
|
|
111
|
+
resolved_parts.append(resolved_part)
|
|
112
|
+
property_values: list[str | dict] = list()
|
|
113
|
+
for combination in product(*resolved_parts):
|
|
114
|
+
# Patterns terms are meant to be validated individually.
|
|
115
|
+
# So their regex are defined as a whole (begins by a ^, ends by a $).
|
|
116
|
+
# As the pattern is a concatenation of plain or regex, multiple ^ and $ can exist.
|
|
117
|
+
# The later, must be removed.
|
|
118
|
+
tmp = separator.join(combination)
|
|
119
|
+
if has_pattern:
|
|
120
|
+
tmp = f'^{tmp}$'
|
|
121
|
+
tmp = {'pattern': tmp}
|
|
122
|
+
property_values.append(tmp)
|
|
123
|
+
property_key = 'anyOf' if has_pattern else 'enum'
|
|
124
|
+
return property_key, property_values, has_pattern
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _process_col_pattern_terms(collection: PCollection) -> tuple[str, str]:
|
|
57
128
|
# The generation of the value of the field pattern for the collections with more than one term
|
|
58
129
|
# is not specified yet.
|
|
59
130
|
if len(collection.terms) == 1:
|
|
60
131
|
term = collection.terms[0]
|
|
61
|
-
return term
|
|
132
|
+
return _process_pattern_term(term)
|
|
62
133
|
else:
|
|
63
134
|
msg = f"unsupported collection of term pattern with more than one term for '{collection.id}'"
|
|
64
135
|
raise EsgvocNotImplementedError(msg)
|
|
65
136
|
|
|
66
137
|
|
|
67
|
-
def
|
|
68
|
-
return
|
|
138
|
+
def _process_pattern_term(term: PTerm) -> tuple[str, str]:
|
|
139
|
+
return 'pattern', term.specs[PATTERN_JSON_KEY]
|
|
69
140
|
|
|
70
141
|
|
|
71
|
-
class
|
|
142
|
+
class CatalogPropertiesJsonTranslator:
|
|
72
143
|
def __init__(self, project_id: str) -> None:
|
|
73
144
|
self.project_id = project_id
|
|
74
145
|
# Project session can't be None here.
|
|
@@ -85,101 +156,155 @@ class JsonPropertiesVisitor(GlobalAttributeVisitor, contextlib.AbstractContextMa
|
|
|
85
156
|
raise exception_value
|
|
86
157
|
return True
|
|
87
158
|
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
property_value: str | list[str]
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
159
|
+
def _translate_property_value(self, catalog_property: CatalogProperty) \
|
|
160
|
+
-> tuple[str, str | list[str] | list[str | dict]]:
|
|
161
|
+
property_value: str | list[str] | list[str | dict]
|
|
162
|
+
if catalog_property.source_collection not in self.collections:
|
|
163
|
+
raise EsgvocNotFoundError(f"collection '{catalog_property.source_collection}' is not found")
|
|
164
|
+
|
|
165
|
+
if catalog_property.source_collection_key is None:
|
|
166
|
+
source_collection_key = DRS_SPECS_JSON_KEY
|
|
167
|
+
else:
|
|
168
|
+
source_collection_key = catalog_property.source_collection_key
|
|
169
|
+
|
|
170
|
+
if catalog_property.source_collection_term is None:
|
|
171
|
+
collection = self.collections[catalog_property.source_collection]
|
|
172
|
+
match collection.term_kind:
|
|
173
|
+
case TermKind.PLAIN:
|
|
174
|
+
property_key, property_value = _process_col_plain_terms(
|
|
175
|
+
collection=collection,
|
|
176
|
+
source_collection_key=source_collection_key)
|
|
177
|
+
case TermKind.COMPOSITE:
|
|
178
|
+
property_key, property_value, _ = _process_col_composite_terms(
|
|
179
|
+
collection=collection,
|
|
180
|
+
universe_session=self.universe_session,
|
|
181
|
+
project_session=self.project_session)
|
|
182
|
+
case TermKind.PATTERN:
|
|
183
|
+
property_key, property_value = _process_col_pattern_terms(collection)
|
|
184
|
+
case _:
|
|
185
|
+
msg = f"unsupported term kind '{collection.term_kind}'"
|
|
186
|
+
raise EsgvocNotImplementedError(msg)
|
|
187
|
+
else:
|
|
188
|
+
pterm_found = projects._get_term_in_collection(
|
|
189
|
+
session=self.project_session,
|
|
190
|
+
collection_id=catalog_property.source_collection,
|
|
191
|
+
term_id=catalog_property.source_collection_term)
|
|
192
|
+
if pterm_found is None:
|
|
193
|
+
raise EsgvocValueError(f"term '{catalog_property.source_collection_term}' is not " +
|
|
194
|
+
f"found in collection '{catalog_property.source_collection}'")
|
|
195
|
+
match pterm_found.kind:
|
|
196
|
+
case TermKind.PLAIN:
|
|
197
|
+
property_key, property_value = _process_plain_term(
|
|
198
|
+
term=pterm_found,
|
|
199
|
+
source_collection_key=source_collection_key)
|
|
200
|
+
case TermKind.COMPOSITE:
|
|
201
|
+
property_key, property_value, _ = _process_composite_term(
|
|
202
|
+
term=pterm_found,
|
|
203
|
+
universe_session=self.universe_session,
|
|
204
|
+
project_session=self.project_session)
|
|
205
|
+
case TermKind.PATTERN:
|
|
206
|
+
property_key, property_value = _process_pattern_term(term=pterm_found)
|
|
207
|
+
case _:
|
|
208
|
+
msg = f"unsupported term kind '{pterm_found.kind}'"
|
|
209
|
+
raise EsgvocNotImplementedError(msg)
|
|
113
210
|
return property_key, property_value
|
|
114
211
|
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
212
|
+
def translate_property(self, catalog_property: CatalogProperty) -> _CatalogProperty:
|
|
213
|
+
property_key, property_value = self._translate_property_value(catalog_property)
|
|
214
|
+
field_value = dict()
|
|
215
|
+
if 'array' in catalog_property.catalog_field_value_type:
|
|
216
|
+
field_value['type'] = 'array'
|
|
217
|
+
root_property = dict()
|
|
218
|
+
field_value['items'] = root_property
|
|
219
|
+
root_property['type'] = catalog_property.catalog_field_value_type.split('_')[0]
|
|
220
|
+
root_property['minItems'] = 1
|
|
221
|
+
else:
|
|
222
|
+
field_value['type'] = catalog_property.catalog_field_value_type
|
|
223
|
+
root_property = field_value
|
|
224
|
+
|
|
225
|
+
root_property[property_key] = property_value
|
|
226
|
+
|
|
227
|
+
if catalog_property.catalog_field_name is None:
|
|
228
|
+
attribute_name = catalog_property.source_collection
|
|
229
|
+
else:
|
|
230
|
+
attribute_name = catalog_property.catalog_field_name
|
|
231
|
+
field_name = CatalogPropertiesJsonTranslator._translate_field_name(self.project_id,
|
|
232
|
+
attribute_name)
|
|
233
|
+
return _CatalogProperty(field_name=field_name,
|
|
234
|
+
field_value=field_value,
|
|
235
|
+
is_required=catalog_property.is_required)
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def _translate_field_name(project_id: str, attribute_name) -> str:
|
|
239
|
+
return f'{project_id}{KEY_SEPARATOR}{attribute_name}'
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _catalog_properties_json_processor(property_translator: CatalogPropertiesJsonTranslator,
|
|
243
|
+
properties: list[CatalogProperty]) -> list[_CatalogProperty]:
|
|
244
|
+
result: list[_CatalogProperty] = list()
|
|
245
|
+
for dataset_property_spec in properties:
|
|
246
|
+
catalog_property = property_translator.translate_property(dataset_property_spec)
|
|
247
|
+
result.append(catalog_property)
|
|
248
|
+
return result
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def generate_json_schema(project_id: str) -> dict:
|
|
152
252
|
"""
|
|
153
253
|
Generate json schema for the given project.
|
|
154
254
|
|
|
155
255
|
:param project_id: The id of the given project.
|
|
156
256
|
:type project_id: str
|
|
157
|
-
:returns: The
|
|
158
|
-
:rtype:
|
|
159
|
-
:raises
|
|
160
|
-
:raises
|
|
257
|
+
:returns: The root node of a json schema.
|
|
258
|
+
:rtype: dict
|
|
259
|
+
:raises EsgvocValueError: On wrong information in catalog_specs.
|
|
260
|
+
:raises EsgvocNotFoundError: On missing information in catalog_specs.
|
|
261
|
+
:raises EsgvocNotImplementedError: On unexpected operations resulted in wrong information in catalog_specs).
|
|
262
|
+
:raises EsgvocException: On json compliance error.
|
|
161
263
|
"""
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
264
|
+
project_specs = projects.get_project(project_id)
|
|
265
|
+
if project_specs is not None:
|
|
266
|
+
catalog_specs = project_specs.catalog_specs
|
|
267
|
+
if catalog_specs is not None:
|
|
268
|
+
env = Environment(loader=FileSystemLoader(TEMPLATE_DIR_PATH)) # noqa: S701
|
|
269
|
+
template = env.get_template(TEMPLATE_FILE_NAME)
|
|
270
|
+
|
|
271
|
+
file_extension_version = catalog_specs.catalog_properties.extensions[0].version
|
|
272
|
+
drs_dataset_id_regex = project_specs.drs_specs[DrsType.DATASET_ID].regex
|
|
273
|
+
property_translator = CatalogPropertiesJsonTranslator(project_id)
|
|
274
|
+
catalog_dataset_properties = \
|
|
275
|
+
_catalog_properties_json_processor(property_translator,
|
|
276
|
+
catalog_specs.dataset_properties)
|
|
277
|
+
|
|
278
|
+
catalog_file_properties = \
|
|
279
|
+
_catalog_properties_json_processor(property_translator,
|
|
280
|
+
catalog_specs.file_properties)
|
|
281
|
+
del property_translator
|
|
282
|
+
json_raw_str = template.render(project_id=project_id,
|
|
283
|
+
catalog_version=catalog_specs.version,
|
|
284
|
+
file_extension_version=file_extension_version,
|
|
285
|
+
drs_dataset_id_regex=drs_dataset_id_regex,
|
|
286
|
+
catalog_dataset_properties=catalog_dataset_properties,
|
|
287
|
+
catalog_file_properties=catalog_file_properties)
|
|
288
|
+
# Json compliance checking.
|
|
289
|
+
try:
|
|
290
|
+
result = json.loads(json_raw_str)
|
|
291
|
+
return result
|
|
292
|
+
except Exception as e:
|
|
293
|
+
raise EsgvocException(f'unable to produce schema compliant to JSON: {e}') from e
|
|
182
294
|
else:
|
|
183
|
-
raise EsgvocNotFoundError(f"project '{project_id}'
|
|
295
|
+
raise EsgvocNotFoundError(f"catalog properties for the project '{project_id}' " +
|
|
296
|
+
"are missing")
|
|
184
297
|
else:
|
|
185
|
-
raise EsgvocNotFoundError(f"
|
|
298
|
+
raise EsgvocNotFoundError(f"unknown project '{project_id}'")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def pretty_print_json_node(obj: dict) -> str:
|
|
302
|
+
"""
|
|
303
|
+
Serialize a dictionary into json format.
|
|
304
|
+
|
|
305
|
+
:param obj: The dictionary.
|
|
306
|
+
:type obj: dict
|
|
307
|
+
:returns: a string that represents the dictionary in json format.
|
|
308
|
+
:rtype: str
|
|
309
|
+
"""
|
|
310
|
+
return json.dumps(obj, indent=JSON_INDENTATION)
|