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.
- esgvoc/__init__.py +3 -0
- esgvoc/api/__init__.py +91 -0
- esgvoc/api/data_descriptors/EMD_models/__init__.py +66 -0
- esgvoc/api/data_descriptors/EMD_models/arrangement.py +21 -0
- esgvoc/api/data_descriptors/EMD_models/calendar.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/cell_variable_type.py +20 -0
- esgvoc/api/data_descriptors/EMD_models/component_type.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/coordinate.py +52 -0
- esgvoc/api/data_descriptors/EMD_models/grid_mapping.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/grid_region.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/grid_type.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_computational_grid.py +56 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_grid_cells.py +230 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_subgrid.py +41 -0
- esgvoc/api/data_descriptors/EMD_models/horizontal_units.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/model.py +139 -0
- esgvoc/api/data_descriptors/EMD_models/model_component.py +115 -0
- esgvoc/api/data_descriptors/EMD_models/reference.py +61 -0
- esgvoc/api/data_descriptors/EMD_models/resolution.py +48 -0
- esgvoc/api/data_descriptors/EMD_models/temporal_refinement.py +19 -0
- esgvoc/api/data_descriptors/EMD_models/truncation_method.py +17 -0
- esgvoc/api/data_descriptors/EMD_models/vertical_computational_grid.py +91 -0
- esgvoc/api/data_descriptors/EMD_models/vertical_coordinate.py +5 -0
- esgvoc/api/data_descriptors/EMD_models/vertical_units.py +19 -0
- esgvoc/api/data_descriptors/__init__.py +159 -0
- esgvoc/api/data_descriptors/activity.py +72 -0
- esgvoc/api/data_descriptors/archive.py +5 -0
- esgvoc/api/data_descriptors/area_label.py +30 -0
- esgvoc/api/data_descriptors/branded_suffix.py +30 -0
- esgvoc/api/data_descriptors/branded_variable.py +21 -0
- esgvoc/api/data_descriptors/citation_url.py +5 -0
- esgvoc/api/data_descriptors/contact.py +5 -0
- esgvoc/api/data_descriptors/conventions.py +28 -0
- esgvoc/api/data_descriptors/creation_date.py +18 -0
- esgvoc/api/data_descriptors/data_descriptor.py +127 -0
- esgvoc/api/data_descriptors/data_specs_version.py +25 -0
- esgvoc/api/data_descriptors/date.py +5 -0
- esgvoc/api/data_descriptors/directory_date.py +22 -0
- esgvoc/api/data_descriptors/drs_specs.py +38 -0
- esgvoc/api/data_descriptors/experiment.py +215 -0
- esgvoc/api/data_descriptors/forcing_index.py +21 -0
- esgvoc/api/data_descriptors/frequency.py +48 -0
- esgvoc/api/data_descriptors/further_info_url.py +5 -0
- esgvoc/api/data_descriptors/grid.py +43 -0
- esgvoc/api/data_descriptors/horizontal_label.py +20 -0
- esgvoc/api/data_descriptors/initialization_index.py +27 -0
- esgvoc/api/data_descriptors/institution.py +80 -0
- esgvoc/api/data_descriptors/known_branded_variable.py +75 -0
- esgvoc/api/data_descriptors/license.py +31 -0
- esgvoc/api/data_descriptors/member_id.py +9 -0
- esgvoc/api/data_descriptors/mip_era.py +26 -0
- esgvoc/api/data_descriptors/model_component.py +32 -0
- esgvoc/api/data_descriptors/models_test/models.py +17 -0
- esgvoc/api/data_descriptors/nominal_resolution.py +50 -0
- esgvoc/api/data_descriptors/obs_type.py +5 -0
- esgvoc/api/data_descriptors/organisation.py +22 -0
- esgvoc/api/data_descriptors/physics_index.py +21 -0
- esgvoc/api/data_descriptors/product.py +16 -0
- esgvoc/api/data_descriptors/publication_status.py +5 -0
- esgvoc/api/data_descriptors/realization_index.py +24 -0
- esgvoc/api/data_descriptors/realm.py +16 -0
- esgvoc/api/data_descriptors/regex.py +5 -0
- esgvoc/api/data_descriptors/region.py +35 -0
- esgvoc/api/data_descriptors/resolution.py +7 -0
- esgvoc/api/data_descriptors/source.py +120 -0
- esgvoc/api/data_descriptors/source_type.py +5 -0
- esgvoc/api/data_descriptors/sub_experiment.py +5 -0
- esgvoc/api/data_descriptors/table.py +28 -0
- esgvoc/api/data_descriptors/temporal_label.py +20 -0
- esgvoc/api/data_descriptors/time_range.py +17 -0
- esgvoc/api/data_descriptors/title.py +5 -0
- esgvoc/api/data_descriptors/tracking_id.py +67 -0
- esgvoc/api/data_descriptors/variable.py +56 -0
- esgvoc/api/data_descriptors/variant_label.py +25 -0
- esgvoc/api/data_descriptors/vertical_label.py +20 -0
- esgvoc/api/project_specs.py +143 -0
- esgvoc/api/projects.py +1253 -0
- esgvoc/api/py.typed +0 -0
- esgvoc/api/pydantic_handler.py +146 -0
- esgvoc/api/report.py +127 -0
- esgvoc/api/search.py +171 -0
- esgvoc/api/universe.py +434 -0
- esgvoc/apps/__init__.py +6 -0
- esgvoc/apps/cmor_tables/__init__.py +7 -0
- esgvoc/apps/cmor_tables/cvs_table.py +948 -0
- esgvoc/apps/drs/__init__.py +0 -0
- esgvoc/apps/drs/constants.py +2 -0
- esgvoc/apps/drs/generator.py +429 -0
- esgvoc/apps/drs/report.py +540 -0
- esgvoc/apps/drs/validator.py +312 -0
- esgvoc/apps/ga/__init__.py +104 -0
- esgvoc/apps/ga/example_usage.py +315 -0
- esgvoc/apps/ga/models/__init__.py +47 -0
- esgvoc/apps/ga/models/netcdf_header.py +306 -0
- esgvoc/apps/ga/models/validator.py +491 -0
- esgvoc/apps/ga/test_ga.py +161 -0
- esgvoc/apps/ga/validator.py +277 -0
- esgvoc/apps/jsg/json_schema_generator.py +341 -0
- esgvoc/apps/jsg/templates/template.jinja +241 -0
- esgvoc/apps/test_cv/README.md +214 -0
- esgvoc/apps/test_cv/__init__.py +0 -0
- esgvoc/apps/test_cv/cv_tester.py +1611 -0
- esgvoc/apps/test_cv/example_usage.py +216 -0
- esgvoc/apps/vr/__init__.py +12 -0
- esgvoc/apps/vr/build_variable_registry.py +71 -0
- esgvoc/apps/vr/example_usage.py +60 -0
- esgvoc/apps/vr/vr_app.py +333 -0
- esgvoc/cli/clean.py +304 -0
- esgvoc/cli/cmor.py +46 -0
- esgvoc/cli/config.py +1300 -0
- esgvoc/cli/drs.py +267 -0
- esgvoc/cli/find.py +138 -0
- esgvoc/cli/get.py +155 -0
- esgvoc/cli/install.py +41 -0
- esgvoc/cli/main.py +60 -0
- esgvoc/cli/offline.py +269 -0
- esgvoc/cli/status.py +79 -0
- esgvoc/cli/test_cv.py +258 -0
- esgvoc/cli/valid.py +147 -0
- esgvoc/core/constants.py +17 -0
- esgvoc/core/convert.py +0 -0
- esgvoc/core/data_handler.py +206 -0
- esgvoc/core/db/__init__.py +3 -0
- esgvoc/core/db/connection.py +40 -0
- esgvoc/core/db/models/mixins.py +25 -0
- esgvoc/core/db/models/project.py +102 -0
- esgvoc/core/db/models/universe.py +98 -0
- esgvoc/core/db/project_ingestion.py +231 -0
- esgvoc/core/db/universe_ingestion.py +172 -0
- esgvoc/core/exceptions.py +33 -0
- esgvoc/core/logging_handler.py +26 -0
- esgvoc/core/repo_fetcher.py +345 -0
- esgvoc/core/service/__init__.py +41 -0
- esgvoc/core/service/configuration/config_manager.py +196 -0
- esgvoc/core/service/configuration/setting.py +363 -0
- esgvoc/core/service/data_merger.py +634 -0
- esgvoc/core/service/esg_voc.py +77 -0
- esgvoc/core/service/resolver_config.py +56 -0
- esgvoc/core/service/state.py +324 -0
- esgvoc/core/service/string_heuristics.py +98 -0
- esgvoc/core/service/term_cache.py +108 -0
- esgvoc/core/service/uri_resolver.py +133 -0
- esgvoc-2.0.2.dist-info/METADATA +82 -0
- esgvoc-2.0.2.dist-info/RECORD +147 -0
- esgvoc-2.0.2.dist-info/WHEEL +4 -0
- esgvoc-2.0.2.dist-info/entry_points.txt +2 -0
- esgvoc-2.0.2.dist-info/licenses/LICENSE.txt +519 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Top-level model description (EMD v1.0 Section 2).
|
|
3
|
+
|
|
4
|
+
The following properties provide a top-level description of the model as a whole.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from pydantic import Field, field_validator
|
|
12
|
+
|
|
13
|
+
from esgvoc.api.data_descriptors.data_descriptor import PlainTermDataDescriptor
|
|
14
|
+
|
|
15
|
+
from .calendar import Calendar
|
|
16
|
+
from .component_type import ComponentType
|
|
17
|
+
from .model_component import EMDModelComponent
|
|
18
|
+
from .reference import Reference
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Model(PlainTermDataDescriptor):
|
|
22
|
+
"""
|
|
23
|
+
Top-level model description (EMD v1.0 Section 2).
|
|
24
|
+
|
|
25
|
+
The following properties provide a top-level description of the model as a whole.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
name: str = Field(
|
|
29
|
+
description="The name of the top-level model. For CMIP7, this name will be registered as the model's source_id.",
|
|
30
|
+
min_length=1,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
family: str = Field(
|
|
34
|
+
description="The top-level model's 'family' name. Use 'none' to indicate that there is no such family.",
|
|
35
|
+
min_length=1,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
dynamic_components: List[str | ComponentType] = Field(
|
|
39
|
+
description="The model components that are dynamically simulated within the top-level model. "
|
|
40
|
+
"Taken from 7.1 component CV.",
|
|
41
|
+
min_length=1,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
prescribed_components: List[str | ComponentType] = Field(
|
|
45
|
+
description="The components that are represented in the top-level model with prescribed values. "
|
|
46
|
+
"Taken from 7.1 component CV.",
|
|
47
|
+
default_factory=list,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
omitted_components: List[str | ComponentType] = Field(
|
|
51
|
+
description="The components that are wholly omitted from the top-level model. Taken from 7.1 component CV.",
|
|
52
|
+
default_factory=list,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
description: str = Field(
|
|
56
|
+
description="A scientific overview of the top-level model. The description should include a brief mention "
|
|
57
|
+
"of all the components listed in the 7.1 component CV, whether dynamically simulated, prescribed, or omitted.",
|
|
58
|
+
min_length=1,
|
|
59
|
+
default="",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
calendar: List[str | Calendar] = Field(
|
|
63
|
+
description="The calendar, or calendars, that define which dates are permitted in the top-level model. "
|
|
64
|
+
"Taken from 7.2 calendar CV.",
|
|
65
|
+
min_length=1,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
release_year: int = Field(
|
|
69
|
+
description="The year in which the top-level model being documented was released, "
|
|
70
|
+
"or first used for published simulations.",
|
|
71
|
+
ge=1900,
|
|
72
|
+
le=2100,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
references: List[str | Reference] = Field(
|
|
76
|
+
description="One or more references to published work for the top-level model as a whole.", min_length=1
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
model_components: List[str | EMDModelComponent] = Field(
|
|
80
|
+
description="The model components that dynamically simulate processes within the model."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@field_validator("model_components")
|
|
84
|
+
@classmethod
|
|
85
|
+
def validate_same_dynamic_components(cls, v, info):
|
|
86
|
+
"""Validate that model_components has the same length as dynamic_components."""
|
|
87
|
+
if "dynamic_components" in info.data:
|
|
88
|
+
dynamic_components = info.data["dynamic_components"]
|
|
89
|
+
if len(v) != len(dynamic_components):
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Number of model_components ({len(v)}) must equal number of dynamic_components({
|
|
92
|
+
len(dynamic_components)
|
|
93
|
+
})"
|
|
94
|
+
)
|
|
95
|
+
return v
|
|
96
|
+
|
|
97
|
+
@field_validator("name", "family", "description", mode="before")
|
|
98
|
+
@classmethod
|
|
99
|
+
def validate_non_empty_strings(cls, v):
|
|
100
|
+
"""Validate that string fields are not empty."""
|
|
101
|
+
if isinstance(v, str):
|
|
102
|
+
if not v.strip():
|
|
103
|
+
raise ValueError("Field cannot be empty")
|
|
104
|
+
return v.strip()
|
|
105
|
+
return v
|
|
106
|
+
|
|
107
|
+
@field_validator("dynamic_components", "prescribed_components", "omitted_components", mode="before")
|
|
108
|
+
@classmethod
|
|
109
|
+
def validate_component_lists(cls, v):
|
|
110
|
+
"""Validate component lists contain valid strings or ComponentType objects."""
|
|
111
|
+
if v is None:
|
|
112
|
+
return []
|
|
113
|
+
# Filter out empty strings, keep ComponentType objects
|
|
114
|
+
cleaned = []
|
|
115
|
+
for item in v:
|
|
116
|
+
if isinstance(item, str):
|
|
117
|
+
if item.strip():
|
|
118
|
+
cleaned.append(item.strip())
|
|
119
|
+
else:
|
|
120
|
+
cleaned.append(item)
|
|
121
|
+
return cleaned
|
|
122
|
+
|
|
123
|
+
@field_validator("calendar", mode="before")
|
|
124
|
+
@classmethod
|
|
125
|
+
def validate_calendar_list(cls, v):
|
|
126
|
+
"""Validate calendar list contains valid strings or Calendar objects."""
|
|
127
|
+
if not v:
|
|
128
|
+
raise ValueError("At least one calendar must be specified")
|
|
129
|
+
# Filter out empty strings, keep Calendar objects
|
|
130
|
+
cleaned = []
|
|
131
|
+
for item in v:
|
|
132
|
+
if isinstance(item, str):
|
|
133
|
+
if item.strip():
|
|
134
|
+
cleaned.append(item.strip())
|
|
135
|
+
else:
|
|
136
|
+
cleaned.append(item)
|
|
137
|
+
if not cleaned:
|
|
138
|
+
raise ValueError("Calendar list cannot be empty")
|
|
139
|
+
return cleaned
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model component description (EMD v1.0 Section 3).
|
|
3
|
+
|
|
4
|
+
Properties that provide a description of individual model components.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from pydantic import Field, field_validator
|
|
12
|
+
|
|
13
|
+
from esgvoc.api.data_descriptors.data_descriptor import PlainTermDataDescriptor
|
|
14
|
+
|
|
15
|
+
from .component_type import ComponentType
|
|
16
|
+
from .horizontal_computational_grid import HorizontalComputationalGrid
|
|
17
|
+
from .reference import Reference
|
|
18
|
+
from .vertical_computational_grid import VerticalComputationalGrid
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EMDModelComponent(PlainTermDataDescriptor):
|
|
22
|
+
"""
|
|
23
|
+
Model component description (EMD v1.0 Section 3).
|
|
24
|
+
|
|
25
|
+
Properties that provide a description of individual model components.
|
|
26
|
+
|
|
27
|
+
Eight model components are defined that somewhat independently account for different
|
|
28
|
+
sets of interactive processes: aerosol, atmosphere, atmospheric chemistry, land surface,
|
|
29
|
+
land ice, ocean, ocean biogeochemistry, and sea ice.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
component: str | ComponentType = Field(description="The type of the model component. Taken from 7.1 component CV.")
|
|
33
|
+
|
|
34
|
+
name: str = Field(description="The name of the model component.", min_length=1)
|
|
35
|
+
|
|
36
|
+
family: str = Field(
|
|
37
|
+
description="The model component's 'family' name. Use 'none' to indicate that there is no such family.",
|
|
38
|
+
min_length=1,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
description: str = Field(
|
|
42
|
+
description="A scientific overview of the model component. The description should summarise the key "
|
|
43
|
+
"processes simulated by the model component.",
|
|
44
|
+
min_length=1,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
references: List[str | Reference] = Field(
|
|
48
|
+
description="One or more references to published work for the model component.", min_length=1
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
code_base: str = Field(
|
|
52
|
+
description="A URL (preferably for a DOI) for the source code for the model component. "
|
|
53
|
+
"Set to 'private' if not publicly available.",
|
|
54
|
+
min_length=1,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
embedded_in: Optional[str | ComponentType] = Field(
|
|
58
|
+
default=None,
|
|
59
|
+
description="The host model component (identified by its component property) in which this component "
|
|
60
|
+
"is 'embedded'. Taken from 7.1 component CV. Omit when this component is coupled with other components.",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
coupled_with: Optional[List[str | ComponentType]] = Field(
|
|
64
|
+
default=None,
|
|
65
|
+
description="The model components (identified by their component properties) with which this component "
|
|
66
|
+
"is 'coupled'. Taken from 7.1 component CV. Omit when this component is embedded in another component.",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
horizontal_computational_grid: HorizontalComputationalGrid = Field(
|
|
70
|
+
description="A standardised description of the model component's horizontal computational grid."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
vertical_computational_grid: VerticalComputationalGrid = Field(
|
|
74
|
+
description="A standardised description of the model component's vertical computational grid."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@field_validator("component", "name", "family", "code_base", "description", mode="before")
|
|
78
|
+
@classmethod
|
|
79
|
+
def validate_non_empty_strings(cls, v):
|
|
80
|
+
"""Validate that string fields are not empty."""
|
|
81
|
+
if isinstance(v, str):
|
|
82
|
+
if not v.strip():
|
|
83
|
+
raise ValueError("Field cannot be empty")
|
|
84
|
+
return v.strip()
|
|
85
|
+
return v
|
|
86
|
+
|
|
87
|
+
@field_validator("coupled_with")
|
|
88
|
+
@classmethod
|
|
89
|
+
def validate_coupling_exclusivity(cls, v, info):
|
|
90
|
+
"""Validate that a component cannot be both embedded and coupled."""
|
|
91
|
+
if v is not None and info.data.get("embedded_in") is not None:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"A component cannot be both embedded_in another component and coupled_with other components"
|
|
94
|
+
)
|
|
95
|
+
return v
|
|
96
|
+
|
|
97
|
+
@field_validator("embedded_in")
|
|
98
|
+
@classmethod
|
|
99
|
+
def validate_embedding_exclusivity(cls, v, info):
|
|
100
|
+
"""Validate that a component cannot be both embedded and coupled."""
|
|
101
|
+
if v is not None and info.data.get("coupled_with") is not None:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
"A component cannot be both embedded_in another component and coupled_with other components"
|
|
104
|
+
)
|
|
105
|
+
return v
|
|
106
|
+
|
|
107
|
+
@field_validator("code_base", mode="before")
|
|
108
|
+
@classmethod
|
|
109
|
+
def validate_code_base_format(cls, v):
|
|
110
|
+
"""Validate code_base is either 'private' or a URL."""
|
|
111
|
+
if isinstance(v, str):
|
|
112
|
+
v = v.strip()
|
|
113
|
+
if v.lower() != "private" and not (v.startswith("http://") or v.startswith("https://")):
|
|
114
|
+
raise ValueError('code_base must be either "private" or a valid URL starting with http:// or https://')
|
|
115
|
+
return v
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Academic reference to published work for the top-level model or model components.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, field_validator
|
|
6
|
+
|
|
7
|
+
from esgvoc.api.data_descriptors.data_descriptor import PlainTermDataDescriptor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Reference(PlainTermDataDescriptor):
|
|
11
|
+
"""
|
|
12
|
+
Academic reference to published work for the top-level model or model components.
|
|
13
|
+
|
|
14
|
+
An academic reference to published work for the top-level model or one of its model
|
|
15
|
+
components is defined by the following properties:
|
|
16
|
+
|
|
17
|
+
• Citation
|
|
18
|
+
◦ A human-readable citation for the work.
|
|
19
|
+
◦ E.g. Smith, R. S., Mathiot, P., Siahaan, A., Lee, V., Cornford, S. L., Gregory, J. M.,
|
|
20
|
+
et al. (2021). Coupling the U.K. Earth System model to dynamic models of the Greenland
|
|
21
|
+
and Antarctic ice sheets. Journal of Advances in Modeling Earth Systems, 13,
|
|
22
|
+
e2021MS002520. https://doi.org/10.1029/2021MS002520, 2023
|
|
23
|
+
• DOI
|
|
24
|
+
◦ The persistent identifier (DOI) used to identify the work.
|
|
25
|
+
◦ A DOI is required for all references. A reference that does not already have a DOI
|
|
26
|
+
(as could be the case for some technical reports, for instance) must be given one
|
|
27
|
+
(e.g. with a service like Zenodo).
|
|
28
|
+
◦ E.g. https://doi.org/10.1029/2021MS002520
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
citation: str = Field(description="A human-readable citation for the work.", min_length=1)
|
|
32
|
+
doi: str = Field(
|
|
33
|
+
description="The persistent identifier (DOI) used to identify the work. Must be a valid DOI URL.", min_length=1
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@field_validator("doi")
|
|
37
|
+
@classmethod
|
|
38
|
+
def validate_doi(cls, v):
|
|
39
|
+
"""Validate that DOI follows proper format (accepts proxies like doi-org.insu.bib...)."""
|
|
40
|
+
# Remove all whitespace to handle formatting issues
|
|
41
|
+
v = "".join(v.split())
|
|
42
|
+
|
|
43
|
+
# Accept both canonical DOIs and proxy URLs
|
|
44
|
+
if not v.startswith("https://doi"):
|
|
45
|
+
raise ValueError(
|
|
46
|
+
'DOI must start with "https://doi" (canonical: https://doi.org/, proxies: https://doi-...)'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Ensure there's an actual identifier after the DOI prefix
|
|
50
|
+
if len(v) <= len("https://doi"):
|
|
51
|
+
raise ValueError('DOI must contain identifier after "https://doi"')
|
|
52
|
+
|
|
53
|
+
return v
|
|
54
|
+
|
|
55
|
+
@field_validator("citation")
|
|
56
|
+
@classmethod
|
|
57
|
+
def validate_citation(cls, v):
|
|
58
|
+
"""Validate that citation is not empty."""
|
|
59
|
+
if not v.strip():
|
|
60
|
+
raise ValueError("Citation cannot be empty")
|
|
61
|
+
return v.strip()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from esgvoc.api.data_descriptors.data_descriptor import PlainTermDataDescriptor
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EMDResolution(PlainTermDataDescriptor):
|
|
5
|
+
"""
|
|
6
|
+
The nominal resolution (in km) characterises the resolution of a model's native horizontal grid. See section 5.1 Native horizontal grid properties.
|
|
7
|
+
Options for the native horizontal grid nominal resolution property are defined in the following table as a function of the value of the mean resolution km property:
|
|
8
|
+
|
|
9
|
+
for mean resolution, R, in the range (km):
|
|
10
|
+
nominal resolution is:
|
|
11
|
+
0.036 ≤ R < 0.072
|
|
12
|
+
0.05 km
|
|
13
|
+
0.072 ≤ R < 0.16
|
|
14
|
+
0.1 km
|
|
15
|
+
0.16 ≤ R < 0.36
|
|
16
|
+
0.25 km
|
|
17
|
+
0.36 ≤ R < 0.72
|
|
18
|
+
0.5 km
|
|
19
|
+
0.72 ≤ R < 1.6
|
|
20
|
+
1 km
|
|
21
|
+
1.6 ≤ R < 3.6
|
|
22
|
+
2.5 km
|
|
23
|
+
3.6 ≤ R < 7.2
|
|
24
|
+
5 km
|
|
25
|
+
7.2 ≤ R < 16
|
|
26
|
+
10 km
|
|
27
|
+
16 ≤ R < 36
|
|
28
|
+
25 km
|
|
29
|
+
36 ≤ R < 72
|
|
30
|
+
50 km
|
|
31
|
+
72 ≤ R < 160
|
|
32
|
+
100 km
|
|
33
|
+
160 ≤ R < 360
|
|
34
|
+
250 km
|
|
35
|
+
360 ≤ R < 720
|
|
36
|
+
500 km
|
|
37
|
+
720 ≤ R < 1600
|
|
38
|
+
1000 km
|
|
39
|
+
1600 ≤ R < 3600
|
|
40
|
+
2500 km
|
|
41
|
+
3600 ≤ R < 7200
|
|
42
|
+
5000 km
|
|
43
|
+
7200 ≤ R < 16000
|
|
44
|
+
10000 km
|
|
45
|
+
7.10. Native vertical grid "Coordinate" CV
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
mean_resolution: str
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EMD v1.0 Section 7.8 - temporal_refinement CV
|
|
3
|
+
|
|
4
|
+
Grid temporal refinement types.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from esgvoc.api.data_descriptors.data_descriptor import PlainTermDataDescriptor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TemporalRefinement(PlainTermDataDescriptor):
|
|
11
|
+
"""
|
|
12
|
+
Temporal refinement (EMD v1.0 Section 7.8).
|
|
13
|
+
|
|
14
|
+
Options: static, etc.
|
|
15
|
+
|
|
16
|
+
The grid temporal refinement, indicating how the distribution of grid cells varies with time.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
pass
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EMD v1.0 Section 7.12 - truncation_method CV
|
|
3
|
+
|
|
4
|
+
Spectral truncation method types.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from esgvoc.api.data_descriptors.data_descriptor import PlainTermDataDescriptor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TruncationMethod(PlainTermDataDescriptor):
|
|
11
|
+
"""
|
|
12
|
+
Truncation method (EMD v1.0 Section 7.12).
|
|
13
|
+
|
|
14
|
+
The method for truncating the spherical harmonic representation of a spectral model.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
pass
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Vertical computational grid description (EMD v1.0 Section 4.2).
|
|
3
|
+
|
|
4
|
+
The model component's vertical computational grid is described by a subset of the following properties.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from pydantic import Field, field_validator
|
|
12
|
+
|
|
13
|
+
from esgvoc.api.data_descriptors.data_descriptor import DataDescriptor
|
|
14
|
+
|
|
15
|
+
from .coordinate import Coordinate
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class VerticalComputationalGrid(DataDescriptor):
|
|
19
|
+
"""
|
|
20
|
+
Vertical computational grid description (EMD v1.0 Section 4.2).
|
|
21
|
+
|
|
22
|
+
The model component's vertical computational grid is described by a subset of the following properties.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
vertical_coordinate: str | Coordinate = Field(
|
|
26
|
+
description="The coordinate type of the vertical grid. Taken from 7.11 vertical_coordinate CV. If there is no vertical grid, then the value 'none' must be selected."
|
|
27
|
+
)
|
|
28
|
+
description: Optional[str] = Field(
|
|
29
|
+
default=None,
|
|
30
|
+
description="A description of the vertical grid. A description is only required if there is information that is not covered by any of the other properties. Omit when not required.",
|
|
31
|
+
)
|
|
32
|
+
n_z: Optional[int] = Field(
|
|
33
|
+
default=None,
|
|
34
|
+
description="The number of layers (i.e. grid cells) in the Z direction. Omit when not applicable or not constant. If the number of layers varies in time or across the horizontal grid, then the n_z_range property may be used instead.",
|
|
35
|
+
ge=1,
|
|
36
|
+
)
|
|
37
|
+
n_z_range: Optional[List[int]] = Field(
|
|
38
|
+
default=None,
|
|
39
|
+
description="The minimum and maximum number of layers for vertical grids with a time- or space-varying number of layers. Omit if the n_z property has been set.",
|
|
40
|
+
min_length=2,
|
|
41
|
+
max_length=2,
|
|
42
|
+
)
|
|
43
|
+
bottom_layer_thickness: Optional[float] = Field(
|
|
44
|
+
default=None,
|
|
45
|
+
description="The thickness of the bottom model layer (i.e. the layer closest to the centre of the Earth). The value should be reported as a dimensional (as opposed to parametric) quantity. All measurements are in metres (EMD v1.0).",
|
|
46
|
+
gt=0,
|
|
47
|
+
)
|
|
48
|
+
top_layer_thickness: Optional[float] = Field(
|
|
49
|
+
default=None,
|
|
50
|
+
description="The thickness of the top model layer (i.e. the layer furthest away from the centre of the Earth). The value should be reported as a dimensional (as opposed to parametric) quantity. All measurements are in metres (EMD v1.0).",
|
|
51
|
+
gt=0,
|
|
52
|
+
)
|
|
53
|
+
top_of_model: Optional[float] = Field(
|
|
54
|
+
default=None,
|
|
55
|
+
description="The upper boundary of the top model layer (i.e. the upper boundary of the layer that is furthest away from the centre of the Earth). The value should be relative to the lower boundary of the bottom layer of the model, or an appropriate datum (such as mean sea level). All measurements are in metres (EMD v1.0).",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@field_validator("vertical_coordinate", mode="before")
|
|
59
|
+
@classmethod
|
|
60
|
+
def validate_vertical_coordinate(cls, v):
|
|
61
|
+
"""Validate that vertical_coordinate is not empty if it's a string."""
|
|
62
|
+
if isinstance(v, str):
|
|
63
|
+
if not v.strip():
|
|
64
|
+
raise ValueError("Vertical coordinate cannot be empty")
|
|
65
|
+
return v.strip()
|
|
66
|
+
return v
|
|
67
|
+
|
|
68
|
+
@field_validator("n_z_range")
|
|
69
|
+
@classmethod
|
|
70
|
+
def validate_n_z_range(cls, v):
|
|
71
|
+
"""Validate that n_z_range has exactly 2 values and min <= max."""
|
|
72
|
+
if v is not None:
|
|
73
|
+
if len(v) != 2:
|
|
74
|
+
raise ValueError("n_z_range must contain exactly 2 values [min, max]")
|
|
75
|
+
if v[0] > v[1]:
|
|
76
|
+
raise ValueError("n_z_range: minimum must be <= maximum")
|
|
77
|
+
if any(val < 1 for val in v):
|
|
78
|
+
raise ValueError("n_z_range values must be >= 1")
|
|
79
|
+
return v
|
|
80
|
+
|
|
81
|
+
@field_validator("n_z")
|
|
82
|
+
@classmethod
|
|
83
|
+
def validate_n_z_exclusivity(cls, v, info):
|
|
84
|
+
"""Validate that n_z and n_z_range are mutually exclusive."""
|
|
85
|
+
if v is not None and info.data.get("n_z_range") is not None:
|
|
86
|
+
raise ValueError("n_z and n_z_range cannot both be set")
|
|
87
|
+
return v
|
|
88
|
+
|
|
89
|
+
def accept(self, visitor):
|
|
90
|
+
"""Accept a data descriptor visitor."""
|
|
91
|
+
return visitor.visit_plain_term(self)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Vertical units CV (7.14 vertical_units CV).
|
|
3
|
+
|
|
4
|
+
Physical units of vertical grid thickness and position values.
|
|
5
|
+
Options include: m, Pa, K
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from esgvoc.api.data_descriptors.data_descriptor import PlainTermDataDescriptor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VerticalUnits(PlainTermDataDescriptor):
|
|
12
|
+
"""
|
|
13
|
+
Vertical units CV (7.14 vertical_units CV).
|
|
14
|
+
|
|
15
|
+
Physical units of vertical grid thickness and position values.
|
|
16
|
+
Options include: m, Pa, K
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
pass
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from esgvoc.api.data_descriptors.EMD_models.horizontal_computational_grid import HorizontalComputationalGrid
|
|
2
|
+
from esgvoc.api.data_descriptors.EMD_models.horizontal_grid_cells import HorizontalGridCells
|
|
3
|
+
from esgvoc.api.data_descriptors.EMD_models.horizontal_subgrid import HorizontalSubgrid
|
|
4
|
+
from esgvoc.api.data_descriptors.EMD_models.horizontal_units import HorizontalUnits
|
|
5
|
+
from esgvoc.api.data_descriptors.EMD_models.vertical_computational_grid import VerticalComputationalGrid
|
|
6
|
+
from esgvoc.api.data_descriptors.EMD_models.vertical_coordinate import VerticalCoordinate
|
|
7
|
+
from esgvoc.api.data_descriptors.model_component import ModelComponent
|
|
8
|
+
from esgvoc.api.data_descriptors.vertical_label import VerticalLabel
|
|
9
|
+
from esgvoc.api.data_descriptors.variant_label import VariantLabel
|
|
10
|
+
from esgvoc.api.data_descriptors.variable import Variable
|
|
11
|
+
from esgvoc.api.data_descriptors.tracking_id import TrackingId
|
|
12
|
+
from esgvoc.api.data_descriptors.title import Title
|
|
13
|
+
from esgvoc.api.data_descriptors.time_range import TimeRange
|
|
14
|
+
from esgvoc.api.data_descriptors.temporal_label import TemporalLabel
|
|
15
|
+
from esgvoc.api.data_descriptors.table import Table
|
|
16
|
+
from esgvoc.api.data_descriptors.sub_experiment import SubExperiment
|
|
17
|
+
from esgvoc.api.data_descriptors.source_type import SourceType
|
|
18
|
+
from esgvoc.api.data_descriptors.source import Source
|
|
19
|
+
from esgvoc.api.data_descriptors.EMD_models.resolution import EMDResolution
|
|
20
|
+
from esgvoc.api.data_descriptors.resolution import Resolution
|
|
21
|
+
from esgvoc.api.data_descriptors.region import Region
|
|
22
|
+
from esgvoc.api.data_descriptors.regex import Regex
|
|
23
|
+
from esgvoc.api.data_descriptors.EMD_models.reference import Reference
|
|
24
|
+
from esgvoc.api.data_descriptors.realm import Realm
|
|
25
|
+
from esgvoc.api.data_descriptors.realization_index import RealizationIndex
|
|
26
|
+
from esgvoc.api.data_descriptors.publication_status import PublicationStatus
|
|
27
|
+
from esgvoc.api.data_descriptors.product import Product
|
|
28
|
+
from esgvoc.api.data_descriptors.physics_index import PhysicsIndex
|
|
29
|
+
from esgvoc.api.data_descriptors.organisation import Organisation
|
|
30
|
+
from esgvoc.api.data_descriptors.obs_type import ObsType
|
|
31
|
+
from esgvoc.api.data_descriptors.nominal_resolution import NominalResolution
|
|
32
|
+
|
|
33
|
+
from esgvoc.api.data_descriptors.models_test.models import CompositeTermDDex, PatternTermDDex, PlainTermDDex
|
|
34
|
+
from esgvoc.api.data_descriptors.EMD_models.model import Model
|
|
35
|
+
from esgvoc.api.data_descriptors.EMD_models.model_component import EMDModelComponent
|
|
36
|
+
|
|
37
|
+
# from esgvoc.api.data_descriptors.model_component import ModelComponent
|
|
38
|
+
from esgvoc.api.data_descriptors.mip_era import MipEra
|
|
39
|
+
from esgvoc.api.data_descriptors.member_id import MemberId
|
|
40
|
+
from esgvoc.api.data_descriptors.license import License
|
|
41
|
+
from esgvoc.api.data_descriptors.known_branded_variable import KnownBrandedVariable
|
|
42
|
+
from esgvoc.api.data_descriptors.institution import Institution
|
|
43
|
+
from esgvoc.api.data_descriptors.initialization_index import InitializationIndex
|
|
44
|
+
from esgvoc.api.data_descriptors.horizontal_label import HorizontalLabel
|
|
45
|
+
from esgvoc.api.data_descriptors.activity import Activity
|
|
46
|
+
from esgvoc.api.data_descriptors.archive import Archive
|
|
47
|
+
from esgvoc.api.data_descriptors.area_label import AreaLabel
|
|
48
|
+
from esgvoc.api.data_descriptors.branded_suffix import BrandedSuffix
|
|
49
|
+
from esgvoc.api.data_descriptors.branded_variable import BrandedVariable
|
|
50
|
+
from esgvoc.api.data_descriptors.EMD_models.calendar import Calendar
|
|
51
|
+
from esgvoc.api.data_descriptors.citation_url import CitationUrl
|
|
52
|
+
from esgvoc.api.data_descriptors.EMD_models.component_type import ComponentType
|
|
53
|
+
from esgvoc.api.data_descriptors.contact import Contact
|
|
54
|
+
from esgvoc.api.data_descriptors.conventions import Convention
|
|
55
|
+
from esgvoc.api.data_descriptors.creation_date import CreationDate
|
|
56
|
+
from esgvoc.api.data_descriptors.data_descriptor import DataDescriptor
|
|
57
|
+
from esgvoc.api.data_descriptors.data_specs_version import DataSpecsVersion
|
|
58
|
+
from esgvoc.api.data_descriptors.date import Date
|
|
59
|
+
from esgvoc.api.data_descriptors.directory_date import DirectoryDate
|
|
60
|
+
from esgvoc.api.data_descriptors.drs_specs import DRSSpecs
|
|
61
|
+
from esgvoc.api.data_descriptors.experiment import Experiment
|
|
62
|
+
from esgvoc.api.data_descriptors.forcing_index import ForcingIndex
|
|
63
|
+
from esgvoc.api.data_descriptors.frequency import Frequency
|
|
64
|
+
from esgvoc.api.data_descriptors.further_info_url import FurtherInfoUrl
|
|
65
|
+
from esgvoc.api.data_descriptors.grid import Grid
|
|
66
|
+
from esgvoc.api.data_descriptors.EMD_models.coordinate import Coordinate
|
|
67
|
+
from esgvoc.api.data_descriptors.EMD_models.arrangement import Arrangement
|
|
68
|
+
from esgvoc.api.data_descriptors.EMD_models.cell_variable_type import CellVariableType
|
|
69
|
+
from esgvoc.api.data_descriptors.EMD_models.grid_mapping import GridMapping
|
|
70
|
+
from esgvoc.api.data_descriptors.EMD_models.grid_region import GridRegion
|
|
71
|
+
from esgvoc.api.data_descriptors.EMD_models.grid_type import GridType
|
|
72
|
+
from esgvoc.api.data_descriptors.EMD_models.temporal_refinement import TemporalRefinement
|
|
73
|
+
from esgvoc.api.data_descriptors.EMD_models.truncation_method import TruncationMethod
|
|
74
|
+
from esgvoc.api.data_descriptors.EMD_models.vertical_units import VerticalUnits
|
|
75
|
+
|
|
76
|
+
from esgvoc.api.data_descriptors.activity import ActivityCMIP7
|
|
77
|
+
|
|
78
|
+
ActivityCMIP7.model_rebuild()
|
|
79
|
+
|
|
80
|
+
DATA_DESCRIPTOR_CLASS_MAPPING: dict[str, type[DataDescriptor]] = {
|
|
81
|
+
"PlainTermDDex": PlainTermDDex,
|
|
82
|
+
"PatternTermDDex": PatternTermDDex,
|
|
83
|
+
"CompositeTermDDex": CompositeTermDDex,
|
|
84
|
+
"activity": Activity,
|
|
85
|
+
"date": Date,
|
|
86
|
+
"directory_date": DirectoryDate,
|
|
87
|
+
"experiment": Experiment,
|
|
88
|
+
"forcing_index": ForcingIndex,
|
|
89
|
+
"frequency": Frequency,
|
|
90
|
+
"grid": Grid,
|
|
91
|
+
"initialization_index": InitializationIndex,
|
|
92
|
+
"institution": Institution,
|
|
93
|
+
"license": License,
|
|
94
|
+
"mip_era": MipEra,
|
|
95
|
+
"model_component": ModelComponent,
|
|
96
|
+
"organisation": Organisation,
|
|
97
|
+
"physics_index": PhysicsIndex,
|
|
98
|
+
"product": Product,
|
|
99
|
+
"realization_index": RealizationIndex,
|
|
100
|
+
"realm": Realm,
|
|
101
|
+
"resolution": Resolution,
|
|
102
|
+
"source": Source,
|
|
103
|
+
"source_type": SourceType,
|
|
104
|
+
"sub_experiment": SubExperiment,
|
|
105
|
+
"table": Table,
|
|
106
|
+
"time_range": TimeRange,
|
|
107
|
+
"variable": Variable,
|
|
108
|
+
"variant_label": VariantLabel,
|
|
109
|
+
"area_label": AreaLabel,
|
|
110
|
+
"temporal_label": TemporalLabel,
|
|
111
|
+
"vertical_label": VerticalLabel,
|
|
112
|
+
"horizontal_label": HorizontalLabel,
|
|
113
|
+
"branded_suffix": BrandedSuffix,
|
|
114
|
+
"branded_variable": BrandedVariable,
|
|
115
|
+
"publication_status": PublicationStatus,
|
|
116
|
+
"known_branded_variable": KnownBrandedVariable,
|
|
117
|
+
"calendar": Calendar,
|
|
118
|
+
"component_type": ComponentType,
|
|
119
|
+
"grid_arrangement": Arrangement, # EMD v1.0
|
|
120
|
+
"coordinate": Coordinate,
|
|
121
|
+
"grid_mapping": GridMapping, # EMD v1.0
|
|
122
|
+
"model_componentEMD": EMDModelComponent, # EMD v1.0
|
|
123
|
+
"model": Model, # EMD v1.0
|
|
124
|
+
"reference": Reference, # EMD v1.0
|
|
125
|
+
# "resolution": EMDResolution,
|
|
126
|
+
"grid_type": GridType, # EMD v1.0
|
|
127
|
+
"cell_variable_type": CellVariableType, # EMD v1.0
|
|
128
|
+
"truncation_method": TruncationMethod, # EMD v1.0
|
|
129
|
+
"vertical_units": VerticalUnits, # EMD v1.0
|
|
130
|
+
"grid_region": GridRegion, # EMD v1.0
|
|
131
|
+
"data_specs_version": DataSpecsVersion,
|
|
132
|
+
"further_info_url": FurtherInfoUrl,
|
|
133
|
+
"tracking_id": TrackingId,
|
|
134
|
+
"creation_date": CreationDate,
|
|
135
|
+
"conventions": Convention,
|
|
136
|
+
"title": Title,
|
|
137
|
+
"contact": Contact,
|
|
138
|
+
"region": Region,
|
|
139
|
+
"member_id": MemberId,
|
|
140
|
+
"obs_type": ObsType, # obs4Mips
|
|
141
|
+
"regex": Regex,
|
|
142
|
+
"citation_url": CitationUrl,
|
|
143
|
+
"archive": Archive,
|
|
144
|
+
"drs_specs": DRSSpecs,
|
|
145
|
+
"nominal_resolution": NominalResolution,
|
|
146
|
+
"grid_arrangement": Arrangement, # EMD v1.0
|
|
147
|
+
"cell_variable_type": CellVariableType, # EMD v1.0
|
|
148
|
+
"grid_mapping": GridMapping, # EMD v1.0
|
|
149
|
+
"grid_region": GridRegion, # EMD v1.0
|
|
150
|
+
"grid_temporal_refinement": TemporalRefinement, # EMD v1.0
|
|
151
|
+
"truncation_method": TruncationMethod, # EMD v1.0
|
|
152
|
+
"grid_type": GridType, # EMD v1.0
|
|
153
|
+
"horizontal_units": HorizontalUnits,
|
|
154
|
+
"vertical_coordinate": VerticalCoordinate,
|
|
155
|
+
"horizontal_grid_cell": HorizontalGridCells,
|
|
156
|
+
"horizontal_computational_grid": HorizontalComputationalGrid,
|
|
157
|
+
"horizontal_subgrid": HorizontalSubgrid,
|
|
158
|
+
"vertical_computational_grid": VerticalComputationalGrid,
|
|
159
|
+
}
|