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