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.

Files changed (78) hide show
  1. esgvoc/__init__.py +3 -1
  2. esgvoc/api/__init__.py +30 -30
  3. esgvoc/api/_utils.py +28 -14
  4. esgvoc/api/data_descriptors/__init__.py +19 -10
  5. esgvoc/api/data_descriptors/activity.py +8 -45
  6. esgvoc/api/data_descriptors/area_label.py +6 -0
  7. esgvoc/api/data_descriptors/branded_suffix.py +5 -0
  8. esgvoc/api/data_descriptors/branded_variable.py +5 -0
  9. esgvoc/api/data_descriptors/consortium.py +16 -56
  10. esgvoc/api/data_descriptors/data_descriptor.py +106 -0
  11. esgvoc/api/data_descriptors/date.py +3 -46
  12. esgvoc/api/data_descriptors/directory_date.py +5 -0
  13. esgvoc/api/data_descriptors/experiment.py +19 -54
  14. esgvoc/api/data_descriptors/forcing_index.py +3 -45
  15. esgvoc/api/data_descriptors/frequency.py +6 -43
  16. esgvoc/api/data_descriptors/grid_label.py +6 -44
  17. esgvoc/api/data_descriptors/horizontal_label.py +6 -0
  18. esgvoc/api/data_descriptors/initialisation_index.py +3 -44
  19. esgvoc/api/data_descriptors/institution.py +11 -54
  20. esgvoc/api/data_descriptors/license.py +4 -44
  21. esgvoc/api/data_descriptors/mip_era.py +6 -44
  22. esgvoc/api/data_descriptors/model_component.py +7 -45
  23. esgvoc/api/data_descriptors/organisation.py +3 -40
  24. esgvoc/api/data_descriptors/physic_index.py +3 -45
  25. esgvoc/api/data_descriptors/product.py +4 -43
  26. esgvoc/api/data_descriptors/realisation_index.py +3 -44
  27. esgvoc/api/data_descriptors/realm.py +4 -42
  28. esgvoc/api/data_descriptors/resolution.py +6 -44
  29. esgvoc/api/data_descriptors/source.py +18 -53
  30. esgvoc/api/data_descriptors/source_type.py +3 -41
  31. esgvoc/api/data_descriptors/sub_experiment.py +3 -41
  32. esgvoc/api/data_descriptors/table.py +6 -48
  33. esgvoc/api/data_descriptors/temporal_label.py +6 -0
  34. esgvoc/api/data_descriptors/time_range.py +3 -27
  35. esgvoc/api/data_descriptors/variable.py +13 -71
  36. esgvoc/api/data_descriptors/variant_label.py +3 -47
  37. esgvoc/api/data_descriptors/vertical_label.py +5 -0
  38. esgvoc/api/project_specs.py +82 -0
  39. esgvoc/api/projects.py +284 -238
  40. esgvoc/api/report.py +89 -52
  41. esgvoc/api/search.py +31 -11
  42. esgvoc/api/universe.py +57 -48
  43. esgvoc/apps/__init__.py +6 -0
  44. esgvoc/apps/drs/__init__.py +0 -16
  45. esgvoc/apps/drs/constants.py +2 -0
  46. esgvoc/apps/drs/generator.py +429 -0
  47. esgvoc/apps/drs/report.py +492 -0
  48. esgvoc/apps/drs/validator.py +330 -0
  49. esgvoc/cli/drs.py +248 -0
  50. esgvoc/cli/get.py +26 -25
  51. esgvoc/cli/install.py +11 -8
  52. esgvoc/cli/main.py +4 -5
  53. esgvoc/cli/status.py +14 -2
  54. esgvoc/cli/valid.py +41 -45
  55. esgvoc/core/db/models/mixins.py +7 -0
  56. esgvoc/core/db/models/project.py +3 -8
  57. esgvoc/core/db/models/universe.py +3 -3
  58. esgvoc/core/db/project_ingestion.py +4 -1
  59. esgvoc/core/db/universe_ingestion.py +8 -7
  60. esgvoc/core/logging_handler.py +1 -1
  61. esgvoc/core/repo_fetcher.py +4 -3
  62. esgvoc/core/service/__init__.py +37 -5
  63. esgvoc/core/service/configuration/config_manager.py +188 -0
  64. esgvoc/core/service/configuration/setting.py +88 -0
  65. esgvoc/core/service/state.py +66 -42
  66. esgvoc-0.3.0.dist-info/METADATA +89 -0
  67. esgvoc-0.3.0.dist-info/RECORD +78 -0
  68. esgvoc-0.3.0.dist-info/licenses/LICENSE.txt +519 -0
  69. esgvoc/apps/drs/models.py +0 -43
  70. esgvoc/apps/drs/parser.py +0 -27
  71. esgvoc/cli/config.py +0 -79
  72. esgvoc/core/service/settings.py +0 -64
  73. esgvoc/core/service/settings.toml +0 -12
  74. esgvoc/core/service/settings_default.toml +0 -20
  75. esgvoc-0.1.2.dist-info/METADATA +0 -54
  76. esgvoc-0.1.2.dist-info/RECORD +0 -66
  77. {esgvoc-0.1.2.dist-info → esgvoc-0.3.0.dist-info}/WHEEL +0 -0
  78. {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__()