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
esgvoc/cli/drs.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import shlex
|
|
2
|
+
import sys
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
import esgvoc.api as ev
|
|
10
|
+
from esgvoc.apps.drs.generator import DrsGenerator
|
|
11
|
+
from esgvoc.apps.drs.report import DrsGenerationReport, DrsValidationReport
|
|
12
|
+
from esgvoc.apps.drs.validator import DrsValidator
|
|
13
|
+
from esgvoc.core.exceptions import EsgvocValueError
|
|
14
|
+
|
|
15
|
+
app = typer.Typer()
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Predefined list of projects and DRS types
|
|
20
|
+
# projects = ["cmip5", "cmip6","cmip6plus", "cmip7"]
|
|
21
|
+
projects = ev.get_all_projects()
|
|
22
|
+
drs_types = ["filename", "directory", "dataset"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def display(table):
|
|
26
|
+
"""
|
|
27
|
+
Function to display a rich table in the console.
|
|
28
|
+
|
|
29
|
+
:param table: The table to be displayed
|
|
30
|
+
"""
|
|
31
|
+
console = Console(record=True, width=200)
|
|
32
|
+
console.print(table)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command()
|
|
36
|
+
def drsvalid(
|
|
37
|
+
drs_entries: Optional[List[str]] = typer.Argument(
|
|
38
|
+
None, help="List of DRS validation inputs in the form <project> <drstype> <string>"
|
|
39
|
+
),
|
|
40
|
+
file: Optional[typer.FileText] = typer.Option(
|
|
41
|
+
None,
|
|
42
|
+
"--file",
|
|
43
|
+
"-f",
|
|
44
|
+
help="File containing DRS validation inputs, one per line in the form <project> <drstype> <string>",
|
|
45
|
+
),
|
|
46
|
+
verbose: bool = typer.Option(False, "-v", "--verbose", help="Provide detailed validation results"),
|
|
47
|
+
output: Optional[str] = typer.Option(None, "-o", "--output", help="File to save the DRS entries validation"),
|
|
48
|
+
rm_prefix: Optional[str] = typer.Option(
|
|
49
|
+
None, "-p", "--prefix", help="Remove given prefix from all checked directory"
|
|
50
|
+
),
|
|
51
|
+
pedantic: Optional[bool] = typer.Option(
|
|
52
|
+
False,
|
|
53
|
+
"-e",
|
|
54
|
+
"--enforce",
|
|
55
|
+
help="Enable pedantic mode, enforcing strict compliance, mean that warnings are now errors.",
|
|
56
|
+
),
|
|
57
|
+
) -> List[DrsValidationReport]:
|
|
58
|
+
"""
|
|
59
|
+
Validates DRS strings for a specific project and type.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
drs_entries (Optional[List[str]]): A list of DRS validation inputs in the form <project> <drstype> <string>.
|
|
63
|
+
file (Optional[typer.FileText]): File containing DRS validation inputs, one per line.
|
|
64
|
+
verbose (bool): If true, prints detailed validation results.
|
|
65
|
+
|
|
66
|
+
Usage Examples:
|
|
67
|
+
# Validate multiple filenames for CMIP6
|
|
68
|
+
drsvalid cmip6 filename file1.nc file2.nc file3.nc
|
|
69
|
+
|
|
70
|
+
# Validate using a file
|
|
71
|
+
drsvalid --file drs_input.txt
|
|
72
|
+
"""
|
|
73
|
+
current_project = None
|
|
74
|
+
current_drs_type = None
|
|
75
|
+
reports = []
|
|
76
|
+
|
|
77
|
+
entries = drs_entries or []
|
|
78
|
+
|
|
79
|
+
if not sys.stdin.isatty(): # Check if input is being piped via stdin
|
|
80
|
+
entries.extend(el for line in sys.stdin for el in shlex.split(line))
|
|
81
|
+
|
|
82
|
+
if file:
|
|
83
|
+
entries.extend(el for line in file for el in line.strip().split(" "))
|
|
84
|
+
|
|
85
|
+
i = 0
|
|
86
|
+
while i < len(entries):
|
|
87
|
+
if entries[i] in ["", " "]:
|
|
88
|
+
i += 1
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
if entries[i] in projects:
|
|
92
|
+
current_project = entries[i]
|
|
93
|
+
i += 1
|
|
94
|
+
continue
|
|
95
|
+
if entries[i] in drs_types:
|
|
96
|
+
current_drs_type = entries[i]
|
|
97
|
+
i += 1
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
if current_project is None:
|
|
101
|
+
raise typer.BadParameter(f"Invalid project: {entries[i]}")
|
|
102
|
+
|
|
103
|
+
if current_drs_type is None:
|
|
104
|
+
raise typer.BadParameter(f"Invalid drs_type: {entries[i]}")
|
|
105
|
+
|
|
106
|
+
string = entries[i]
|
|
107
|
+
i += 1
|
|
108
|
+
validator = DrsValidator(current_project, pedantic=pedantic)
|
|
109
|
+
report = None
|
|
110
|
+
match current_drs_type:
|
|
111
|
+
case "filename":
|
|
112
|
+
report = validator.validate_file_name(string)
|
|
113
|
+
case "directory":
|
|
114
|
+
if rm_prefix:
|
|
115
|
+
prefix = rm_prefix + "/" if rm_prefix[-1] != "/" else ""
|
|
116
|
+
else:
|
|
117
|
+
prefix = None
|
|
118
|
+
report = validator.validate_directory(string, prefix)
|
|
119
|
+
case "dataset":
|
|
120
|
+
report = validator.validate_dataset_id(string)
|
|
121
|
+
case _:
|
|
122
|
+
raise EsgvocValueError(f"unsupported drs type '{current_drs_type}'")
|
|
123
|
+
reports.append(report)
|
|
124
|
+
|
|
125
|
+
if verbose:
|
|
126
|
+
table = Table(title="Validation result")
|
|
127
|
+
table.add_column("entry", style="cyan")
|
|
128
|
+
table.add_column("project & drs_type", style="cyan")
|
|
129
|
+
table.add_column("warnings", style="magenta")
|
|
130
|
+
table.add_column("errors", style="red")
|
|
131
|
+
table.add_column("valid")
|
|
132
|
+
|
|
133
|
+
for report in reports:
|
|
134
|
+
entry = str(report.expression)
|
|
135
|
+
proj_and_type = str(report.project_id) + " " + report.type + " "
|
|
136
|
+
warnings = "\n".join(["⚠️ " + str(warning) for warning in report.warnings])
|
|
137
|
+
errors = "\n".join(["⚠️ " + str(error) for error in report.errors])
|
|
138
|
+
valid = "✅ Valid" if report else "❌ Invalid"
|
|
139
|
+
|
|
140
|
+
table.add_row("-" * 4, "-" * 4, "-" * 4, "-" * 4, "-" * 4)
|
|
141
|
+
table.add_row(entry, proj_and_type, warnings, errors, valid)
|
|
142
|
+
|
|
143
|
+
console.print(table)
|
|
144
|
+
elif output:
|
|
145
|
+
with open(output, "w") as f:
|
|
146
|
+
for report in reports:
|
|
147
|
+
f.write(str(report) + "\n")
|
|
148
|
+
console.print(f"DRS validation entries saved to [green]{output}[/green]")
|
|
149
|
+
|
|
150
|
+
else:
|
|
151
|
+
for report in reports:
|
|
152
|
+
console.print(str(report))
|
|
153
|
+
|
|
154
|
+
return reports
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@app.command()
|
|
158
|
+
def drsgen(
|
|
159
|
+
drs_entries: Optional[List[str]] = typer.Argument(
|
|
160
|
+
None, help="List of inputs to generate DRS in the form <project> <drstype> <bag_of_terms>"
|
|
161
|
+
),
|
|
162
|
+
file: Optional[typer.FileText] = typer.Option(
|
|
163
|
+
None,
|
|
164
|
+
"--file",
|
|
165
|
+
"-f",
|
|
166
|
+
help="File containing DRS generation inputs, one per line in the form <project> <drstype> <bag_of_terms>",
|
|
167
|
+
),
|
|
168
|
+
verbose: bool = typer.Option(False, "-v", "--verbose", help="Provide detailed generation results"),
|
|
169
|
+
output: Optional[str] = typer.Option(None, "-o", "--output", help="File to save the generated DRS entries"),
|
|
170
|
+
) -> List[DrsGenerationReport]:
|
|
171
|
+
"""
|
|
172
|
+
Generates DRS strings for a specific project and type based on input bag of terms.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
drs_entries (Optional[List[str]]): A list of inputs in the form <project> <drstype> <bag_of_terms>.
|
|
176
|
+
file (Optional[typer.FileText]): File containing DRS generation inputs, one per line.
|
|
177
|
+
verbose (bool): If true, prints detailed generation results.
|
|
178
|
+
output (Optional[str]): File path to save the generated DRS entries.
|
|
179
|
+
|
|
180
|
+
Usage Examples:
|
|
181
|
+
# Generate multiple filenames for CMIP6
|
|
182
|
+
drsgen cmip6 filename var1=tas var2=pr
|
|
183
|
+
|
|
184
|
+
# Generate using a file
|
|
185
|
+
drsgen --file drs_input.txt
|
|
186
|
+
"""
|
|
187
|
+
current_project = None
|
|
188
|
+
current_drs_type = None
|
|
189
|
+
generated_reports = []
|
|
190
|
+
|
|
191
|
+
entries = drs_entries or []
|
|
192
|
+
|
|
193
|
+
if not sys.stdin.isatty(): # Check if input is being piped via stdin
|
|
194
|
+
entries.extend(el for line in sys.stdin for el in shlex.split(line))
|
|
195
|
+
|
|
196
|
+
if file:
|
|
197
|
+
entries.extend(el for line in file for el in shlex.split(line))
|
|
198
|
+
|
|
199
|
+
i = 0
|
|
200
|
+
while i < len(entries):
|
|
201
|
+
if entries[i] in ["", " "]:
|
|
202
|
+
i += 1
|
|
203
|
+
continue
|
|
204
|
+
if entries[i] in projects:
|
|
205
|
+
current_project = entries[i]
|
|
206
|
+
i += 1
|
|
207
|
+
continue
|
|
208
|
+
if entries[i] in drs_types:
|
|
209
|
+
current_drs_type = entries[i]
|
|
210
|
+
i += 1
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
if current_project is None:
|
|
214
|
+
raise typer.BadParameter(f"Invalid project: {entries[i]}")
|
|
215
|
+
|
|
216
|
+
if current_drs_type is None:
|
|
217
|
+
raise typer.BadParameter(f"Invalid drs_type: {entries[i]}")
|
|
218
|
+
|
|
219
|
+
bag_of_terms = entries[i]
|
|
220
|
+
bag_of_terms = set(entries[i].split(" "))
|
|
221
|
+
i += 1
|
|
222
|
+
|
|
223
|
+
generator = DrsGenerator(current_project)
|
|
224
|
+
report = None
|
|
225
|
+
match current_drs_type:
|
|
226
|
+
case "filename":
|
|
227
|
+
report = generator.generate_file_name_from_bag_of_terms(bag_of_terms)
|
|
228
|
+
case "directory":
|
|
229
|
+
report = generator.generate_directory_from_bag_of_terms(bag_of_terms)
|
|
230
|
+
case "dataset":
|
|
231
|
+
report = generator.generate_dataset_id_from_bag_of_terms(bag_of_terms)
|
|
232
|
+
case _:
|
|
233
|
+
raise EsgvocValueError(f"unsupported drs type '{current_drs_type}'")
|
|
234
|
+
generated_reports.append(report)
|
|
235
|
+
|
|
236
|
+
if verbose:
|
|
237
|
+
table = Table(title="Generation result")
|
|
238
|
+
table.add_column("deduced mapping entry", style="cyan")
|
|
239
|
+
table.add_column("warnings", style="magenta")
|
|
240
|
+
table.add_column("errors", style="red")
|
|
241
|
+
table.add_column("result", style="green", width=10)
|
|
242
|
+
for report in generated_reports:
|
|
243
|
+
entry = str(report.mapping_used)
|
|
244
|
+
warnings = "\n".join(["⚠️ " + str(warning) for warning in report.warnings])
|
|
245
|
+
errors = "\n".join([f"🔍 {error}" for error in report.errors])
|
|
246
|
+
result = report.generated_drs_expression
|
|
247
|
+
table.add_row(entry, warnings, errors, result)
|
|
248
|
+
table.add_row("----", "----", "----", "----")
|
|
249
|
+
if table.columns[3].width is not None and len(result) > table.columns[3].width:
|
|
250
|
+
table.columns[3].width = len(result) + 1
|
|
251
|
+
console.print(table)
|
|
252
|
+
|
|
253
|
+
elif output:
|
|
254
|
+
with open(output, "w") as f:
|
|
255
|
+
for report in generated_reports:
|
|
256
|
+
f.write(str(report) + "\n")
|
|
257
|
+
console.print(f"Generated entries saved to [green]{output}[/green]")
|
|
258
|
+
|
|
259
|
+
else:
|
|
260
|
+
for report in generated_reports:
|
|
261
|
+
console.print(str(report))
|
|
262
|
+
|
|
263
|
+
return generated_reports
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
app()
|
esgvoc/cli/find.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from requests import logging
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.json import JSON
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from esgvoc.api.projects import (
|
|
12
|
+
find_collections_in_project,
|
|
13
|
+
find_items_in_project,
|
|
14
|
+
find_terms_in_all_projects,
|
|
15
|
+
find_terms_in_collection,
|
|
16
|
+
find_terms_in_project,
|
|
17
|
+
get_all_projects,
|
|
18
|
+
)
|
|
19
|
+
from esgvoc.api.universe import (
|
|
20
|
+
find_data_descriptors_in_universe,
|
|
21
|
+
find_items_in_universe,
|
|
22
|
+
find_terms_in_data_descriptor,
|
|
23
|
+
find_terms_in_universe,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
app = typer.Typer()
|
|
27
|
+
console = Console()
|
|
28
|
+
|
|
29
|
+
_LOGGER = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def validate_key_format(key: str):
|
|
33
|
+
"""
|
|
34
|
+
Validate if the key matches the XXXX:YYYY:ZZZZ format.
|
|
35
|
+
"""
|
|
36
|
+
if not re.match(r"^[a-zA-Z0-9\/_-]*:[a-zA-Z0-9\/_-]*:[a-zA-Z0-9\/_.-]*$", key):
|
|
37
|
+
raise typer.BadParameter(f"Invalid key format: {key}. Must be XXXX:YYYY:ZZZZ.")
|
|
38
|
+
return key.split(":")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def handle_universe(expression: str, data_descriptor_id: str | None, term_id: str | None, options=None):
|
|
42
|
+
_LOGGER.debug(f"Handling universe with data_descriptor_id={data_descriptor_id}, term_id={term_id}")
|
|
43
|
+
|
|
44
|
+
if data_descriptor_id:
|
|
45
|
+
return find_terms_in_data_descriptor(expression, data_descriptor_id)
|
|
46
|
+
# BaseModel|dict[str: BaseModel]|None:
|
|
47
|
+
|
|
48
|
+
else:
|
|
49
|
+
return find_terms_in_universe(expression)
|
|
50
|
+
# dict[str, dict]:
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def handle_project(expression: str, project_id: str, collection_id: str | None, term_id: str | None, options=None):
|
|
54
|
+
_LOGGER.debug(f"Handling project {project_id} with Y={collection_id}, Z={term_id}, options = {options}")
|
|
55
|
+
|
|
56
|
+
if project_id == "all":
|
|
57
|
+
return find_terms_in_all_projects(expression)
|
|
58
|
+
|
|
59
|
+
elif collection_id:
|
|
60
|
+
return find_terms_in_collection(expression, project_id, collection_id)
|
|
61
|
+
# dict[str, BaseModel]|None:
|
|
62
|
+
|
|
63
|
+
else:
|
|
64
|
+
res = find_terms_in_project(expression, project_id)
|
|
65
|
+
if res is None:
|
|
66
|
+
return None
|
|
67
|
+
else:
|
|
68
|
+
return res
|
|
69
|
+
# dict[str, dict]:
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def handle_unknown(x: str | None, y: str | None, z: str | None):
|
|
73
|
+
print(f"Something wrong in X,Y or Z : X={x}, Y={y}, Z={z}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def display(data: Any):
|
|
77
|
+
if isinstance(data, BaseModel):
|
|
78
|
+
# Pydantic Model
|
|
79
|
+
console.print(JSON.from_data(data.model_dump()))
|
|
80
|
+
elif isinstance(data, dict):
|
|
81
|
+
# Dictionary as JSON
|
|
82
|
+
console.print(data.keys())
|
|
83
|
+
elif isinstance(data, list):
|
|
84
|
+
# List as Table
|
|
85
|
+
table = Table(title="List")
|
|
86
|
+
table.add_column("Index")
|
|
87
|
+
table.add_column("Item")
|
|
88
|
+
for i, item in enumerate(data):
|
|
89
|
+
table.add_row(str(i), str(item))
|
|
90
|
+
console.print(table)
|
|
91
|
+
else:
|
|
92
|
+
# Fallback to simple print
|
|
93
|
+
console.print(data)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.command()
|
|
97
|
+
def find(expression: str, keys: list[str] = typer.Argument(..., help="List of keys in XXXX:YYYY:ZZZZ format")):
|
|
98
|
+
"""
|
|
99
|
+
Retrieve a specific value from the database system.\n
|
|
100
|
+
This command allows you to fetch a value by specifying the universe/project, data_descriptor/collection,
|
|
101
|
+
and term in a structured format.\n
|
|
102
|
+
\n
|
|
103
|
+
|
|
104
|
+
Usage:\n
|
|
105
|
+
`find <expression> <project>:<collection>:<term>`\n
|
|
106
|
+
\n
|
|
107
|
+
Arguments:\n
|
|
108
|
+
<expression>\t The full text search expression.
|
|
109
|
+
<project>\tThe project id to query. like `cmip6plus`\n
|
|
110
|
+
<collection>\tThe collection id in the specified database.\n
|
|
111
|
+
<term>\t\tThe term id within the specified collection.\n
|
|
112
|
+
\n
|
|
113
|
+
Example:
|
|
114
|
+
\n
|
|
115
|
+
Notes:\n
|
|
116
|
+
- Ensure data exist in your system before using this command (use `esgvoc status` command to see whats available).\n
|
|
117
|
+
- Use a colon (`:`) to separate the parts of the argument. \n
|
|
118
|
+
- if more than one argument is given i.e get X:Y:Z A:B:C the 2 results are appended. \n
|
|
119
|
+
\n
|
|
120
|
+
"""
|
|
121
|
+
known_projects = get_all_projects()
|
|
122
|
+
_LOGGER.debug(f"Processed expression: {expression}")
|
|
123
|
+
|
|
124
|
+
# Validate and process each key
|
|
125
|
+
for key in keys:
|
|
126
|
+
validated_key = validate_key_format(key)
|
|
127
|
+
_LOGGER.debug(f"Processed key: {validated_key}")
|
|
128
|
+
where, what, who = validated_key
|
|
129
|
+
what = what if what != "" else None
|
|
130
|
+
who = who if who != "" else None
|
|
131
|
+
if where == "" or where == "universe":
|
|
132
|
+
res = handle_universe(expression, what, who)
|
|
133
|
+
elif where in known_projects:
|
|
134
|
+
res = handle_project(expression, where, what, who, None)
|
|
135
|
+
else:
|
|
136
|
+
res = handle_unknown(where, what, who)
|
|
137
|
+
|
|
138
|
+
display(res)
|
esgvoc/cli/get.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from typing import Any, List, Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.json import JSON
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from esgvoc.api.projects import (
|
|
12
|
+
get_all_collections_in_project,
|
|
13
|
+
get_all_projects,
|
|
14
|
+
get_all_terms_in_collection,
|
|
15
|
+
get_term_in_collection,
|
|
16
|
+
get_term_in_project,
|
|
17
|
+
)
|
|
18
|
+
from esgvoc.api.universe import (
|
|
19
|
+
get_all_data_descriptors_in_universe,
|
|
20
|
+
get_all_terms_in_data_descriptor,
|
|
21
|
+
get_term_in_data_descriptor,
|
|
22
|
+
get_term_in_universe,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
app = typer.Typer()
|
|
26
|
+
console = Console()
|
|
27
|
+
|
|
28
|
+
_LOGGER = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def validate_key_format(key: str):
|
|
32
|
+
"""
|
|
33
|
+
Validate if the key matches the XXXX:YYYY:ZZZZ format.
|
|
34
|
+
"""
|
|
35
|
+
if not re.match(r"^[a-zA-Z0-9\/_-]*:[a-zA-Z0-9\/_-]*:[a-zA-Z0-9\/_.-]*$", key):
|
|
36
|
+
raise typer.BadParameter(f"Invalid key format: {key}. Must be XXXX:YYYY:ZZZZ.")
|
|
37
|
+
return key.split(":")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def handle_universe(data_descriptor_id: str | None, term_id: str | None, options=None):
|
|
41
|
+
_LOGGER.debug(f"Handling universe with data_descriptor_id={data_descriptor_id}, term_id={term_id}")
|
|
42
|
+
if data_descriptor_id and term_id:
|
|
43
|
+
return get_term_in_data_descriptor(data_descriptor_id, term_id, options)
|
|
44
|
+
# BaseModel|dict[str: BaseModel]|None:
|
|
45
|
+
|
|
46
|
+
elif term_id:
|
|
47
|
+
return get_term_in_universe(term_id, options)
|
|
48
|
+
# dict[str, BaseModel] | dict[str, dict[str, BaseModel]] | None:
|
|
49
|
+
|
|
50
|
+
elif data_descriptor_id:
|
|
51
|
+
return get_all_terms_in_data_descriptor(data_descriptor_id, options)
|
|
52
|
+
# dict[str, BaseModel]|None:
|
|
53
|
+
|
|
54
|
+
else:
|
|
55
|
+
return get_all_data_descriptors_in_universe()
|
|
56
|
+
# dict[str, dict]:
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def handle_project(project_id: str, collection_id: str | None, term_id: str | None, options=None):
|
|
60
|
+
_LOGGER.debug(f"Handling project {project_id} with Y={collection_id}, Z={term_id}, options = {options}")
|
|
61
|
+
|
|
62
|
+
if project_id and collection_id and term_id:
|
|
63
|
+
return get_term_in_collection(project_id, collection_id, term_id, options)
|
|
64
|
+
# BaseModel|dict[str: BaseModel]|None:
|
|
65
|
+
|
|
66
|
+
elif term_id:
|
|
67
|
+
return get_term_in_project(project_id, term_id, options)
|
|
68
|
+
# dict[str, BaseModel] | dict[str, dict[str, BaseModel]] | None:
|
|
69
|
+
|
|
70
|
+
elif collection_id:
|
|
71
|
+
return get_all_terms_in_collection(project_id, collection_id, options)
|
|
72
|
+
# dict[str, BaseModel]|None:
|
|
73
|
+
|
|
74
|
+
else:
|
|
75
|
+
res = get_all_collections_in_project(project_id)
|
|
76
|
+
if res is None:
|
|
77
|
+
return None
|
|
78
|
+
else:
|
|
79
|
+
return res
|
|
80
|
+
# dict[str, dict]:
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def handle_unknown(x: str | None, y: str | None, z: str | None):
|
|
84
|
+
print(f"Something wrong in X,Y or Z : X={x}, Y={y}, Z={z}")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def display(data: Any):
|
|
88
|
+
if isinstance(data, BaseModel):
|
|
89
|
+
# Pydantic Model - use mode='json' to serialize datetime and other types
|
|
90
|
+
console.print(JSON.from_data(data.model_dump(mode="json")))
|
|
91
|
+
elif isinstance(data, dict):
|
|
92
|
+
# Dictionary as JSON
|
|
93
|
+
console.print(data.keys())
|
|
94
|
+
elif isinstance(data, list):
|
|
95
|
+
# List as Table
|
|
96
|
+
table = Table(title="List")
|
|
97
|
+
table.add_column("Index")
|
|
98
|
+
table.add_column("Item")
|
|
99
|
+
for i, item in enumerate(data):
|
|
100
|
+
table.add_row(str(i), str(item))
|
|
101
|
+
console.print(table)
|
|
102
|
+
else:
|
|
103
|
+
# Fallback to simple print
|
|
104
|
+
console.print(data)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@app.command()
|
|
108
|
+
def get(
|
|
109
|
+
keys: List[str] = typer.Argument(..., help="List of keys in XXXX:YYYY:ZZZZ format"),
|
|
110
|
+
select: Optional[List[str]] = typer.Option(None, "--select", help="keys selected for the result"),
|
|
111
|
+
):
|
|
112
|
+
"""
|
|
113
|
+
Retrieve a specific value from the database system.\n
|
|
114
|
+
This command allows you to fetch a value by specifying the universe/project, data_descriptor/collection,
|
|
115
|
+
and term in a structured format.\n
|
|
116
|
+
\n
|
|
117
|
+
|
|
118
|
+
Usage:\n
|
|
119
|
+
`get <project>:<collection>:<term>`\n
|
|
120
|
+
\n
|
|
121
|
+
Arguments:\n
|
|
122
|
+
<project>\tThe project id to query. like `cmip6plus`\n
|
|
123
|
+
<collection>\tThe collection id in the specified database.\n
|
|
124
|
+
<term>\t\tThe term id within the specified collection.\n
|
|
125
|
+
\n
|
|
126
|
+
Example:
|
|
127
|
+
To retrieve the value from the "cmip6plus" project, under the "institution_id" column, the term with the identifier "ipsl", you would use: \n
|
|
128
|
+
`get cmip6plus:institution_id:ipsl`\n
|
|
129
|
+
The default project is the universe CV : the argument would be like `universe:institution:ipsl` or `:institution:ipsl` \n
|
|
130
|
+
- to get list of available term from universe institution `:institution:` \n
|
|
131
|
+
\n
|
|
132
|
+
\n
|
|
133
|
+
Notes:\n
|
|
134
|
+
- Ensure data exist in your system before using this command (use `esgvoc status` command to see whats available).\n
|
|
135
|
+
- Use a colon (`:`) to separate the parts of the argument. \n
|
|
136
|
+
- if more than one argument is given i.e get X:Y:Z A:B:C the 2 results are appended. \n
|
|
137
|
+
\n
|
|
138
|
+
"""
|
|
139
|
+
known_projects = get_all_projects()
|
|
140
|
+
|
|
141
|
+
# Validate and process each key
|
|
142
|
+
for key in keys:
|
|
143
|
+
validated_key = validate_key_format(key)
|
|
144
|
+
_LOGGER.debug(f"Processed key: {validated_key}")
|
|
145
|
+
where, what, who = validated_key
|
|
146
|
+
what = what if what != "" else None
|
|
147
|
+
who = who if who != "" else None
|
|
148
|
+
if where == "" or where == "universe":
|
|
149
|
+
res = handle_universe(what, who, select)
|
|
150
|
+
elif where in known_projects:
|
|
151
|
+
res = handle_project(where, what, who, select)
|
|
152
|
+
else:
|
|
153
|
+
res = handle_unknown(where, what, who)
|
|
154
|
+
|
|
155
|
+
display(res)
|
esgvoc/cli/install.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from esgvoc.core.service import current_state
|
|
3
|
+
|
|
4
|
+
app = typer.Typer()
|
|
5
|
+
|
|
6
|
+
@app.command()
|
|
7
|
+
def install():
|
|
8
|
+
"""Initialize default config and apply settings"""
|
|
9
|
+
try:
|
|
10
|
+
typer.echo("Initialized default configuration")
|
|
11
|
+
|
|
12
|
+
# Check if any components are in offline mode
|
|
13
|
+
offline_components = []
|
|
14
|
+
if current_state.universe.offline_mode:
|
|
15
|
+
offline_components.append("universe")
|
|
16
|
+
for project_name, project in current_state.projects.items():
|
|
17
|
+
if project.offline_mode:
|
|
18
|
+
offline_components.append(project_name)
|
|
19
|
+
|
|
20
|
+
if offline_components:
|
|
21
|
+
typer.echo(f"Note: The following components are in offline mode: {', '.join(offline_components)}")
|
|
22
|
+
typer.echo("Only local repositories and databases will be used.")
|
|
23
|
+
|
|
24
|
+
current_state.synchronize_all()
|
|
25
|
+
|
|
26
|
+
# Ensure versions are properly fetched after synchronization
|
|
27
|
+
current_state.fetch_versions()
|
|
28
|
+
current_state.get_state_summary()
|
|
29
|
+
|
|
30
|
+
# Display final status after installation
|
|
31
|
+
from rich.console import Console
|
|
32
|
+
console = Console()
|
|
33
|
+
typer.echo("\nInstallation completed. Final status:")
|
|
34
|
+
console.print(current_state.table())
|
|
35
|
+
|
|
36
|
+
except Exception as e:
|
|
37
|
+
typer.echo(f"Error during installation: {str(e)}", err=True)
|
|
38
|
+
raise typer.Exit(1)
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
app()
|
esgvoc/cli/main.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
|
|
5
|
+
from esgvoc.cli.clean import app as clean_app
|
|
6
|
+
from esgvoc.cli.cmor import app as cmor_app
|
|
7
|
+
from esgvoc.cli.config import app as config_app
|
|
8
|
+
from esgvoc.cli.drs import app as drs_app
|
|
9
|
+
from esgvoc.cli.find import app as find_app
|
|
10
|
+
from esgvoc.cli.get import app as get_app
|
|
11
|
+
from esgvoc.cli.install import app as install_app
|
|
12
|
+
from esgvoc.cli.offline import app as offline_app
|
|
13
|
+
from esgvoc.cli.status import app as status_app
|
|
14
|
+
from esgvoc.cli.test_cv import app as test_cv_app
|
|
15
|
+
from esgvoc.cli.valid import app as valid_app
|
|
16
|
+
from esgvoc.core.service.configuration.setting import ServiceSettings
|
|
17
|
+
|
|
18
|
+
app = typer.Typer()
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
# Register the subcommands
|
|
22
|
+
app.add_typer(get_app)
|
|
23
|
+
app.add_typer(status_app)
|
|
24
|
+
app.add_typer(valid_app)
|
|
25
|
+
app.add_typer(install_app)
|
|
26
|
+
app.add_typer(drs_app)
|
|
27
|
+
app.add_typer(config_app, name="config")
|
|
28
|
+
app.add_typer(offline_app, name="offline")
|
|
29
|
+
app.add_typer(clean_app, name="clean")
|
|
30
|
+
app.add_typer(test_cv_app, name="test")
|
|
31
|
+
app.add_typer(find_app)
|
|
32
|
+
app.add_typer(cmor_app)
|
|
33
|
+
|
|
34
|
+
# maybe remove during a future refactor
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command()
|
|
38
|
+
def list_projects():
|
|
39
|
+
"""List all available projects with their default configurations."""
|
|
40
|
+
default_configs = ServiceSettings._get_default_project_configs()
|
|
41
|
+
|
|
42
|
+
table = Table(title="Available Projects")
|
|
43
|
+
table.add_column("Project Name", style="cyan")
|
|
44
|
+
table.add_column("Repository", style="green")
|
|
45
|
+
table.add_column("Default Branch", style="yellow")
|
|
46
|
+
table.add_column("Local Path", style="blue")
|
|
47
|
+
|
|
48
|
+
for project_name, config in default_configs.items():
|
|
49
|
+
table.add_row(project_name, config["github_repo"], config["branch"], config["local_path"])
|
|
50
|
+
|
|
51
|
+
console.print(table)
|
|
52
|
+
console.print(f"\n[blue]Total: {len(default_configs)} projects available[/blue]")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main():
|
|
56
|
+
app()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
main()
|