esgvoc 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of esgvoc might be problematic. Click here for more details.
- esgvoc/__init__.py +1 -1
- esgvoc/api/__init__.py +95 -60
- esgvoc/api/project_specs.py +3 -2
- esgvoc/api/projects.py +671 -406
- esgvoc/api/py.typed +0 -0
- esgvoc/api/report.py +12 -8
- esgvoc/api/search.py +141 -98
- esgvoc/api/universe.py +353 -157
- esgvoc/apps/drs/constants.py +1 -1
- esgvoc/apps/drs/generator.py +51 -69
- esgvoc/apps/drs/report.py +60 -15
- esgvoc/apps/drs/validator.py +60 -71
- esgvoc/apps/py.typed +0 -0
- esgvoc/cli/drs.py +3 -2
- esgvoc/cli/get.py +9 -6
- esgvoc/core/constants.py +1 -1
- esgvoc/core/db/__init__.py +2 -4
- esgvoc/core/db/connection.py +5 -3
- esgvoc/core/db/models/project.py +50 -8
- esgvoc/core/db/models/universe.py +48 -9
- esgvoc/core/db/project_ingestion.py +60 -46
- esgvoc/core/db/universe_ingestion.py +55 -27
- esgvoc/core/exceptions.py +33 -0
- {esgvoc-0.3.0.dist-info → esgvoc-0.4.0.dist-info}/METADATA +1 -1
- {esgvoc-0.3.0.dist-info → esgvoc-0.4.0.dist-info}/RECORD +28 -26
- esgvoc/api/_utils.py +0 -53
- {esgvoc-0.3.0.dist-info → esgvoc-0.4.0.dist-info}/WHEEL +0 -0
- {esgvoc-0.3.0.dist-info → esgvoc-0.4.0.dist-info}/entry_points.txt +0 -0
- {esgvoc-0.3.0.dist-info → esgvoc-0.4.0.dist-info}/licenses/LICENSE.txt +0 -0
esgvoc/apps/drs/generator.py
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
from typing import Any, Iterable, Mapping, cast
|
|
2
2
|
|
|
3
3
|
import esgvoc.api.projects as projects
|
|
4
|
-
from esgvoc.api.project_specs import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
from esgvoc.api.project_specs import DrsCollection, DrsConstant, DrsPartKind, DrsSpecification, DrsType
|
|
5
|
+
from esgvoc.apps.drs.report import (
|
|
6
|
+
AssignedTerm,
|
|
7
|
+
ConflictingCollections,
|
|
8
|
+
DrsGenerationReport,
|
|
9
|
+
GenerationError,
|
|
10
|
+
GenerationIssue,
|
|
11
|
+
GenerationWarning,
|
|
12
|
+
InvalidTerm,
|
|
13
|
+
MissingTerm,
|
|
14
|
+
TooManyTermCollection,
|
|
15
|
+
)
|
|
11
16
|
from esgvoc.apps.drs.validator import DrsApplication
|
|
17
|
+
from esgvoc.core.exceptions import EsgvocDbError
|
|
12
18
|
|
|
13
19
|
|
|
14
20
|
def _get_first_item(items: set[Any]) -> Any:
|
|
15
21
|
result = None
|
|
16
|
-
for result in items:
|
|
22
|
+
for result in items: # noqa: B007
|
|
17
23
|
break
|
|
18
24
|
return result
|
|
19
25
|
|
|
@@ -74,7 +80,6 @@ class DrsGenerator(DrsApplication):
|
|
|
74
80
|
"""
|
|
75
81
|
return self._generate_from_bag_of_terms(terms, self.dataset_id_specs)
|
|
76
82
|
|
|
77
|
-
|
|
78
83
|
def generate_file_name_from_mapping(self, mapping: Mapping[str, str]) -> DrsGenerationReport:
|
|
79
84
|
"""
|
|
80
85
|
Generate a file name DRS expression from a mapping of collection ids and terms.
|
|
@@ -88,7 +93,7 @@ class DrsGenerator(DrsApplication):
|
|
|
88
93
|
"""
|
|
89
94
|
report = self._generate_from_mapping(mapping, self.file_name_specs)
|
|
90
95
|
report.generated_drs_expression = report.generated_drs_expression + \
|
|
91
|
-
self._get_full_file_name_extension()
|
|
96
|
+
self._get_full_file_name_extension() # noqa E127
|
|
92
97
|
return report
|
|
93
98
|
|
|
94
99
|
def generate_file_name_from_bag_of_terms(self, terms: Iterable[str]) -> DrsGenerationReport:
|
|
@@ -104,11 +109,11 @@ class DrsGenerator(DrsApplication):
|
|
|
104
109
|
"""
|
|
105
110
|
report = self._generate_from_bag_of_terms(terms, self.file_name_specs)
|
|
106
111
|
report.generated_drs_expression = report.generated_drs_expression + \
|
|
107
|
-
self._get_full_file_name_extension()
|
|
112
|
+
self._get_full_file_name_extension() # noqa E127
|
|
108
113
|
return report
|
|
109
114
|
|
|
110
115
|
def generate_from_mapping(self, mapping: Mapping[str, str],
|
|
111
|
-
drs_type: DrsType|str) -> DrsGenerationReport:
|
|
116
|
+
drs_type: DrsType | str) -> DrsGenerationReport:
|
|
112
117
|
"""
|
|
113
118
|
Generate a DRS expression from a mapping of collection ids and terms.
|
|
114
119
|
|
|
@@ -127,10 +132,10 @@ class DrsGenerator(DrsApplication):
|
|
|
127
132
|
case DrsType.DATASET_ID:
|
|
128
133
|
return self.generate_dataset_id_from_mapping(mapping=mapping)
|
|
129
134
|
case _:
|
|
130
|
-
raise
|
|
135
|
+
raise EsgvocDbError(f"unsupported drs type '{drs_type}'")
|
|
131
136
|
|
|
132
|
-
def generate_from_bag_of_terms(self, terms: Iterable[str], drs_type: DrsType|str) \
|
|
133
|
-
|
|
137
|
+
def generate_from_bag_of_terms(self, terms: Iterable[str], drs_type: DrsType | str) \
|
|
138
|
+
-> DrsGenerationReport: # noqa E127
|
|
134
139
|
"""
|
|
135
140
|
Generate a DRS expression from an unordered bag of terms.
|
|
136
141
|
|
|
@@ -149,26 +154,25 @@ class DrsGenerator(DrsApplication):
|
|
|
149
154
|
case DrsType.DATASET_ID:
|
|
150
155
|
return self.generate_dataset_id_from_bag_of_terms(terms=terms)
|
|
151
156
|
case _:
|
|
152
|
-
raise
|
|
153
|
-
|
|
157
|
+
raise EsgvocDbError(f"unsupported drs type '{drs_type}'")
|
|
154
158
|
|
|
155
159
|
def _generate_from_mapping(self, mapping: Mapping[str, str], specs: DrsSpecification) \
|
|
156
|
-
|
|
160
|
+
-> DrsGenerationReport: # noqa E127
|
|
157
161
|
drs_expression, errors, warnings = self.__generate_from_mapping(mapping, specs, True)
|
|
158
162
|
if self.pedantic:
|
|
159
163
|
errors.extend(warnings)
|
|
160
164
|
warnings.clear()
|
|
161
165
|
return DrsGenerationReport(project_id=self.project_id, type=specs.type,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
given_mapping_or_bag_of_terms=mapping,
|
|
167
|
+
mapping_used=mapping,
|
|
168
|
+
generated_drs_expression=drs_expression,
|
|
169
|
+
errors=cast(list[GenerationError], errors),
|
|
170
|
+
warnings=cast(list[GenerationWarning], warnings))
|
|
167
171
|
|
|
168
172
|
def __generate_from_mapping(self, mapping: Mapping[str, str],
|
|
169
173
|
specs: DrsSpecification,
|
|
170
|
-
has_to_valid_terms: bool)\
|
|
171
|
-
|
|
174
|
+
has_to_valid_terms: bool) \
|
|
175
|
+
-> tuple[str, list[GenerationIssue], list[GenerationIssue]]: # noqa E127
|
|
172
176
|
errors: list[GenerationIssue] = list()
|
|
173
177
|
warnings: list[GenerationIssue] = list()
|
|
174
178
|
drs_expression = ""
|
|
@@ -186,19 +190,19 @@ class DrsGenerator(DrsApplication):
|
|
|
186
190
|
collection_id)
|
|
187
191
|
if not matching_terms:
|
|
188
192
|
issue = InvalidTerm(term=part_value,
|
|
189
|
-
|
|
190
|
-
|
|
193
|
+
term_position=part_position,
|
|
194
|
+
collection_id_or_constant_value=collection_id)
|
|
191
195
|
errors.append(issue)
|
|
192
196
|
part_value = DrsGenerationReport.INVALID_TAG
|
|
193
197
|
else:
|
|
194
198
|
other_issue = MissingTerm(collection_id=collection_id,
|
|
195
|
-
|
|
199
|
+
collection_position=part_position)
|
|
196
200
|
if collection_part.is_required:
|
|
197
201
|
errors.append(other_issue)
|
|
198
202
|
part_value = DrsGenerationReport.MISSING_TAG
|
|
199
203
|
else:
|
|
200
204
|
warnings.append(other_issue)
|
|
201
|
-
continue
|
|
205
|
+
continue # The for loop.
|
|
202
206
|
else:
|
|
203
207
|
constant_part = cast(DrsConstant, part)
|
|
204
208
|
part_value = constant_part.value
|
|
@@ -209,7 +213,7 @@ class DrsGenerator(DrsApplication):
|
|
|
209
213
|
return drs_expression, errors, warnings
|
|
210
214
|
|
|
211
215
|
def _generate_from_bag_of_terms(self, terms: Iterable[str], specs: DrsSpecification) \
|
|
212
|
-
|
|
216
|
+
-> DrsGenerationReport: # noqa E127
|
|
213
217
|
collection_terms_mapping: dict[str, set[str]] = dict()
|
|
214
218
|
for term in terms:
|
|
215
219
|
matching_terms = projects.valid_term_in_project(term, self.project_id)
|
|
@@ -226,14 +230,14 @@ class DrsGenerator(DrsApplication):
|
|
|
226
230
|
errors.extend(warnings)
|
|
227
231
|
warnings.clear()
|
|
228
232
|
return DrsGenerationReport(project_id=self.project_id, type=specs.type,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
given_mapping_or_bag_of_terms=terms,
|
|
234
|
+
mapping_used=mapping, generated_drs_expression=drs_expression,
|
|
235
|
+
errors=cast(list[GenerationError], errors),
|
|
236
|
+
warnings=cast(list[GenerationWarning], warnings))
|
|
233
237
|
|
|
234
238
|
@staticmethod
|
|
235
239
|
def _resolve_conflicts(collection_terms_mapping: dict[str, set[str]]) \
|
|
236
|
-
|
|
240
|
+
-> tuple[dict[str, set[str]], list[GenerationIssue]]: # noqa E127
|
|
237
241
|
warnings: list[GenerationIssue] = list()
|
|
238
242
|
conflicting_collection_ids_list: list[list[str]] = list()
|
|
239
243
|
collection_ids: list[str] = list(collection_terms_mapping.keys())
|
|
@@ -242,8 +246,8 @@ class DrsGenerator(DrsApplication):
|
|
|
242
246
|
for l_collection_index in range(0, len_collection_ids - 1):
|
|
243
247
|
conflicting_collection_ids: list[str] = list()
|
|
244
248
|
for r_collection_index in range(l_collection_index + 1, len_collection_ids):
|
|
245
|
-
if collection_terms_mapping[collection_ids[l_collection_index]].isdisjoint
|
|
246
|
-
|
|
249
|
+
if collection_terms_mapping[collection_ids[l_collection_index]].isdisjoint(
|
|
250
|
+
collection_terms_mapping[collection_ids[r_collection_index]]):
|
|
247
251
|
continue
|
|
248
252
|
else:
|
|
249
253
|
not_registered = True
|
|
@@ -286,7 +290,7 @@ class DrsGenerator(DrsApplication):
|
|
|
286
290
|
DrsGenerator._remove_ids_from_conflicts(conflicting_collection_ids_list,
|
|
287
291
|
collection_ids_to_be_removed)
|
|
288
292
|
DrsGenerator._remove_term_from_other_term_sets(collection_terms_mapping,
|
|
289
|
-
|
|
293
|
+
collection_ids_to_be_removed)
|
|
290
294
|
# Every time conflicting_collection_ids_list is modified, we must restart the loop,
|
|
291
295
|
# as conflicting collections may be resolved.
|
|
292
296
|
continue
|
|
@@ -306,7 +310,7 @@ class DrsGenerator(DrsApplication):
|
|
|
306
310
|
DrsGenerator._remove_ids_from_conflicts(conflicting_collection_ids_list,
|
|
307
311
|
wining_collection_ids)
|
|
308
312
|
DrsGenerator._remove_term_from_other_term_sets(collection_terms_mapping,
|
|
309
|
-
|
|
313
|
+
wining_collection_ids)
|
|
310
314
|
# Every time conflicting_collection_ids_list is modified, we must restart the loop,
|
|
311
315
|
# as conflicting collections may be resolved.
|
|
312
316
|
continue
|
|
@@ -316,13 +320,10 @@ class DrsGenerator(DrsApplication):
|
|
|
316
320
|
wining_id_and_term_pairs: list[tuple[str, str]] = list()
|
|
317
321
|
for collection_ids in conflicting_collection_ids_list:
|
|
318
322
|
for collection_index in range(0, len(collection_ids)):
|
|
323
|
+
collection_set = collection_ids[collection_index + 1:] + collection_ids[:collection_index]
|
|
319
324
|
diff: set[str] = collection_terms_mapping[collection_ids[collection_index]]\
|
|
320
|
-
.difference(
|
|
321
|
-
|
|
322
|
-
for index in collection_ids[collection_index + 1 :] +\
|
|
323
|
-
collection_ids[:collection_index]
|
|
324
|
-
]
|
|
325
|
-
)
|
|
325
|
+
.difference(*[collection_terms_mapping[index] # noqa E127
|
|
326
|
+
for index in collection_set])
|
|
326
327
|
if len(diff) == 1:
|
|
327
328
|
wining_id_and_term_pairs.append((collection_ids[collection_index],
|
|
328
329
|
_get_first_item(diff)))
|
|
@@ -341,12 +342,12 @@ class DrsGenerator(DrsApplication):
|
|
|
341
342
|
wining_collection_ids)
|
|
342
343
|
continue
|
|
343
344
|
else:
|
|
344
|
-
break
|
|
345
|
+
break # Stop the loop when no progress is made.
|
|
345
346
|
return collection_terms_mapping, warnings
|
|
346
347
|
|
|
347
348
|
@staticmethod
|
|
348
349
|
def _check_collection_terms_mapping(collection_terms_mapping: dict[str, set[str]]) \
|
|
349
|
-
|
|
350
|
+
-> tuple[dict[str, str], list[GenerationIssue]]: # noqa E127
|
|
350
351
|
errors: list[GenerationIssue] = list()
|
|
351
352
|
# 1. Looking for collections that share strictly the same term(s).
|
|
352
353
|
collection_ids: list[str] = list(collection_terms_mapping.keys())
|
|
@@ -358,7 +359,7 @@ class DrsGenerator(DrsApplication):
|
|
|
358
359
|
for r_collection_index in range(l_collection_index + 1, len_collection_ids):
|
|
359
360
|
r_collection_id = collection_ids[r_collection_index]
|
|
360
361
|
r_term_set = collection_terms_mapping[r_collection_id]
|
|
361
|
-
#
|
|
362
|
+
# Check if the set is empty because the difference will always be an empty set!
|
|
362
363
|
if l_term_set and (not l_term_set.difference(r_term_set)):
|
|
363
364
|
not_registered = True
|
|
364
365
|
for faulty_collections in faulty_collections_list:
|
|
@@ -386,9 +387,9 @@ class DrsGenerator(DrsApplication):
|
|
|
386
387
|
result[collection_id] = _get_first_item(term_set)
|
|
387
388
|
elif len_term_set > 1:
|
|
388
389
|
other_issue = TooManyTermCollection(collection_id=collection_id,
|
|
389
|
-
|
|
390
|
+
terms=_transform_set_and_sort(term_set))
|
|
390
391
|
errors.append(other_issue)
|
|
391
|
-
#else: Don't add emptied collection to the result.
|
|
392
|
+
# else: Don't add emptied collection to the result.
|
|
392
393
|
return result, errors
|
|
393
394
|
|
|
394
395
|
@staticmethod
|
|
@@ -408,22 +409,3 @@ class DrsGenerator(DrsApplication):
|
|
|
408
409
|
for conflicting_collection_ids in conflicting_collection_ids_list:
|
|
409
410
|
if collection_id_to_be_removed in conflicting_collection_ids:
|
|
410
411
|
conflicting_collection_ids.remove(collection_id_to_be_removed)
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if __name__ == "__main__":
|
|
414
|
-
project_id = 'cmip6plus'
|
|
415
|
-
generator = DrsGenerator(project_id)
|
|
416
|
-
mapping = \
|
|
417
|
-
{
|
|
418
|
-
'member_id': 'r2i2p1f2',
|
|
419
|
-
'activity_id': 'CMIP',
|
|
420
|
-
'source_id': 'MIROC6',
|
|
421
|
-
'mip_era': 'CMIP6Plus',
|
|
422
|
-
'experiment_id': 'amip',
|
|
423
|
-
'variable_id': 'od550aer',
|
|
424
|
-
'table_id': 'ACmon',
|
|
425
|
-
'grid_label': 'gn',
|
|
426
|
-
'institution_id': 'IPSL',
|
|
427
|
-
}
|
|
428
|
-
report = generator.generate_file_name_from_mapping(mapping)
|
|
429
|
-
print(report.warnings)
|
esgvoc/apps/drs/report.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import
|
|
4
|
-
Protocol)
|
|
3
|
+
from typing import Annotated, Any, ClassVar, Iterable, Literal, Mapping, Protocol
|
|
5
4
|
|
|
6
5
|
from pydantic import BaseModel, Field, computed_field
|
|
7
6
|
|
|
@@ -46,15 +45,19 @@ class ParsingIssueVisitor(Protocol):
|
|
|
46
45
|
def visit_space_issue(self, issue: "Space") -> Any:
|
|
47
46
|
"""Visit a space issue."""
|
|
48
47
|
pass
|
|
48
|
+
|
|
49
49
|
def visit_unparsable_issue(self, issue: "Unparsable") -> Any:
|
|
50
50
|
"""Visit a unparsable issue."""
|
|
51
51
|
pass
|
|
52
|
+
|
|
52
53
|
def visit_extra_separator_issue(self, issue: "ExtraSeparator") -> Any:
|
|
53
54
|
"""Visit an extra separator issue."""
|
|
54
55
|
pass
|
|
56
|
+
|
|
55
57
|
def visit_extra_char_issue(self, issue: "ExtraChar") -> Any:
|
|
56
58
|
"""Visit an extra char issue."""
|
|
57
59
|
pass
|
|
60
|
+
|
|
58
61
|
def visit_blank_term_issue(self, issue: "BlankTerm") -> Any:
|
|
59
62
|
"""Visit a blank term issue."""
|
|
60
63
|
pass
|
|
@@ -67,12 +70,15 @@ class ComplianceIssueVisitor(Protocol):
|
|
|
67
70
|
def visit_filename_extension_issue(self, issue: "FileNameExtensionIssue") -> Any:
|
|
68
71
|
"""Visit a file name extension issue."""
|
|
69
72
|
pass
|
|
73
|
+
|
|
70
74
|
def visit_invalid_term_issue(self, issue: "InvalidTerm") -> Any:
|
|
71
75
|
"""Visit an invalid term issue."""
|
|
72
76
|
pass
|
|
77
|
+
|
|
73
78
|
def visit_extra_term_issue(self, issue: "ExtraTerm") -> Any:
|
|
74
79
|
"""Visit an extra term issue."""
|
|
75
80
|
pass
|
|
81
|
+
|
|
76
82
|
def visit_missing_term_issue(self, issue: "MissingTerm") -> Any:
|
|
77
83
|
"""Visit a missing term issue."""
|
|
78
84
|
pass
|
|
@@ -89,15 +95,19 @@ class GenerationIssueVisitor(Protocol):
|
|
|
89
95
|
def visit_invalid_term_issue(self, issue: "InvalidTerm") -> Any:
|
|
90
96
|
"""Visit an invalid term issue."""
|
|
91
97
|
pass
|
|
98
|
+
|
|
92
99
|
def visit_missing_term_issue(self, issue: "MissingTerm") -> Any:
|
|
93
100
|
"""Visit a missing term issue."""
|
|
94
101
|
pass
|
|
102
|
+
|
|
95
103
|
def visit_too_many_terms_collection_issue(self, issue: "TooManyTermCollection") -> Any:
|
|
96
104
|
"""Visit a too many terms collection issue."""
|
|
97
105
|
pass
|
|
106
|
+
|
|
98
107
|
def visit_conflicting_collections_issue(self, issue: "ConflictingCollections") -> Any:
|
|
99
108
|
"""Visit a conflicting collections issue."""
|
|
100
109
|
pass
|
|
110
|
+
|
|
101
111
|
def visit_assign_term_issue(self, issue: "AssignedTerm") -> Any:
|
|
102
112
|
"""Visit an assign term issue."""
|
|
103
113
|
pass
|
|
@@ -126,7 +136,7 @@ class ParsingIssue(DrsIssue):
|
|
|
126
136
|
"""
|
|
127
137
|
Generic class for the DRS parsing issues.
|
|
128
138
|
"""
|
|
129
|
-
column: int|None = None
|
|
139
|
+
column: int | None = None
|
|
130
140
|
"""the column of faulty characters."""
|
|
131
141
|
|
|
132
142
|
@abstractmethod
|
|
@@ -148,10 +158,13 @@ class Space(ParsingIssue):
|
|
|
148
158
|
Note: `column` is `None`.
|
|
149
159
|
"""
|
|
150
160
|
kind: Literal[IssueKind.SPACE] = IssueKind.SPACE
|
|
161
|
+
|
|
151
162
|
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
152
163
|
return visitor.visit_space_issue(self)
|
|
164
|
+
|
|
153
165
|
def __str__(self):
|
|
154
166
|
return "expression is surrounded by white space[s]"
|
|
167
|
+
|
|
155
168
|
def __repr__(self) -> str:
|
|
156
169
|
return self.__str__()
|
|
157
170
|
|
|
@@ -164,10 +177,13 @@ class Unparsable(ParsingIssue):
|
|
|
164
177
|
expected_drs_type: DrsType
|
|
165
178
|
"""The expected DRS type of the expression (directory, file name or dataset id)."""
|
|
166
179
|
kind: Literal[IssueKind.UNPARSABLE] = IssueKind.UNPARSABLE
|
|
180
|
+
|
|
167
181
|
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
168
182
|
return visitor.visit_unparsable_issue(self)
|
|
183
|
+
|
|
169
184
|
def __str__(self):
|
|
170
185
|
return "unable to parse this expression"
|
|
186
|
+
|
|
171
187
|
def __repr__(self) -> str:
|
|
172
188
|
return self.__str__()
|
|
173
189
|
|
|
@@ -177,10 +193,13 @@ class ExtraSeparator(ParsingIssue):
|
|
|
177
193
|
Represents a problem of multiple separator occurrences in the DRS expression.
|
|
178
194
|
"""
|
|
179
195
|
kind: Literal[IssueKind.EXTRA_SEPARATOR] = IssueKind.EXTRA_SEPARATOR
|
|
196
|
+
|
|
180
197
|
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
181
198
|
return visitor.visit_extra_separator_issue(self)
|
|
199
|
+
|
|
182
200
|
def __str__(self):
|
|
183
201
|
return f"extra separator(s) at column {self.column}"
|
|
202
|
+
|
|
184
203
|
def __repr__(self) -> str:
|
|
185
204
|
return self.__str__()
|
|
186
205
|
|
|
@@ -190,10 +209,13 @@ class ExtraChar(ParsingIssue):
|
|
|
190
209
|
Represents a problem of extra characters at the end of the DRS expression.
|
|
191
210
|
"""
|
|
192
211
|
kind: Literal[IssueKind.EXTRA_CHAR] = IssueKind.EXTRA_CHAR
|
|
212
|
+
|
|
193
213
|
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
194
214
|
return visitor.visit_extra_char_issue(self)
|
|
215
|
+
|
|
195
216
|
def __str__(self):
|
|
196
217
|
return f"extra character(s) at column {self.column}"
|
|
218
|
+
|
|
197
219
|
def __repr__(self) -> str:
|
|
198
220
|
return self.__str__()
|
|
199
221
|
|
|
@@ -203,10 +225,13 @@ class BlankTerm(ParsingIssue):
|
|
|
203
225
|
Represents a problem of blank term in the DRS expression (i.e., space[s] surrounded by separators).
|
|
204
226
|
"""
|
|
205
227
|
kind: Literal[IssueKind.BLANK_TERM] = IssueKind.BLANK_TERM
|
|
228
|
+
|
|
206
229
|
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
207
230
|
return visitor.visit_blank_term_issue(self)
|
|
231
|
+
|
|
208
232
|
def __str__(self):
|
|
209
233
|
return f"blank term at column {self.column}"
|
|
234
|
+
|
|
210
235
|
def __repr__(self) -> str:
|
|
211
236
|
return self.__str__()
|
|
212
237
|
|
|
@@ -235,8 +260,10 @@ class FileNameExtensionIssue(ComplianceIssue):
|
|
|
235
260
|
expected_extension: str
|
|
236
261
|
"""The expected file name extension."""
|
|
237
262
|
kind: Literal[IssueKind.FILE_NAME] = IssueKind.FILE_NAME
|
|
263
|
+
|
|
238
264
|
def accept(self, visitor: ComplianceIssueVisitor) -> Any:
|
|
239
265
|
return visitor.visit_filename_extension_issue(self)
|
|
266
|
+
|
|
240
267
|
def __str__(self):
|
|
241
268
|
return f"filename extension missing or not compliant with '{self.expected_extension}'"
|
|
242
269
|
|
|
@@ -275,10 +302,14 @@ class InvalidTerm(TermIssue, GenerationIssue):
|
|
|
275
302
|
collection_id_or_constant_value: str
|
|
276
303
|
"""The collection id or the constant part of a DRS specification."""
|
|
277
304
|
kind: Literal[IssueKind.INVALID_TERM] = IssueKind.INVALID_TERM
|
|
278
|
-
|
|
305
|
+
|
|
306
|
+
def accept(self, visitor: ComplianceIssueVisitor | GenerationIssueVisitor) -> Any:
|
|
279
307
|
return visitor.visit_invalid_term_issue(self)
|
|
308
|
+
|
|
280
309
|
def __str__(self):
|
|
281
|
-
return f"term '{self.term}' not compliant with {self.collection_id_or_constant_value} at
|
|
310
|
+
return f"term '{self.term}' not compliant with {self.collection_id_or_constant_value} at " + \
|
|
311
|
+
f"position {self.term_position}"
|
|
312
|
+
|
|
282
313
|
def __repr__(self) -> str:
|
|
283
314
|
return self.__str__()
|
|
284
315
|
|
|
@@ -290,16 +321,19 @@ class ExtraTerm(TermIssue):
|
|
|
290
321
|
(`collection_id` is `None`) or it has been invalidated by an optional collection part
|
|
291
322
|
of the DRS specification (`collection_id` is set).
|
|
292
323
|
"""
|
|
293
|
-
collection_id: str|None
|
|
324
|
+
collection_id: str | None
|
|
294
325
|
"""The optional collection id or `None`."""
|
|
295
326
|
kind: Literal[IssueKind.EXTRA_TERM] = IssueKind.EXTRA_TERM
|
|
327
|
+
|
|
296
328
|
def accept(self, visitor: ComplianceIssueVisitor) -> Any:
|
|
297
329
|
return visitor.visit_extra_term_issue(self)
|
|
330
|
+
|
|
298
331
|
def __str__(self):
|
|
299
332
|
repr = f"extra term {self.term}"
|
|
300
333
|
if self.collection_id:
|
|
301
334
|
repr += f" invalidated by the optional collection {self.collection_id}"
|
|
302
335
|
return repr + f" at position {self.term_position}"
|
|
336
|
+
|
|
303
337
|
def __repr__(self) -> str:
|
|
304
338
|
return self.__str__()
|
|
305
339
|
|
|
@@ -313,10 +347,13 @@ class MissingTerm(ComplianceIssue, GenerationIssue):
|
|
|
313
347
|
collection_position: int
|
|
314
348
|
"""The collection part position (not the column of the characters)."""
|
|
315
349
|
kind: Literal[IssueKind.MISSING_TERM] = IssueKind.MISSING_TERM
|
|
316
|
-
|
|
350
|
+
|
|
351
|
+
def accept(self, visitor: ComplianceIssueVisitor | GenerationIssueVisitor) -> Any:
|
|
317
352
|
return visitor.visit_missing_term_issue(self)
|
|
353
|
+
|
|
318
354
|
def __str__(self):
|
|
319
355
|
return f'missing term for {self.collection_id} at position {self.collection_position}'
|
|
356
|
+
|
|
320
357
|
def __repr__(self) -> str:
|
|
321
358
|
return self.__str__()
|
|
322
359
|
|
|
@@ -332,6 +369,7 @@ class TooManyTermCollection(GenerationIssue):
|
|
|
332
369
|
terms: list[str]
|
|
333
370
|
"""The faulty terms."""
|
|
334
371
|
kind: Literal[IssueKind.TOO_MANY] = IssueKind.TOO_MANY
|
|
372
|
+
|
|
335
373
|
def accept(self, visitor: GenerationIssueVisitor) -> Any:
|
|
336
374
|
return visitor.visit_too_many_terms_collection_issue(self)
|
|
337
375
|
|
|
@@ -339,6 +377,7 @@ class TooManyTermCollection(GenerationIssue):
|
|
|
339
377
|
terms_str = ", ".join(term for term in self.terms)
|
|
340
378
|
result = f'collection {self.collection_id} has more than one term ({terms_str})'
|
|
341
379
|
return result
|
|
380
|
+
|
|
342
381
|
def __repr__(self) -> str:
|
|
343
382
|
return self.__str__()
|
|
344
383
|
|
|
@@ -354,13 +393,16 @@ class ConflictingCollections(GenerationIssue):
|
|
|
354
393
|
terms: list[str]
|
|
355
394
|
"""The shared terms."""
|
|
356
395
|
kind: Literal[IssueKind.CONFLICT] = IssueKind.CONFLICT
|
|
396
|
+
|
|
357
397
|
def accept(self, visitor: GenerationIssueVisitor) -> Any:
|
|
358
398
|
return visitor.visit_conflicting_collections_issue(self)
|
|
399
|
+
|
|
359
400
|
def __str__(self):
|
|
360
401
|
collection_ids_str = ", ".join(collection_id for collection_id in self.collection_ids)
|
|
361
402
|
terms_str = ", ".join(term for term in self.terms)
|
|
362
403
|
result = f"collections {collection_ids_str} are competing for the same term(s) {terms_str}"
|
|
363
404
|
return result
|
|
405
|
+
|
|
364
406
|
def __repr__(self) -> str:
|
|
365
407
|
return self.__str__()
|
|
366
408
|
|
|
@@ -375,22 +417,25 @@ class AssignedTerm(GenerationIssue):
|
|
|
375
417
|
term: str
|
|
376
418
|
"""The term."""
|
|
377
419
|
kind: Literal[IssueKind.ASSIGNED] = IssueKind.ASSIGNED
|
|
420
|
+
|
|
378
421
|
def accept(self, visitor: GenerationIssueVisitor) -> Any:
|
|
379
422
|
return visitor.visit_assign_term_issue(self)
|
|
423
|
+
|
|
380
424
|
def __str__(self):
|
|
381
425
|
result = f"assign term {self.term} for collection {self.collection_id}"
|
|
382
426
|
return result
|
|
427
|
+
|
|
383
428
|
def __repr__(self) -> str:
|
|
384
429
|
return self.__str__()
|
|
385
430
|
|
|
386
431
|
|
|
387
|
-
GenerationError =
|
|
432
|
+
GenerationError = Annotated[AssignedTerm | ConflictingCollections | InvalidTerm | MissingTerm |
|
|
388
433
|
TooManyTermCollection, Field(discriminator='kind')]
|
|
389
434
|
GenerationWarning = Annotated[AssignedTerm | MissingTerm, Field(discriminator='kind')]
|
|
390
435
|
|
|
391
|
-
ValidationError = Annotated[BlankTerm | ExtraChar | ExtraSeparator | ExtraTerm |
|
|
392
|
-
|
|
393
|
-
|
|
436
|
+
ValidationError = Annotated[BlankTerm | ExtraChar | ExtraSeparator | ExtraTerm |
|
|
437
|
+
FileNameExtensionIssue | InvalidTerm | MissingTerm | Space | Unparsable,
|
|
438
|
+
Field(discriminator='kind')]
|
|
394
439
|
ValidationWarning = Annotated[ExtraSeparator | MissingTerm | Space, Field(discriminator='kind')]
|
|
395
440
|
|
|
396
441
|
|
|
@@ -411,19 +456,19 @@ class DrsReport(BaseModel):
|
|
|
411
456
|
warnings: list
|
|
412
457
|
"""A list of DRS issues that are considered as warnings."""
|
|
413
458
|
|
|
414
|
-
@computed_field
|
|
459
|
+
@computed_field # type: ignore
|
|
415
460
|
@property
|
|
416
461
|
def nb_errors(self) -> int:
|
|
417
462
|
"""The number of errors."""
|
|
418
463
|
return len(self.errors) if self.errors else 0
|
|
419
464
|
|
|
420
|
-
@computed_field
|
|
465
|
+
@computed_field # type: ignore
|
|
421
466
|
@property
|
|
422
467
|
def nb_warnings(self) -> int:
|
|
423
468
|
"""The number of warnings."""
|
|
424
469
|
return len(self.warnings) if self.warnings else 0
|
|
425
470
|
|
|
426
|
-
@computed_field
|
|
471
|
+
@computed_field # type: ignore
|
|
427
472
|
@property
|
|
428
473
|
def validated(self) -> bool:
|
|
429
474
|
"""The correctness of the result of the DRS application."""
|
|
@@ -469,7 +514,7 @@ class DrsGenerationReport(DrsReport):
|
|
|
469
514
|
INVALID_TAG: ClassVar[str] = '[INVALID]'
|
|
470
515
|
"""Tag used in the DRS generated expression to replace a invalid term."""
|
|
471
516
|
|
|
472
|
-
given_mapping_or_bag_of_terms: Mapping|Iterable
|
|
517
|
+
given_mapping_or_bag_of_terms: Mapping | Iterable
|
|
473
518
|
"""The mapping or the bag of terms given."""
|
|
474
519
|
|
|
475
520
|
mapping_used: Mapping
|