esgvoc 0.3.0__py3-none-any.whl → 1.0.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 (87) hide show
  1. esgvoc/__init__.py +1 -1
  2. esgvoc/api/__init__.py +95 -60
  3. esgvoc/api/data_descriptors/__init__.py +50 -28
  4. esgvoc/api/data_descriptors/activity.py +3 -3
  5. esgvoc/api/data_descriptors/area_label.py +16 -1
  6. esgvoc/api/data_descriptors/branded_suffix.py +20 -0
  7. esgvoc/api/data_descriptors/branded_variable.py +12 -0
  8. esgvoc/api/data_descriptors/consortium.py +14 -13
  9. esgvoc/api/data_descriptors/contact.py +5 -0
  10. esgvoc/api/data_descriptors/conventions.py +6 -0
  11. esgvoc/api/data_descriptors/creation_date.py +5 -0
  12. esgvoc/api/data_descriptors/data_descriptor.py +14 -9
  13. esgvoc/api/data_descriptors/data_specs_version.py +5 -0
  14. esgvoc/api/data_descriptors/date.py +1 -1
  15. esgvoc/api/data_descriptors/directory_date.py +1 -1
  16. esgvoc/api/data_descriptors/experiment.py +13 -11
  17. esgvoc/api/data_descriptors/forcing_index.py +1 -1
  18. esgvoc/api/data_descriptors/frequency.py +3 -3
  19. esgvoc/api/data_descriptors/further_info_url.py +5 -0
  20. esgvoc/api/data_descriptors/grid_label.py +2 -2
  21. esgvoc/api/data_descriptors/horizontal_label.py +15 -1
  22. esgvoc/api/data_descriptors/initialisation_index.py +1 -1
  23. esgvoc/api/data_descriptors/institution.py +8 -5
  24. esgvoc/api/data_descriptors/known_branded_variable.py +23 -0
  25. esgvoc/api/data_descriptors/license.py +3 -3
  26. esgvoc/api/data_descriptors/mip_era.py +1 -1
  27. esgvoc/api/data_descriptors/model_component.py +1 -1
  28. esgvoc/api/data_descriptors/obs_type.py +5 -0
  29. esgvoc/api/data_descriptors/organisation.py +1 -1
  30. esgvoc/api/data_descriptors/physic_index.py +1 -1
  31. esgvoc/api/data_descriptors/product.py +2 -2
  32. esgvoc/api/data_descriptors/publication_status.py +5 -0
  33. esgvoc/api/data_descriptors/realisation_index.py +1 -1
  34. esgvoc/api/data_descriptors/realm.py +1 -1
  35. esgvoc/api/data_descriptors/region.py +5 -0
  36. esgvoc/api/data_descriptors/resolution.py +3 -3
  37. esgvoc/api/data_descriptors/source.py +9 -5
  38. esgvoc/api/data_descriptors/source_type.py +1 -1
  39. esgvoc/api/data_descriptors/table.py +3 -2
  40. esgvoc/api/data_descriptors/temporal_label.py +15 -1
  41. esgvoc/api/data_descriptors/time_range.py +4 -3
  42. esgvoc/api/data_descriptors/title.py +5 -0
  43. esgvoc/api/data_descriptors/tracking_id.py +5 -0
  44. esgvoc/api/data_descriptors/variable.py +25 -12
  45. esgvoc/api/data_descriptors/variant_label.py +3 -3
  46. esgvoc/api/data_descriptors/vertical_label.py +14 -0
  47. esgvoc/api/project_specs.py +120 -4
  48. esgvoc/api/projects.py +733 -505
  49. esgvoc/api/py.typed +0 -0
  50. esgvoc/api/report.py +12 -8
  51. esgvoc/api/search.py +168 -98
  52. esgvoc/api/universe.py +368 -157
  53. esgvoc/apps/drs/constants.py +1 -1
  54. esgvoc/apps/drs/generator.py +51 -69
  55. esgvoc/apps/drs/report.py +60 -15
  56. esgvoc/apps/drs/validator.py +60 -71
  57. esgvoc/apps/jsg/cmip6_template.json +74 -0
  58. esgvoc/apps/jsg/cmip6plus_template.json +74 -0
  59. esgvoc/apps/jsg/json_schema_generator.py +185 -0
  60. esgvoc/apps/py.typed +0 -0
  61. esgvoc/cli/config.py +500 -0
  62. esgvoc/cli/drs.py +3 -2
  63. esgvoc/cli/find.py +138 -0
  64. esgvoc/cli/get.py +46 -38
  65. esgvoc/cli/main.py +10 -3
  66. esgvoc/cli/status.py +27 -18
  67. esgvoc/cli/valid.py +10 -15
  68. esgvoc/core/constants.py +1 -1
  69. esgvoc/core/db/__init__.py +2 -4
  70. esgvoc/core/db/connection.py +5 -3
  71. esgvoc/core/db/models/project.py +57 -15
  72. esgvoc/core/db/models/universe.py +49 -10
  73. esgvoc/core/db/project_ingestion.py +79 -65
  74. esgvoc/core/db/universe_ingestion.py +71 -40
  75. esgvoc/core/exceptions.py +33 -0
  76. esgvoc/core/logging_handler.py +24 -2
  77. esgvoc/core/repo_fetcher.py +61 -59
  78. esgvoc/core/service/data_merger.py +47 -34
  79. esgvoc/core/service/state.py +107 -83
  80. {esgvoc-0.3.0.dist-info → esgvoc-1.0.0.dist-info}/METADATA +7 -20
  81. esgvoc-1.0.0.dist-info/RECORD +95 -0
  82. esgvoc/api/_utils.py +0 -53
  83. esgvoc/core/logging.conf +0 -21
  84. esgvoc-0.3.0.dist-info/RECORD +0 -78
  85. {esgvoc-0.3.0.dist-info → esgvoc-1.0.0.dist-info}/WHEEL +0 -0
  86. {esgvoc-0.3.0.dist-info → esgvoc-1.0.0.dist-info}/entry_points.txt +0 -0
  87. {esgvoc-0.3.0.dist-info → esgvoc-1.0.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -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 (DrsCollection, DrsConstant, DrsPartKind,
5
- DrsSpecification, DrsType)
6
- from esgvoc.apps.drs.report import (AssignedTerm, ConflictingCollections,
7
- DrsGenerationReport, GenerationError,
8
- GenerationIssue, GenerationWarning,
9
- InvalidTerm, MissingTerm,
10
- TooManyTermCollection)
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 RuntimeError(f'unsupported drs type {drs_type}')
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
- -> DrsGenerationReport:
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 RuntimeError(f'unsupported drs type {drs_type}')
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
- -> DrsGenerationReport:
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
- given_mapping_or_bag_of_terms=mapping,
163
- mapping_used=mapping,
164
- generated_drs_expression=drs_expression,
165
- errors=cast(list[GenerationError], errors),
166
- warnings=cast(list[GenerationWarning], warnings))
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
- -> tuple[str, list[GenerationIssue], list[GenerationIssue]]:
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
- term_position=part_position,
190
- collection_id_or_constant_value=collection_id)
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
- collection_position=part_position)
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 # The for loop.
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
- -> DrsGenerationReport:
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
- given_mapping_or_bag_of_terms=terms,
230
- mapping_used=mapping,generated_drs_expression=drs_expression,
231
- errors=cast(list[GenerationError], errors),
232
- warnings=cast(list[GenerationWarning], warnings))
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
- -> tuple[dict[str, set[str]], list[GenerationIssue]]:
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
- (collection_terms_mapping[collection_ids[r_collection_index]]):
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
- collection_ids_to_be_removed)
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
- wining_collection_ids)
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
- *[collection_terms_mapping[index]
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 Stop the loop when no progress is made.
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
- -> tuple[dict[str, str], list[GenerationIssue]]:
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
- # check if the set is empty because the difference will always be an empty set!
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
- terms=_transform_set_and_sort(term_set))
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 (Annotated, Any, ClassVar, Iterable, Literal, Mapping,
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
- def accept(self, visitor: ComplianceIssueVisitor|GenerationIssueVisitor) -> Any:
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 position {self.term_position}"
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
- def accept(self, visitor: ComplianceIssueVisitor|GenerationIssueVisitor) -> Any:
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 = Annotated[AssignedTerm | ConflictingCollections | InvalidTerm | MissingTerm | \
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
- FileNameExtensionIssue | InvalidTerm | MissingTerm | Space | Unparsable,
393
- Field(discriminator='kind')]
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 # type: ignore
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 # type: ignore
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 # type: ignore
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