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,312 @@
1
+ from typing import cast
2
+
3
+ import esgvoc.api.projects as projects
4
+ import esgvoc.apps.drs.constants as constants
5
+ from esgvoc.api.project_specs import (
6
+ DrsPart,
7
+ DrsSpecification,
8
+ DrsType,
9
+ ProjectSpecs,
10
+ )
11
+ from esgvoc.apps.drs.report import (
12
+ BlankTerm,
13
+ ComplianceIssue,
14
+ DrsIssue,
15
+ DrsValidationReport,
16
+ ExtraChar,
17
+ ExtraSeparator,
18
+ ExtraTerm,
19
+ FileNameExtensionIssue,
20
+ InvalidTerm,
21
+ MissingTerm,
22
+ ParsingIssue,
23
+ Space,
24
+ Unparsable,
25
+ ValidationError,
26
+ ValidationWarning,
27
+ )
28
+ from esgvoc.core.exceptions import EsgvocDbError, EsgvocNotFoundError
29
+
30
+
31
+ class DrsApplication:
32
+ """
33
+ Generic DRS application class.
34
+ """
35
+
36
+ def __init__(self, project_id: str, pedantic: bool = False) -> None:
37
+ self.project_id: str = project_id
38
+ """The project id."""
39
+ self.pedantic: bool = pedantic
40
+ """Same as the option of GCC: turn warnings into errors. Default False."""
41
+ project_specs: ProjectSpecs | None = projects.get_project(project_id)
42
+ if not project_specs or project_specs.drs_specs is None:
43
+ raise EsgvocNotFoundError(f"unable to find project spec or only drs_spec for '{project_id}'")
44
+ self.directory_specs: DrsSpecification = project_specs.drs_specs[DrsType.DIRECTORY]
45
+ """The DRS directory specs of the project."""
46
+ self.file_name_specs: DrsSpecification = project_specs.drs_specs[DrsType.FILE_NAME]
47
+ """The DRS file name specs of the project."""
48
+ self.dataset_id_specs: DrsSpecification = project_specs.drs_specs[DrsType.DATASET_ID]
49
+ """The DRS dataset id specs of the project."""
50
+
51
+ def _get_full_file_name_extension(self) -> str:
52
+ """
53
+ Returns the full file name extension (the separator plus the extension) of the DRS file
54
+ name specs of the project.
55
+
56
+ :returns: The full file name extension.
57
+ :rtype: str
58
+ """
59
+ specs: DrsSpecification = self.file_name_specs
60
+ if specs.properties:
61
+ full_extension = (
62
+ specs.properties[constants.FILE_NAME_EXTENSION_SEPARATOR_KEY]
63
+ + specs.properties[constants.FILE_NAME_EXTENSION_KEY]
64
+ )
65
+ else:
66
+ raise EsgvocDbError(
67
+ "missing properties in the DRS file name specifications of the " + f"project '{self.project_id}'"
68
+ )
69
+ return full_extension
70
+
71
+
72
+ class DrsValidator(DrsApplication):
73
+ """
74
+ Valid a DRS directory, dataset id and file name expression against a project.
75
+ """
76
+
77
+ def validate_directory(self, drs_expression: str, prefix: str | None = None) -> DrsValidationReport:
78
+ """
79
+ Validate a DRS directory expression.
80
+
81
+ :param drs_expression: A DRS directory expression.
82
+ :type drs_expression: str
83
+ :param prefix: A directory prefix to be removed from the directory expression.
84
+ :type prefix: str|None
85
+ :returns: A validation report.
86
+ :rtype: DrsValidationReport
87
+ """
88
+ if prefix:
89
+ # Remove prefix if present. Always returns a copy.
90
+ drs_expression = drs_expression.removeprefix(prefix)
91
+ return self._validate(drs_expression, self.directory_specs)
92
+
93
+ def validate_dataset_id(self, drs_expression: str) -> DrsValidationReport:
94
+ """
95
+ Validate a DRS dataset id expression.
96
+
97
+ :param drs_expression: A DRS dataset id expression.
98
+ :type drs_expression: str
99
+ :returns: A validation report.
100
+ :rtype: DrsValidationReport
101
+ """
102
+ return self._validate(drs_expression, self.dataset_id_specs)
103
+
104
+ def validate_file_name(self, drs_expression: str) -> DrsValidationReport:
105
+ """
106
+ Validate a file name expression.
107
+
108
+ :param drs_expression: A DRS file name expression.
109
+ :type drs_expression: str
110
+ :returns: A validation report.
111
+ :rtype: DrsValidationReport
112
+ """
113
+ full_extension = self._get_full_file_name_extension()
114
+ if drs_expression.endswith(full_extension):
115
+ drs_expression = drs_expression.replace(full_extension, "")
116
+ result = self._validate(drs_expression, self.file_name_specs)
117
+ else:
118
+ issue = FileNameExtensionIssue(expected_extension=full_extension)
119
+ result = self._create_report(self.file_name_specs.type, drs_expression, [issue], [])
120
+ return result
121
+
122
+ def validate(self, drs_expression: str, drs_type: DrsType | str) -> DrsValidationReport:
123
+ """
124
+ Validate a DRS expression.
125
+
126
+ :param drs_expression: A DRS expression.
127
+ :type drs_expression: str
128
+ :param drs_type: The type of the given DRS expression (directory, file_name or dataset_id)
129
+ :type drs_type: DrsType|str
130
+ :returns: A validation report.
131
+ :rtype: DrsValidationReport
132
+ """
133
+ match drs_type:
134
+ case DrsType.DIRECTORY:
135
+ return self.validate_directory(drs_expression=drs_expression)
136
+ case DrsType.FILE_NAME:
137
+ return self.validate_file_name(drs_expression=drs_expression)
138
+ case DrsType.DATASET_ID:
139
+ return self.validate_dataset_id(drs_expression=drs_expression)
140
+ case _:
141
+ raise EsgvocDbError(f"unsupported drs type '{drs_type}'")
142
+
143
+ def _parse(
144
+ self, drs_expression: str, separator: str, drs_type: DrsType
145
+ ) -> tuple[
146
+ list[str] | None, # terms
147
+ list[DrsIssue], # Errors
148
+ list[DrsIssue],
149
+ ]: # Warnings
150
+ errors: list[DrsIssue] = list()
151
+ warnings: list[DrsIssue] = list()
152
+ cursor_offset = 0
153
+ # Spaces at the beginning/end of expression:
154
+ start_with_space = drs_expression[0].isspace()
155
+ end_with_space = drs_expression[-1].isspace()
156
+ if start_with_space or end_with_space:
157
+ issue: ParsingIssue = Space()
158
+ if self.pedantic:
159
+ errors.append(issue)
160
+ else:
161
+ warnings.append(issue)
162
+ if start_with_space:
163
+ previous_len = len(drs_expression)
164
+ drs_expression = drs_expression.lstrip()
165
+ cursor_offset = previous_len - len(drs_expression)
166
+ if end_with_space:
167
+ drs_expression = drs_expression.rstrip()
168
+ terms = drs_expression.split(separator)
169
+ if len(terms) < 2:
170
+ errors.append(Unparsable(expected_drs_type=drs_type))
171
+ return None, errors, warnings # Early exit
172
+ max_term_index = len(terms)
173
+ cursor_position = initial_cursor_position = len(drs_expression) + 1
174
+ has_white_term = False
175
+ for index in range(max_term_index - 1, -1, -1):
176
+ term = terms[index]
177
+ if (is_white_term := term.isspace()) or (not term):
178
+ has_white_term = has_white_term or is_white_term
179
+ cursor_position -= len(term) + 1
180
+ del terms[index]
181
+ continue
182
+ else:
183
+ break
184
+ if cursor_position != initial_cursor_position:
185
+ max_term_index = len(terms)
186
+ column = cursor_position + cursor_offset
187
+ if (drs_type == DrsType.DIRECTORY) and (not has_white_term):
188
+ issue = ExtraSeparator(column=column)
189
+ if self.pedantic:
190
+ errors.append(issue)
191
+ else:
192
+ warnings.append(issue)
193
+ else:
194
+ issue = ExtraChar(column=column)
195
+ errors.append(issue)
196
+ for index in range(max_term_index - 1, -1, -1):
197
+ term = terms[index]
198
+ len_term = len(term)
199
+ if not term:
200
+ column = cursor_position + cursor_offset
201
+ issue = ExtraSeparator(column=column)
202
+ if self.pedantic or drs_type != DrsType.DIRECTORY or index == 0:
203
+ errors.append(issue)
204
+ else:
205
+ warnings.append(issue)
206
+ del terms[index]
207
+ if term.isspace():
208
+ column = cursor_position + cursor_offset - len_term
209
+ issue = BlankTerm(column=column)
210
+ errors.append(issue)
211
+ del terms[index]
212
+ cursor_position -= len_term + 1
213
+
214
+ # Mypy doesn't understand that ParsingIssues are DrsIssues...
215
+ sorted_errors = DrsValidator._sort_parser_issues(errors) # type: ignore
216
+ sorted_warnings = DrsValidator._sort_parser_issues(warnings) # type: ignore
217
+ return terms, sorted_errors, sorted_warnings # type: ignore
218
+
219
+ @staticmethod
220
+ def _sort_parser_issues(issues: list[ParsingIssue]) -> list[ParsingIssue]:
221
+ return sorted(issues, key=lambda issue: issue.column if issue.column else 0)
222
+
223
+ def _validate_term(self, term: str, part: DrsPart) -> bool:
224
+ if part.source_collection_term is None:
225
+ matching_terms = projects.valid_term_in_collection(term, self.project_id, part.source_collection)
226
+ if len(matching_terms) > 0:
227
+ return True
228
+ else:
229
+ return False
230
+ else:
231
+ return projects.valid_term(
232
+ term, self.project_id, part.source_collection, part.source_collection_term
233
+ ).validated
234
+
235
+ def _create_report(
236
+ self,
237
+ type: DrsType,
238
+ drs_expression: str,
239
+ errors: list[DrsIssue],
240
+ warnings: list[DrsIssue],
241
+ mapping_used: dict[str, str] | None = None,
242
+ ) -> DrsValidationReport:
243
+ if mapping_used is None:
244
+ mapping_used = {}
245
+ return DrsValidationReport(
246
+ project_id=self.project_id,
247
+ type=type,
248
+ expression=drs_expression,
249
+ mapping_used=mapping_used,
250
+ errors=cast(list[ValidationError], errors),
251
+ warnings=cast(list[ValidationWarning], warnings),
252
+ )
253
+
254
+ def _validate(self, drs_expression: str, specs: DrsSpecification) -> DrsValidationReport:
255
+ terms, errors, warnings = self._parse(drs_expression, specs.separator, specs.type)
256
+ if not terms:
257
+ # Early exit.
258
+ return self._create_report(specs.type, drs_expression, errors, warnings)
259
+ term_index = 0
260
+ term_max_index = len(terms)
261
+ part_index = 0
262
+ part_max_index = len(specs.parts)
263
+ matching_code_mapping = dict()
264
+ mapping_used: dict[str, str] = dict()
265
+ while part_index < part_max_index:
266
+ term = terms[term_index]
267
+ part: DrsPart = specs.parts[part_index]
268
+ if self._validate_term(term, part):
269
+ term_index += 1
270
+ part_index += 1
271
+ matching_code_mapping[part.__str__()] = 0
272
+ mapping_used[part.source_collection] = term
273
+ elif part.is_required:
274
+ issue: ComplianceIssue = InvalidTerm(
275
+ term=term, term_position=term_index + 1, collection_id_or_constant_value=str(part)
276
+ )
277
+ errors.append(issue)
278
+ matching_code_mapping[part.__str__()] = 1
279
+ term_index += 1
280
+ part_index += 1
281
+ else: # The part is not required so try to match the term with the next part.
282
+ part_index += 1
283
+ matching_code_mapping[part.__str__()] = -1
284
+ if term_index == term_max_index:
285
+ break
286
+ # Cases:
287
+ # - All terms and collections have been processed.
288
+ # - Not enough term to process all collections.
289
+ # - Extra terms left whereas all collections have been processed:
290
+ # + The last collections are required => report extra terms.
291
+ # + The last collections are not required and these terms were not validated by them.
292
+ # => Should report error even if the collections are not required.
293
+ if part_index < part_max_index: # Missing terms.
294
+ for index in range(part_index, part_max_index):
295
+ part = specs.parts[index]
296
+ issue = MissingTerm(collection_id=str(part), collection_position=index + 1)
297
+ if part.is_required:
298
+ errors.append(issue)
299
+ else:
300
+ warnings.append(issue)
301
+ elif term_index < term_max_index: # Extra terms.
302
+ part_index -= term_max_index - term_index
303
+ for index in range(term_index, term_max_index):
304
+ term = terms[index]
305
+ part = specs.parts[part_index]
306
+ if (not part.is_required) and matching_code_mapping[part.__str__()] < 0: # noqa E125
307
+ issue = ExtraTerm(term=term, term_position=index, collection_id=str(part))
308
+ else:
309
+ issue = ExtraTerm(term=term, term_position=index, collection_id=None)
310
+ errors.append(issue)
311
+ part_index += 1
312
+ return self._create_report(specs.type, drs_expression, errors, warnings, mapping_used)
@@ -0,0 +1,104 @@
1
+ """GA application for global attributes validation for netCDF files.
2
+
3
+ This package provides tools for validating NetCDF global attributes against
4
+ project specifications (like CMIP6, CMIP7) using controlled vocabularies
5
+ from the esgvoc API.
6
+
7
+ Key Features:
8
+ - YAML-based configuration for attribute specifications
9
+ - Integration with esgvoc controlled vocabularies
10
+ - NetCDF header parsing from ncdump output
11
+ - Comprehensive validation reporting
12
+ - Support for different project specifications
13
+
14
+ Example Usage:
15
+ ```python
16
+ from esgvoc.apps.ga import GAValidator, validate_netcdf_attributes
17
+
18
+ # Quick validation from ncdump output
19
+ report = validate_netcdf_attributes(
20
+ ncdump_output=ncdump_text,
21
+ project_id="cmip6",
22
+ filename="my_file.nc"
23
+ )
24
+
25
+ print(f"Validation result: {'PASS' if report.is_valid else 'FAIL'}")
26
+ print(f"Errors: {report.error_count}, Warnings: {report.warning_count}")
27
+
28
+ # Or use the full validator class
29
+ validator = GAValidator(project_id="cmip6")
30
+ report = validator.validate_from_ncdump(ncdump_text)
31
+
32
+ # Get detailed validation summary
33
+ from esgvoc.apps.ga import create_validation_summary
34
+ print(create_validation_summary(report))
35
+ ```
36
+
37
+ Advanced Usage:
38
+ ```python
39
+ from esgvoc.apps.ga.models import NetCDFHeader, NetCDFHeaderParser
40
+
41
+ # Parse NetCDF header from ncdump output
42
+ ncdump_output = '''
43
+ netcdf test_file {
44
+ // global attributes:
45
+ :Conventions = "CF-1.7 CMIP-6.2" ;
46
+ :activity_id = "CMIP" ;
47
+ :experiment_id = "historical" ;
48
+ }
49
+ '''
50
+
51
+ header = NetCDFHeaderParser.parse_from_ncdump(ncdump_output)
52
+ print(f"File: {header.filename}")
53
+ print(f"Attributes: {header.global_attributes.list_attributes()}")
54
+ ```
55
+ """
56
+
57
+ # Main GA validator interface
58
+ from .validator import (
59
+ GAValidator,
60
+ GAValidatorFactory,
61
+ validate_netcdf_attributes,
62
+ create_validation_summary
63
+ )
64
+
65
+ # Core models
66
+ from .models import (
67
+ # Models for advanced usage
68
+ NetCDFHeader,
69
+ NetCDFHeaderParser,
70
+ ValidationReport,
71
+ ValidationSeverity,
72
+ ValidationIssue,
73
+
74
+ # Validator models
75
+ ESGVocAttributeValidator,
76
+ ValidatorFactory,
77
+
78
+ # Import AttributeProperty from project_specs
79
+ AttributeProperty,
80
+ AttributeSpecification,
81
+ )
82
+
83
+ __all__ = [
84
+ # Main interface
85
+ "GAValidator",
86
+ "GAValidatorFactory",
87
+ "validate_netcdf_attributes",
88
+ "create_validation_summary",
89
+
90
+ # Models
91
+ "NetCDFHeader",
92
+ "NetCDFHeaderParser",
93
+ "ValidationReport",
94
+ "ValidationSeverity",
95
+ "ValidationIssue",
96
+
97
+ # Attribute specifications from project_specs
98
+ "AttributeProperty",
99
+ "AttributeSpecification",
100
+
101
+ # Validators
102
+ "ESGVocAttributeValidator",
103
+ "ValidatorFactory",
104
+ ]