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,429 @@
1
+ from typing import Any, Iterable, Mapping, cast
2
+
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)
11
+ from esgvoc.apps.drs.validator import DrsApplication
12
+
13
+
14
+ def _get_first_item(items: set[Any]) -> Any:
15
+ result = None
16
+ for result in items:
17
+ break
18
+ return result
19
+
20
+
21
+ def _transform_set_and_sort(_set: set[Any]) -> list[Any]:
22
+ result = list(_set)
23
+ result.sort()
24
+ return result
25
+
26
+
27
+ class DrsGenerator(DrsApplication):
28
+ """
29
+ Generate a directory, dataset id and file name expression specified by the given project from
30
+ a mapping of collection ids and terms or an unordered bag of terms.
31
+ """
32
+
33
+ def generate_directory_from_mapping(self, mapping: Mapping[str, str]) -> DrsGenerationReport:
34
+ """
35
+ Generate a directory DRS expression from a mapping of collection ids and terms.
36
+
37
+ :param mapping: A mapping of collection ids (keys) and terms (values).
38
+ :type mapping: Mapping[str, str]
39
+ :returns: A generation report.
40
+ :rtype: DrsGeneratorReport
41
+ """
42
+ return self._generate_from_mapping(mapping, self.directory_specs)
43
+
44
+ def generate_directory_from_bag_of_terms(self, terms: Iterable[str]) -> DrsGenerationReport:
45
+ """
46
+ Generate a directory DRS expression from an unordered bag of terms.
47
+
48
+ :param terms: An unordered bag of terms.
49
+ :type terms: Iterable[str]
50
+ :returns: A generation report.
51
+ :rtype: DrsGeneratorReport
52
+ """
53
+ return self._generate_from_bag_of_terms(terms, self.directory_specs)
54
+
55
+ def generate_dataset_id_from_mapping(self, mapping: Mapping[str, str]) -> DrsGenerationReport:
56
+ """
57
+ Generate a dataset id DRS expression from a mapping of collection ids and terms.
58
+
59
+ :param mapping: A mapping of collection ids (keys) and terms (values).
60
+ :type mapping: Mapping[str, str]
61
+ :returns: A generation report.
62
+ :rtype: DrsGeneratorReport
63
+ """
64
+ return self._generate_from_mapping(mapping, self.dataset_id_specs)
65
+
66
+ def generate_dataset_id_from_bag_of_terms(self, terms: Iterable[str]) -> DrsGenerationReport:
67
+ """
68
+ Generate a dataset id DRS expression from an unordered bag of terms.
69
+
70
+ :param terms: An unordered bag of terms.
71
+ :type terms: Iterable[str]
72
+ :returns: A generation report.
73
+ :rtype: DrsGeneratorReport
74
+ """
75
+ return self._generate_from_bag_of_terms(terms, self.dataset_id_specs)
76
+
77
+
78
+ def generate_file_name_from_mapping(self, mapping: Mapping[str, str]) -> DrsGenerationReport:
79
+ """
80
+ Generate a file name DRS expression from a mapping of collection ids and terms.
81
+ The file name extension is append automatically, according to the DRS specification,
82
+ so none of the terms given must include the extension.
83
+
84
+ :param mapping: A mapping of collection ids (keys) and terms (values).
85
+ :type mapping: Mapping[str, str]
86
+ :returns: A generation report.
87
+ :rtype: DrsGeneratorReport
88
+ """
89
+ report = self._generate_from_mapping(mapping, self.file_name_specs)
90
+ report.generated_drs_expression = report.generated_drs_expression + \
91
+ self._get_full_file_name_extension()
92
+ return report
93
+
94
+ def generate_file_name_from_bag_of_terms(self, terms: Iterable[str]) -> DrsGenerationReport:
95
+ """
96
+ Generate a file name DRS expression from an unordered bag of terms.
97
+ The file name extension is append automatically, according to the DRS specification,
98
+ so none of the terms given must include the extension.
99
+
100
+ :param terms: An unordered bag of terms.
101
+ :type terms: Iterable[str]
102
+ :returns: A generation report.
103
+ :rtype: DrsGeneratorReport
104
+ """
105
+ report = self._generate_from_bag_of_terms(terms, self.file_name_specs)
106
+ report.generated_drs_expression = report.generated_drs_expression + \
107
+ self._get_full_file_name_extension()
108
+ return report
109
+
110
+ def generate_from_mapping(self, mapping: Mapping[str, str],
111
+ drs_type: DrsType|str) -> DrsGenerationReport:
112
+ """
113
+ Generate a DRS expression from a mapping of collection ids and terms.
114
+
115
+ :param mapping: A mapping of collection ids (keys) and terms (values).
116
+ :type mapping: Mapping[str, str]
117
+ :param drs_type: The type of the given DRS expression (directory, file_name or dataset_id)
118
+ :type drs_type: DrsType|str
119
+ :returns: A generation report.
120
+ :rtype: DrsGeneratorReport
121
+ """
122
+ match drs_type:
123
+ case DrsType.DIRECTORY:
124
+ return self.generate_directory_from_mapping(mapping=mapping)
125
+ case DrsType.FILE_NAME:
126
+ return self.generate_file_name_from_mapping(mapping=mapping)
127
+ case DrsType.DATASET_ID:
128
+ return self.generate_dataset_id_from_mapping(mapping=mapping)
129
+ case _:
130
+ raise RuntimeError(f'unsupported drs type {drs_type}')
131
+
132
+ def generate_from_bag_of_terms(self, terms: Iterable[str], drs_type: DrsType|str) \
133
+ -> DrsGenerationReport:
134
+ """
135
+ Generate a DRS expression from an unordered bag of terms.
136
+
137
+ :param terms: An unordered bag of terms.
138
+ :type terms: Iterable[str]
139
+ :param drs_type: The type of the given DRS expression (directory, file_name or dataset_id)
140
+ :type drs_type: DrsType|str
141
+ :returns: A generation report.
142
+ :rtype: DrsGeneratorReport
143
+ """
144
+ match drs_type:
145
+ case DrsType.DIRECTORY:
146
+ return self.generate_directory_from_bag_of_terms(terms=terms)
147
+ case DrsType.FILE_NAME:
148
+ return self.generate_file_name_from_bag_of_terms(terms=terms)
149
+ case DrsType.DATASET_ID:
150
+ return self.generate_dataset_id_from_bag_of_terms(terms=terms)
151
+ case _:
152
+ raise RuntimeError(f'unsupported drs type {drs_type}')
153
+
154
+
155
+ def _generate_from_mapping(self, mapping: Mapping[str, str], specs: DrsSpecification) \
156
+ -> DrsGenerationReport:
157
+ drs_expression, errors, warnings = self.__generate_from_mapping(mapping, specs, True)
158
+ if self.pedantic:
159
+ errors.extend(warnings)
160
+ warnings.clear()
161
+ 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))
167
+
168
+ def __generate_from_mapping(self, mapping: Mapping[str, str],
169
+ specs: DrsSpecification,
170
+ has_to_valid_terms: bool)\
171
+ -> tuple[str, list[GenerationIssue], list[GenerationIssue]]:
172
+ errors: list[GenerationIssue] = list()
173
+ warnings: list[GenerationIssue] = list()
174
+ drs_expression = ""
175
+ part_position: int = 0
176
+ for part in specs.parts:
177
+ part_position += 1
178
+ if part.kind == DrsPartKind.COLLECTION:
179
+ collection_part = cast(DrsCollection, part)
180
+ collection_id = collection_part.collection_id
181
+ if collection_id in mapping:
182
+ part_value = mapping[collection_id]
183
+ if has_to_valid_terms:
184
+ matching_terms = projects.valid_term_in_collection(part_value,
185
+ self.project_id,
186
+ collection_id)
187
+ if not matching_terms:
188
+ issue = InvalidTerm(term=part_value,
189
+ term_position=part_position,
190
+ collection_id_or_constant_value=collection_id)
191
+ errors.append(issue)
192
+ part_value = DrsGenerationReport.INVALID_TAG
193
+ else:
194
+ other_issue = MissingTerm(collection_id=collection_id,
195
+ collection_position=part_position)
196
+ if collection_part.is_required:
197
+ errors.append(other_issue)
198
+ part_value = DrsGenerationReport.MISSING_TAG
199
+ else:
200
+ warnings.append(other_issue)
201
+ continue # The for loop.
202
+ else:
203
+ constant_part = cast(DrsConstant, part)
204
+ part_value = constant_part.value
205
+
206
+ drs_expression += part_value + specs.separator
207
+
208
+ drs_expression = drs_expression[0:len(drs_expression)-len(specs.separator)]
209
+ return drs_expression, errors, warnings
210
+
211
+ def _generate_from_bag_of_terms(self, terms: Iterable[str], specs: DrsSpecification) \
212
+ -> DrsGenerationReport:
213
+ collection_terms_mapping: dict[str, set[str]] = dict()
214
+ for term in terms:
215
+ matching_terms = projects.valid_term_in_project(term, self.project_id)
216
+ for matching_term in matching_terms:
217
+ if matching_term.collection_id not in collection_terms_mapping:
218
+ collection_terms_mapping[matching_term.collection_id] = set()
219
+ collection_terms_mapping[matching_term.collection_id].add(term)
220
+ collection_terms_mapping, warnings = DrsGenerator._resolve_conflicts(collection_terms_mapping)
221
+ mapping, errors = DrsGenerator._check_collection_terms_mapping(collection_terms_mapping)
222
+ drs_expression, errs, warns = self.__generate_from_mapping(mapping, specs, False)
223
+ errors.extend(errs)
224
+ warnings.extend(warns)
225
+ if self.pedantic:
226
+ errors.extend(warnings)
227
+ warnings.clear()
228
+ 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
+
234
+ @staticmethod
235
+ def _resolve_conflicts(collection_terms_mapping: dict[str, set[str]]) \
236
+ -> tuple[dict[str, set[str]], list[GenerationIssue]]:
237
+ warnings: list[GenerationIssue] = list()
238
+ conflicting_collection_ids_list: list[list[str]] = list()
239
+ collection_ids: list[str] = list(collection_terms_mapping.keys())
240
+ len_collection_ids: int = len(collection_ids)
241
+
242
+ for l_collection_index in range(0, len_collection_ids - 1):
243
+ conflicting_collection_ids: list[str] = list()
244
+ 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]]):
247
+ continue
248
+ else:
249
+ not_registered = True
250
+ for cc_ids in conflicting_collection_ids_list:
251
+ if collection_ids[l_collection_index] in cc_ids and \
252
+ collection_ids[r_collection_index] in cc_ids:
253
+ not_registered = False
254
+ break
255
+ if not_registered:
256
+ conflicting_collection_ids.append(collection_ids[r_collection_index])
257
+ if conflicting_collection_ids:
258
+ conflicting_collection_ids.append(collection_ids[l_collection_index])
259
+ conflicting_collection_ids_list.append(conflicting_collection_ids)
260
+
261
+ # Each time a collection is resolved, we must restart the loop so as to check if others can be,
262
+ # until no progress is made.
263
+ while True:
264
+ # 1. Non-conflicting collections with only one term are assigned.
265
+ # Non-conflicting collections with more than one term will be raise an error
266
+ # in the _check method.
267
+
268
+ # Nothing to do.
269
+
270
+ # 2a. Collections with one term that are conflicting to each other will raise an error.
271
+ # We don't search for collection with more than one term which term sets are exactly
272
+ # the same, because we cannot choose which term will be removed in 2b.
273
+ # So stick with one term collections: those collection will be detected in method _check.
274
+ collection_ids_with_len_eq_1_list: list[list[str]] = list()
275
+ for collection_ids in conflicting_collection_ids_list:
276
+ tmp_conflicting_collection_ids: list[str] = list()
277
+ for collection_id in collection_ids:
278
+ if len(collection_terms_mapping[collection_id]) == 1:
279
+ tmp_conflicting_collection_ids.append(collection_id)
280
+ if len(tmp_conflicting_collection_ids) > 1:
281
+ collection_ids_with_len_eq_1_list.append(tmp_conflicting_collection_ids)
282
+ # 2b. As it is not possible to resolve collections sharing the same unique term:
283
+ # raise errors, remove the faulty collections and their term.
284
+ if collection_ids_with_len_eq_1_list:
285
+ for collection_ids_to_be_removed in collection_ids_with_len_eq_1_list:
286
+ DrsGenerator._remove_ids_from_conflicts(conflicting_collection_ids_list,
287
+ collection_ids_to_be_removed)
288
+ DrsGenerator._remove_term_from_other_term_sets(collection_terms_mapping,
289
+ collection_ids_to_be_removed)
290
+ # Every time conflicting_collection_ids_list is modified, we must restart the loop,
291
+ # as conflicting collections may be resolved.
292
+ continue
293
+
294
+ # 3.a For each collections with only one term, assign their term to the detriment of
295
+ # collections with more than one term.
296
+ wining_collection_ids: list[str] = list()
297
+ for collection_ids in conflicting_collection_ids_list:
298
+ for collection_id in collection_ids:
299
+ if len(collection_terms_mapping[collection_id]) == 1:
300
+ wining_collection_ids.append(collection_id)
301
+ term = _get_first_item(collection_terms_mapping[collection_id])
302
+ issue = AssignedTerm(collection_id=collection_id, term=term)
303
+ warnings.append(issue)
304
+ # 3.b Update conflicting collections.
305
+ if wining_collection_ids:
306
+ DrsGenerator._remove_ids_from_conflicts(conflicting_collection_ids_list,
307
+ wining_collection_ids)
308
+ DrsGenerator._remove_term_from_other_term_sets(collection_terms_mapping,
309
+ wining_collection_ids)
310
+ # Every time conflicting_collection_ids_list is modified, we must restart the loop,
311
+ # as conflicting collections may be resolved.
312
+ continue
313
+
314
+ # 4.a For each term set of the remaining conflicting collections, compute their difference.
315
+ # If the difference is one term, this term is assigned to the collection that owns it.
316
+ wining_id_and_term_pairs: list[tuple[str, str]] = list()
317
+ for collection_ids in conflicting_collection_ids_list:
318
+ for collection_index in range(0, len(collection_ids)):
319
+ 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
+ )
326
+ if len(diff) == 1:
327
+ wining_id_and_term_pairs.append((collection_ids[collection_index],
328
+ _get_first_item(diff)))
329
+ # 4.b Update conflicting collections.
330
+ if wining_id_and_term_pairs:
331
+ wining_collection_ids = list()
332
+ for collection_id, term in wining_id_and_term_pairs:
333
+ wining_collection_ids.append(collection_id)
334
+ collection_terms_mapping[collection_id].clear()
335
+ collection_terms_mapping[collection_id].add(term)
336
+ issue = AssignedTerm(collection_id=collection_id, term=term)
337
+ warnings.append(issue)
338
+ DrsGenerator._remove_ids_from_conflicts(conflicting_collection_ids_list,
339
+ wining_collection_ids)
340
+ DrsGenerator._remove_term_from_other_term_sets(collection_terms_mapping,
341
+ wining_collection_ids)
342
+ continue
343
+ else:
344
+ break # Stop the loop when no progress is made.
345
+ return collection_terms_mapping, warnings
346
+
347
+ @staticmethod
348
+ def _check_collection_terms_mapping(collection_terms_mapping: dict[str, set[str]]) \
349
+ -> tuple[dict[str, str], list[GenerationIssue]]:
350
+ errors: list[GenerationIssue] = list()
351
+ # 1. Looking for collections that share strictly the same term(s).
352
+ collection_ids: list[str] = list(collection_terms_mapping.keys())
353
+ len_collection_ids: int = len(collection_ids)
354
+ faulty_collections_list: list[set[str]] = list()
355
+ for l_collection_index in range(0, len_collection_ids - 1):
356
+ l_collection_id = collection_ids[l_collection_index]
357
+ l_term_set = collection_terms_mapping[l_collection_id]
358
+ for r_collection_index in range(l_collection_index + 1, len_collection_ids):
359
+ r_collection_id = collection_ids[r_collection_index]
360
+ 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
+ if l_term_set and (not l_term_set.difference(r_term_set)):
363
+ not_registered = True
364
+ for faulty_collections in faulty_collections_list:
365
+ if l_collection_id in faulty_collections or \
366
+ r_collection_id in faulty_collections:
367
+ faulty_collections.add(l_collection_id)
368
+ faulty_collections.add(r_collection_id)
369
+ not_registered = False
370
+ break
371
+ if not_registered:
372
+ faulty_collections_list.append({l_collection_id, r_collection_id})
373
+ for faulty_collections in faulty_collections_list:
374
+ terms = collection_terms_mapping[_get_first_item(faulty_collections)]
375
+ issue = ConflictingCollections(collection_ids=_transform_set_and_sort(faulty_collections),
376
+ terms=_transform_set_and_sort(terms))
377
+ errors.append(issue)
378
+ for collection_id in faulty_collections:
379
+ del collection_terms_mapping[collection_id]
380
+
381
+ # 2. Looking for collections with more than one term.
382
+ result: dict[str, str] = dict()
383
+ for collection_id, term_set in collection_terms_mapping.items():
384
+ len_term_set = len(term_set)
385
+ if len_term_set == 1:
386
+ result[collection_id] = _get_first_item(term_set)
387
+ elif len_term_set > 1:
388
+ other_issue = TooManyTermCollection(collection_id=collection_id,
389
+ terms=_transform_set_and_sort(term_set))
390
+ errors.append(other_issue)
391
+ #else: Don't add emptied collection to the result.
392
+ return result, errors
393
+
394
+ @staticmethod
395
+ def _remove_term_from_other_term_sets(collection_terms_mapping: dict[str, set[str]],
396
+ collection_ids_to_be_removed: list[str]) -> None:
397
+ for collection_id_to_be_removed in collection_ids_to_be_removed:
398
+ # Should only be one term.
399
+ term_to_be_removed: str = _get_first_item(collection_terms_mapping[collection_id_to_be_removed])
400
+ for collection_id in collection_terms_mapping.keys():
401
+ if (collection_id not in collection_ids_to_be_removed):
402
+ collection_terms_mapping[collection_id].discard(term_to_be_removed)
403
+
404
+ @staticmethod
405
+ def _remove_ids_from_conflicts(conflicting_collection_ids_list: list[list[str]],
406
+ collection_ids_to_be_removed: list[str]) -> None:
407
+ for collection_id_to_be_removed in collection_ids_to_be_removed:
408
+ for conflicting_collection_ids in conflicting_collection_ids_list:
409
+ if collection_id_to_be_removed in conflicting_collection_ids:
410
+ 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)