esgvoc 0.1.2__py3-none-any.whl → 0.3.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 +3 -1
- esgvoc/api/__init__.py +30 -30
- esgvoc/api/_utils.py +28 -14
- esgvoc/api/data_descriptors/__init__.py +19 -10
- esgvoc/api/data_descriptors/activity.py +8 -45
- esgvoc/api/data_descriptors/area_label.py +6 -0
- esgvoc/api/data_descriptors/branded_suffix.py +5 -0
- esgvoc/api/data_descriptors/branded_variable.py +5 -0
- esgvoc/api/data_descriptors/consortium.py +16 -56
- esgvoc/api/data_descriptors/data_descriptor.py +106 -0
- esgvoc/api/data_descriptors/date.py +3 -46
- esgvoc/api/data_descriptors/directory_date.py +5 -0
- esgvoc/api/data_descriptors/experiment.py +19 -54
- esgvoc/api/data_descriptors/forcing_index.py +3 -45
- esgvoc/api/data_descriptors/frequency.py +6 -43
- esgvoc/api/data_descriptors/grid_label.py +6 -44
- esgvoc/api/data_descriptors/horizontal_label.py +6 -0
- esgvoc/api/data_descriptors/initialisation_index.py +3 -44
- esgvoc/api/data_descriptors/institution.py +11 -54
- esgvoc/api/data_descriptors/license.py +4 -44
- esgvoc/api/data_descriptors/mip_era.py +6 -44
- esgvoc/api/data_descriptors/model_component.py +7 -45
- esgvoc/api/data_descriptors/organisation.py +3 -40
- esgvoc/api/data_descriptors/physic_index.py +3 -45
- esgvoc/api/data_descriptors/product.py +4 -43
- esgvoc/api/data_descriptors/realisation_index.py +3 -44
- esgvoc/api/data_descriptors/realm.py +4 -42
- esgvoc/api/data_descriptors/resolution.py +6 -44
- esgvoc/api/data_descriptors/source.py +18 -53
- esgvoc/api/data_descriptors/source_type.py +3 -41
- esgvoc/api/data_descriptors/sub_experiment.py +3 -41
- esgvoc/api/data_descriptors/table.py +6 -48
- esgvoc/api/data_descriptors/temporal_label.py +6 -0
- esgvoc/api/data_descriptors/time_range.py +3 -27
- esgvoc/api/data_descriptors/variable.py +13 -71
- esgvoc/api/data_descriptors/variant_label.py +3 -47
- esgvoc/api/data_descriptors/vertical_label.py +5 -0
- esgvoc/api/project_specs.py +82 -0
- esgvoc/api/projects.py +284 -238
- esgvoc/api/report.py +89 -52
- esgvoc/api/search.py +31 -11
- esgvoc/api/universe.py +57 -48
- esgvoc/apps/__init__.py +6 -0
- esgvoc/apps/drs/__init__.py +0 -16
- esgvoc/apps/drs/constants.py +2 -0
- esgvoc/apps/drs/generator.py +429 -0
- esgvoc/apps/drs/report.py +492 -0
- esgvoc/apps/drs/validator.py +330 -0
- esgvoc/cli/drs.py +248 -0
- esgvoc/cli/get.py +26 -25
- esgvoc/cli/install.py +11 -8
- esgvoc/cli/main.py +4 -5
- esgvoc/cli/status.py +14 -2
- esgvoc/cli/valid.py +41 -45
- esgvoc/core/db/models/mixins.py +7 -0
- esgvoc/core/db/models/project.py +3 -8
- esgvoc/core/db/models/universe.py +3 -3
- esgvoc/core/db/project_ingestion.py +4 -1
- esgvoc/core/db/universe_ingestion.py +8 -7
- esgvoc/core/logging_handler.py +1 -1
- esgvoc/core/repo_fetcher.py +4 -3
- esgvoc/core/service/__init__.py +37 -5
- esgvoc/core/service/configuration/config_manager.py +188 -0
- esgvoc/core/service/configuration/setting.py +88 -0
- esgvoc/core/service/state.py +66 -42
- esgvoc-0.3.0.dist-info/METADATA +89 -0
- esgvoc-0.3.0.dist-info/RECORD +78 -0
- esgvoc-0.3.0.dist-info/licenses/LICENSE.txt +519 -0
- esgvoc/apps/drs/models.py +0 -43
- esgvoc/apps/drs/parser.py +0 -27
- esgvoc/cli/config.py +0 -79
- esgvoc/core/service/settings.py +0 -64
- esgvoc/core/service/settings.toml +0 -12
- esgvoc/core/service/settings_default.toml +0 -20
- esgvoc-0.1.2.dist-info/METADATA +0 -54
- esgvoc-0.1.2.dist-info/RECORD +0 -66
- {esgvoc-0.1.2.dist-info → esgvoc-0.3.0.dist-info}/WHEEL +0 -0
- {esgvoc-0.1.2.dist-info → esgvoc-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import (Annotated, Any, ClassVar, Iterable, Literal, Mapping,
|
|
4
|
+
Protocol)
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, computed_field
|
|
7
|
+
|
|
8
|
+
from esgvoc.api.project_specs import DrsType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class IssueKind(str, Enum):
|
|
12
|
+
"""
|
|
13
|
+
The kinds of validation and generation issues.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
SPACE = 'Space'
|
|
17
|
+
"""Represents a problem of unnecessary space[s] at the beginning or end of the DRS expression."""
|
|
18
|
+
UNPARSABLE = 'Unparsable'
|
|
19
|
+
"""Represents a problem of non-compliance of the DRS expression."""
|
|
20
|
+
EXTRA_SEPARATOR = 'ExtraSeparator'
|
|
21
|
+
"""Represents a problem of multiple separator occurrences in the DRS expression."""
|
|
22
|
+
EXTRA_CHAR = 'ExtraChar'
|
|
23
|
+
"""Represents a problem of extra characters at the end of the DRS expression."""
|
|
24
|
+
BLANK_TERM = 'BlankTerm'
|
|
25
|
+
"""Represents a problem of blank term in the DRS expression (i.e., space[s] surrounded by separators)."""
|
|
26
|
+
FILE_NAME = 'FileNameExtensionIssue'
|
|
27
|
+
"""Represents a problem on the given file name extension (missing or not compliant)."""
|
|
28
|
+
INVALID_TERM = 'InvalidTerm'
|
|
29
|
+
"""Represents a problem of invalid term against a collection or a constant part of a DRS specification."""
|
|
30
|
+
EXTRA_TERM = 'ExtraTerm'
|
|
31
|
+
"""Represents a problem of extra term at the end of the given DRS expression."""
|
|
32
|
+
MISSING_TERM = 'MissingTerm'
|
|
33
|
+
"""Represents a problem of missing term for a collection part of the DRS specification."""
|
|
34
|
+
TOO_MANY = 'TooManyTermsCollection'
|
|
35
|
+
"""Represents a problem while inferring a mapping: one term is able to match a collection"""
|
|
36
|
+
CONFLICT = 'ConflictingCollections'
|
|
37
|
+
"""Represents a problem while inferring a mapping: collections shares the very same terms"""
|
|
38
|
+
ASSIGNED = 'AssignedTerm'
|
|
39
|
+
"""Represents a decision of the Generator to assign a term to a collection, that may not be."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ParsingIssueVisitor(Protocol):
|
|
43
|
+
"""
|
|
44
|
+
Specifications for a parsing issues visitor.
|
|
45
|
+
"""
|
|
46
|
+
def visit_space_issue(self, issue: "Space") -> Any:
|
|
47
|
+
"""Visit a space issue."""
|
|
48
|
+
pass
|
|
49
|
+
def visit_unparsable_issue(self, issue: "Unparsable") -> Any:
|
|
50
|
+
"""Visit a unparsable issue."""
|
|
51
|
+
pass
|
|
52
|
+
def visit_extra_separator_issue(self, issue: "ExtraSeparator") -> Any:
|
|
53
|
+
"""Visit an extra separator issue."""
|
|
54
|
+
pass
|
|
55
|
+
def visit_extra_char_issue(self, issue: "ExtraChar") -> Any:
|
|
56
|
+
"""Visit an extra char issue."""
|
|
57
|
+
pass
|
|
58
|
+
def visit_blank_term_issue(self, issue: "BlankTerm") -> Any:
|
|
59
|
+
"""Visit a blank term issue."""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ComplianceIssueVisitor(Protocol):
|
|
64
|
+
"""
|
|
65
|
+
Specifications for a compliance issues visitor.
|
|
66
|
+
"""
|
|
67
|
+
def visit_filename_extension_issue(self, issue: "FileNameExtensionIssue") -> Any:
|
|
68
|
+
"""Visit a file name extension issue."""
|
|
69
|
+
pass
|
|
70
|
+
def visit_invalid_term_issue(self, issue: "InvalidTerm") -> Any:
|
|
71
|
+
"""Visit an invalid term issue."""
|
|
72
|
+
pass
|
|
73
|
+
def visit_extra_term_issue(self, issue: "ExtraTerm") -> Any:
|
|
74
|
+
"""Visit an extra term issue."""
|
|
75
|
+
pass
|
|
76
|
+
def visit_missing_term_issue(self, issue: "MissingTerm") -> Any:
|
|
77
|
+
"""Visit a missing term issue."""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ValidationIssueVisitor(ParsingIssueVisitor, ComplianceIssueVisitor):
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class GenerationIssueVisitor(Protocol):
|
|
86
|
+
"""
|
|
87
|
+
Specifications for a generator issues visitor.
|
|
88
|
+
"""
|
|
89
|
+
def visit_invalid_term_issue(self, issue: "InvalidTerm") -> Any:
|
|
90
|
+
"""Visit an invalid term issue."""
|
|
91
|
+
pass
|
|
92
|
+
def visit_missing_term_issue(self, issue: "MissingTerm") -> Any:
|
|
93
|
+
"""Visit a missing term issue."""
|
|
94
|
+
pass
|
|
95
|
+
def visit_too_many_terms_collection_issue(self, issue: "TooManyTermCollection") -> Any:
|
|
96
|
+
"""Visit a too many terms collection issue."""
|
|
97
|
+
pass
|
|
98
|
+
def visit_conflicting_collections_issue(self, issue: "ConflictingCollections") -> Any:
|
|
99
|
+
"""Visit a conflicting collections issue."""
|
|
100
|
+
pass
|
|
101
|
+
def visit_assign_term_issue(self, issue: "AssignedTerm") -> Any:
|
|
102
|
+
"""Visit an assign term issue."""
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class DrsIssue(BaseModel, ABC):
|
|
107
|
+
kind: str
|
|
108
|
+
"""The class name of the issue for JSON serialization/deserialization."""
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
Generic class for all the DRS issues.
|
|
112
|
+
"""
|
|
113
|
+
@abstractmethod
|
|
114
|
+
def accept(self, visitor) -> Any:
|
|
115
|
+
"""
|
|
116
|
+
Accept an DRS issue visitor.
|
|
117
|
+
|
|
118
|
+
:param visitor: The DRS issue visitor.
|
|
119
|
+
:return: Depending on the visitor.
|
|
120
|
+
:rtype: Any
|
|
121
|
+
"""
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ParsingIssue(DrsIssue):
|
|
126
|
+
"""
|
|
127
|
+
Generic class for the DRS parsing issues.
|
|
128
|
+
"""
|
|
129
|
+
column: int|None = None
|
|
130
|
+
"""the column of faulty characters."""
|
|
131
|
+
|
|
132
|
+
@abstractmethod
|
|
133
|
+
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
134
|
+
"""
|
|
135
|
+
Accept an DRS parsing issue visitor.
|
|
136
|
+
|
|
137
|
+
:param visitor: The DRS parsing issue visitor.
|
|
138
|
+
:type visitor: ParsingIssueVisitor
|
|
139
|
+
:return: Depending on the visitor.
|
|
140
|
+
:rtype: Any
|
|
141
|
+
"""
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class Space(ParsingIssue):
|
|
146
|
+
"""
|
|
147
|
+
Represents a problem of unnecessary space[s] at the beginning or end of the DRS expression.
|
|
148
|
+
Note: `column` is `None`.
|
|
149
|
+
"""
|
|
150
|
+
kind: Literal[IssueKind.SPACE] = IssueKind.SPACE
|
|
151
|
+
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
152
|
+
return visitor.visit_space_issue(self)
|
|
153
|
+
def __str__(self):
|
|
154
|
+
return "expression is surrounded by white space[s]"
|
|
155
|
+
def __repr__(self) -> str:
|
|
156
|
+
return self.__str__()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class Unparsable(ParsingIssue):
|
|
160
|
+
"""
|
|
161
|
+
Represents a problem of non-compliance of the DRS expression.
|
|
162
|
+
Note: `column` is `None`.
|
|
163
|
+
"""
|
|
164
|
+
expected_drs_type: DrsType
|
|
165
|
+
"""The expected DRS type of the expression (directory, file name or dataset id)."""
|
|
166
|
+
kind: Literal[IssueKind.UNPARSABLE] = IssueKind.UNPARSABLE
|
|
167
|
+
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
168
|
+
return visitor.visit_unparsable_issue(self)
|
|
169
|
+
def __str__(self):
|
|
170
|
+
return "unable to parse this expression"
|
|
171
|
+
def __repr__(self) -> str:
|
|
172
|
+
return self.__str__()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ExtraSeparator(ParsingIssue):
|
|
176
|
+
"""
|
|
177
|
+
Represents a problem of multiple separator occurrences in the DRS expression.
|
|
178
|
+
"""
|
|
179
|
+
kind: Literal[IssueKind.EXTRA_SEPARATOR] = IssueKind.EXTRA_SEPARATOR
|
|
180
|
+
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
181
|
+
return visitor.visit_extra_separator_issue(self)
|
|
182
|
+
def __str__(self):
|
|
183
|
+
return f"extra separator(s) at column {self.column}"
|
|
184
|
+
def __repr__(self) -> str:
|
|
185
|
+
return self.__str__()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class ExtraChar(ParsingIssue):
|
|
189
|
+
"""
|
|
190
|
+
Represents a problem of extra characters at the end of the DRS expression.
|
|
191
|
+
"""
|
|
192
|
+
kind: Literal[IssueKind.EXTRA_CHAR] = IssueKind.EXTRA_CHAR
|
|
193
|
+
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
194
|
+
return visitor.visit_extra_char_issue(self)
|
|
195
|
+
def __str__(self):
|
|
196
|
+
return f"extra character(s) at column {self.column}"
|
|
197
|
+
def __repr__(self) -> str:
|
|
198
|
+
return self.__str__()
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class BlankTerm(ParsingIssue):
|
|
202
|
+
"""
|
|
203
|
+
Represents a problem of blank term in the DRS expression (i.e., space[s] surrounded by separators).
|
|
204
|
+
"""
|
|
205
|
+
kind: Literal[IssueKind.BLANK_TERM] = IssueKind.BLANK_TERM
|
|
206
|
+
def accept(self, visitor: ParsingIssueVisitor) -> Any:
|
|
207
|
+
return visitor.visit_blank_term_issue(self)
|
|
208
|
+
def __str__(self):
|
|
209
|
+
return f"blank term at column {self.column}"
|
|
210
|
+
def __repr__(self) -> str:
|
|
211
|
+
return self.__str__()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class ComplianceIssue(DrsIssue):
|
|
215
|
+
"""
|
|
216
|
+
Generic class for the compliance issues.
|
|
217
|
+
"""
|
|
218
|
+
@abstractmethod
|
|
219
|
+
def accept(self, visitor: ComplianceIssueVisitor) -> Any:
|
|
220
|
+
"""
|
|
221
|
+
Accept an DRS compliance issue visitor.
|
|
222
|
+
|
|
223
|
+
:param visitor: The DRS compliance issue visitor.
|
|
224
|
+
:type visitor: ComplianceIssueVisitor
|
|
225
|
+
:return: Depending on the visitor.
|
|
226
|
+
:rtype: Any
|
|
227
|
+
"""
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class FileNameExtensionIssue(ComplianceIssue):
|
|
232
|
+
"""
|
|
233
|
+
Represents a problem on the given file name extension (missing or not compliant).
|
|
234
|
+
"""
|
|
235
|
+
expected_extension: str
|
|
236
|
+
"""The expected file name extension."""
|
|
237
|
+
kind: Literal[IssueKind.FILE_NAME] = IssueKind.FILE_NAME
|
|
238
|
+
def accept(self, visitor: ComplianceIssueVisitor) -> Any:
|
|
239
|
+
return visitor.visit_filename_extension_issue(self)
|
|
240
|
+
def __str__(self):
|
|
241
|
+
return f"filename extension missing or not compliant with '{self.expected_extension}'"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class TermIssue(ComplianceIssue):
|
|
245
|
+
"""
|
|
246
|
+
Generic class for the DRS term issues.
|
|
247
|
+
"""
|
|
248
|
+
term: str
|
|
249
|
+
"""The faulty term."""
|
|
250
|
+
term_position: int
|
|
251
|
+
"""The position of the faulty term (the part position, not the column of the characters."""
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class GenerationIssue(DrsIssue):
|
|
255
|
+
"""
|
|
256
|
+
Generic class for the DRS generation issues.
|
|
257
|
+
"""
|
|
258
|
+
@abstractmethod
|
|
259
|
+
def accept(self, visitor: GenerationIssueVisitor) -> Any:
|
|
260
|
+
"""
|
|
261
|
+
Accept an DRS generation issue visitor.
|
|
262
|
+
|
|
263
|
+
:param visitor: The DRS generation issue visitor.
|
|
264
|
+
:type visitor: GenerationIssueVisitor
|
|
265
|
+
:return: Depending on the visitor.
|
|
266
|
+
:rtype: Any
|
|
267
|
+
"""
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class InvalidTerm(TermIssue, GenerationIssue):
|
|
272
|
+
"""
|
|
273
|
+
Represents a problem of invalid term against a collection or a constant part of a DRS specification.
|
|
274
|
+
"""
|
|
275
|
+
collection_id_or_constant_value: str
|
|
276
|
+
"""The collection id or the constant part of a DRS specification."""
|
|
277
|
+
kind: Literal[IssueKind.INVALID_TERM] = IssueKind.INVALID_TERM
|
|
278
|
+
def accept(self, visitor: ComplianceIssueVisitor|GenerationIssueVisitor) -> Any:
|
|
279
|
+
return visitor.visit_invalid_term_issue(self)
|
|
280
|
+
def __str__(self):
|
|
281
|
+
return f"term '{self.term}' not compliant with {self.collection_id_or_constant_value} at position {self.term_position}"
|
|
282
|
+
def __repr__(self) -> str:
|
|
283
|
+
return self.__str__()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class ExtraTerm(TermIssue):
|
|
287
|
+
"""
|
|
288
|
+
Represents a problem of extra term at the end of the given DRS expression.
|
|
289
|
+
All part of the DRS specification have been processed and this term is not necessary
|
|
290
|
+
(`collection_id` is `None`) or it has been invalidated by an optional collection part
|
|
291
|
+
of the DRS specification (`collection_id` is set).
|
|
292
|
+
"""
|
|
293
|
+
collection_id: str|None
|
|
294
|
+
"""The optional collection id or `None`."""
|
|
295
|
+
kind: Literal[IssueKind.EXTRA_TERM] = IssueKind.EXTRA_TERM
|
|
296
|
+
def accept(self, visitor: ComplianceIssueVisitor) -> Any:
|
|
297
|
+
return visitor.visit_extra_term_issue(self)
|
|
298
|
+
def __str__(self):
|
|
299
|
+
repr = f"extra term {self.term}"
|
|
300
|
+
if self.collection_id:
|
|
301
|
+
repr += f" invalidated by the optional collection {self.collection_id}"
|
|
302
|
+
return repr + f" at position {self.term_position}"
|
|
303
|
+
def __repr__(self) -> str:
|
|
304
|
+
return self.__str__()
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class MissingTerm(ComplianceIssue, GenerationIssue):
|
|
308
|
+
"""
|
|
309
|
+
Represents a problem of missing term for a collection part of the DRS specification.
|
|
310
|
+
"""
|
|
311
|
+
collection_id: str
|
|
312
|
+
"""The collection id."""
|
|
313
|
+
collection_position: int
|
|
314
|
+
"""The collection part position (not the column of the characters)."""
|
|
315
|
+
kind: Literal[IssueKind.MISSING_TERM] = IssueKind.MISSING_TERM
|
|
316
|
+
def accept(self, visitor: ComplianceIssueVisitor|GenerationIssueVisitor) -> Any:
|
|
317
|
+
return visitor.visit_missing_term_issue(self)
|
|
318
|
+
def __str__(self):
|
|
319
|
+
return f'missing term for {self.collection_id} at position {self.collection_position}'
|
|
320
|
+
def __repr__(self) -> str:
|
|
321
|
+
return self.__str__()
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class TooManyTermCollection(GenerationIssue):
|
|
325
|
+
"""
|
|
326
|
+
Represents a problem while inferring a mapping collection - term in the generation
|
|
327
|
+
of a DRS expression based on a bag of terms. The problem is that more than one term
|
|
328
|
+
is able to match this collection. The generator is unable to choose from these terms
|
|
329
|
+
"""
|
|
330
|
+
collection_id: str
|
|
331
|
+
"""The collection id."""
|
|
332
|
+
terms: list[str]
|
|
333
|
+
"""The faulty terms."""
|
|
334
|
+
kind: Literal[IssueKind.TOO_MANY] = IssueKind.TOO_MANY
|
|
335
|
+
def accept(self, visitor: GenerationIssueVisitor) -> Any:
|
|
336
|
+
return visitor.visit_too_many_terms_collection_issue(self)
|
|
337
|
+
|
|
338
|
+
def __str__(self):
|
|
339
|
+
terms_str = ", ".join(term for term in self.terms)
|
|
340
|
+
result = f'collection {self.collection_id} has more than one term ({terms_str})'
|
|
341
|
+
return result
|
|
342
|
+
def __repr__(self) -> str:
|
|
343
|
+
return self.__str__()
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class ConflictingCollections(GenerationIssue):
|
|
347
|
+
"""
|
|
348
|
+
Represents a problem while inferring a mapping collection - term in the generation
|
|
349
|
+
of a DRS expression based on a bag of terms. The problem is that these collections shares the
|
|
350
|
+
very same terms. The generator is unable to choose which term for which collection.
|
|
351
|
+
"""
|
|
352
|
+
collection_ids: list[str]
|
|
353
|
+
"""The ids of the collections."""
|
|
354
|
+
terms: list[str]
|
|
355
|
+
"""The shared terms."""
|
|
356
|
+
kind: Literal[IssueKind.CONFLICT] = IssueKind.CONFLICT
|
|
357
|
+
def accept(self, visitor: GenerationIssueVisitor) -> Any:
|
|
358
|
+
return visitor.visit_conflicting_collections_issue(self)
|
|
359
|
+
def __str__(self):
|
|
360
|
+
collection_ids_str = ", ".join(collection_id for collection_id in self.collection_ids)
|
|
361
|
+
terms_str = ", ".join(term for term in self.terms)
|
|
362
|
+
result = f"collections {collection_ids_str} are competing for the same term(s) {terms_str}"
|
|
363
|
+
return result
|
|
364
|
+
def __repr__(self) -> str:
|
|
365
|
+
return self.__str__()
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class AssignedTerm(GenerationIssue):
|
|
369
|
+
"""
|
|
370
|
+
Represents a decision of the Generator to assign this term to the collection, that may not be.
|
|
371
|
+
relevant.
|
|
372
|
+
"""
|
|
373
|
+
collection_id: str
|
|
374
|
+
"""The collection id."""
|
|
375
|
+
term: str
|
|
376
|
+
"""The term."""
|
|
377
|
+
kind: Literal[IssueKind.ASSIGNED] = IssueKind.ASSIGNED
|
|
378
|
+
def accept(self, visitor: GenerationIssueVisitor) -> Any:
|
|
379
|
+
return visitor.visit_assign_term_issue(self)
|
|
380
|
+
def __str__(self):
|
|
381
|
+
result = f"assign term {self.term} for collection {self.collection_id}"
|
|
382
|
+
return result
|
|
383
|
+
def __repr__(self) -> str:
|
|
384
|
+
return self.__str__()
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
GenerationError = Annotated[AssignedTerm | ConflictingCollections | InvalidTerm | MissingTerm | \
|
|
388
|
+
TooManyTermCollection, Field(discriminator='kind')]
|
|
389
|
+
GenerationWarning = Annotated[AssignedTerm | MissingTerm, Field(discriminator='kind')]
|
|
390
|
+
|
|
391
|
+
ValidationError = Annotated[BlankTerm | ExtraChar | ExtraSeparator | ExtraTerm | \
|
|
392
|
+
FileNameExtensionIssue | InvalidTerm | MissingTerm | Space | Unparsable,
|
|
393
|
+
Field(discriminator='kind')]
|
|
394
|
+
ValidationWarning = Annotated[ExtraSeparator | MissingTerm | Space, Field(discriminator='kind')]
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class DrsReport(BaseModel):
|
|
398
|
+
"""
|
|
399
|
+
Generic DRS application report class.
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
project_id: str
|
|
403
|
+
"""The project id associated to the result of the DRS application."""
|
|
404
|
+
|
|
405
|
+
type: DrsType
|
|
406
|
+
"""The type of the DRS"""
|
|
407
|
+
|
|
408
|
+
errors: list
|
|
409
|
+
"""A list of DRS issues that are considered as errors."""
|
|
410
|
+
|
|
411
|
+
warnings: list
|
|
412
|
+
"""A list of DRS issues that are considered as warnings."""
|
|
413
|
+
|
|
414
|
+
@computed_field # type: ignore
|
|
415
|
+
@property
|
|
416
|
+
def nb_errors(self) -> int:
|
|
417
|
+
"""The number of errors."""
|
|
418
|
+
return len(self.errors) if self.errors else 0
|
|
419
|
+
|
|
420
|
+
@computed_field # type: ignore
|
|
421
|
+
@property
|
|
422
|
+
def nb_warnings(self) -> int:
|
|
423
|
+
"""The number of warnings."""
|
|
424
|
+
return len(self.warnings) if self.warnings else 0
|
|
425
|
+
|
|
426
|
+
@computed_field # type: ignore
|
|
427
|
+
@property
|
|
428
|
+
def validated(self) -> bool:
|
|
429
|
+
"""The correctness of the result of the DRS application."""
|
|
430
|
+
return False if self.errors else True
|
|
431
|
+
|
|
432
|
+
def __len__(self) -> int:
|
|
433
|
+
return self.nb_errors
|
|
434
|
+
|
|
435
|
+
def __bool__(self) -> bool:
|
|
436
|
+
return self.validated
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class DrsValidationReport(DrsReport):
|
|
440
|
+
"""
|
|
441
|
+
The DRS validation report class.
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
expression: str
|
|
445
|
+
"""The DRS expression been checked."""
|
|
446
|
+
|
|
447
|
+
errors: list[ValidationError]
|
|
448
|
+
"""A list of DRS parsing and compliance issues that are considered as errors."""
|
|
449
|
+
|
|
450
|
+
warnings: list[ValidationWarning]
|
|
451
|
+
"""A list of DRS parsing and compliance issues that are considered as warnings."""
|
|
452
|
+
|
|
453
|
+
def __str__(self) -> str:
|
|
454
|
+
return f"'{self.expression}' has {self.nb_errors} error(s) and " + \
|
|
455
|
+
f"{self.nb_warnings} warning(s)"
|
|
456
|
+
|
|
457
|
+
def __repr__(self) -> str:
|
|
458
|
+
return self.__str__()
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class DrsGenerationReport(DrsReport):
|
|
462
|
+
"""
|
|
463
|
+
The DRS generation report.
|
|
464
|
+
"""
|
|
465
|
+
|
|
466
|
+
MISSING_TAG: ClassVar[str] = '[MISSING]'
|
|
467
|
+
"""Tag used in the DRS generated expression to replace a missing term."""
|
|
468
|
+
|
|
469
|
+
INVALID_TAG: ClassVar[str] = '[INVALID]'
|
|
470
|
+
"""Tag used in the DRS generated expression to replace a invalid term."""
|
|
471
|
+
|
|
472
|
+
given_mapping_or_bag_of_terms: Mapping|Iterable
|
|
473
|
+
"""The mapping or the bag of terms given."""
|
|
474
|
+
|
|
475
|
+
mapping_used: Mapping
|
|
476
|
+
"""The mapping inferred from the given bag of terms (same mapping otherwise)."""
|
|
477
|
+
|
|
478
|
+
generated_drs_expression: str
|
|
479
|
+
"""The generated DRS expression with possible tags to replace missing or invalid terms."""
|
|
480
|
+
|
|
481
|
+
errors: list[GenerationError]
|
|
482
|
+
"""A list of DRS generation issues that are considered as errors."""
|
|
483
|
+
|
|
484
|
+
warnings: list[GenerationWarning]
|
|
485
|
+
"""A list of DRS generation issues that are considered as warnings."""
|
|
486
|
+
|
|
487
|
+
def __str__(self) -> str:
|
|
488
|
+
return f"'{self.generated_drs_expression}' has {self.nb_errors} error(s) and " + \
|
|
489
|
+
f"{self.nb_warnings} warning(s)"
|
|
490
|
+
|
|
491
|
+
def __repr__(self) -> str:
|
|
492
|
+
return self.__str__()
|