esgvoc 0.4.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of esgvoc might be problematic. Click here for more details.
- esgvoc/__init__.py +1 -1
- esgvoc/api/data_descriptors/__init__.py +50 -28
- esgvoc/api/data_descriptors/activity.py +3 -3
- esgvoc/api/data_descriptors/area_label.py +16 -1
- esgvoc/api/data_descriptors/branded_suffix.py +20 -0
- esgvoc/api/data_descriptors/branded_variable.py +12 -0
- esgvoc/api/data_descriptors/consortium.py +14 -13
- esgvoc/api/data_descriptors/contact.py +5 -0
- esgvoc/api/data_descriptors/conventions.py +6 -0
- esgvoc/api/data_descriptors/creation_date.py +5 -0
- esgvoc/api/data_descriptors/data_descriptor.py +14 -9
- esgvoc/api/data_descriptors/data_specs_version.py +5 -0
- esgvoc/api/data_descriptors/date.py +1 -1
- esgvoc/api/data_descriptors/directory_date.py +1 -1
- esgvoc/api/data_descriptors/experiment.py +13 -11
- esgvoc/api/data_descriptors/forcing_index.py +1 -1
- esgvoc/api/data_descriptors/frequency.py +3 -3
- esgvoc/api/data_descriptors/further_info_url.py +5 -0
- esgvoc/api/data_descriptors/grid_label.py +2 -2
- esgvoc/api/data_descriptors/horizontal_label.py +15 -1
- esgvoc/api/data_descriptors/initialisation_index.py +1 -1
- esgvoc/api/data_descriptors/institution.py +8 -5
- esgvoc/api/data_descriptors/known_branded_variable.py +23 -0
- esgvoc/api/data_descriptors/license.py +3 -3
- esgvoc/api/data_descriptors/mip_era.py +1 -1
- esgvoc/api/data_descriptors/model_component.py +1 -1
- esgvoc/api/data_descriptors/obs_type.py +5 -0
- esgvoc/api/data_descriptors/organisation.py +1 -1
- esgvoc/api/data_descriptors/physic_index.py +1 -1
- esgvoc/api/data_descriptors/product.py +2 -2
- esgvoc/api/data_descriptors/publication_status.py +5 -0
- esgvoc/api/data_descriptors/realisation_index.py +1 -1
- esgvoc/api/data_descriptors/realm.py +1 -1
- esgvoc/api/data_descriptors/region.py +5 -0
- esgvoc/api/data_descriptors/resolution.py +3 -3
- esgvoc/api/data_descriptors/source.py +9 -5
- esgvoc/api/data_descriptors/source_type.py +1 -1
- esgvoc/api/data_descriptors/table.py +3 -2
- esgvoc/api/data_descriptors/temporal_label.py +15 -1
- esgvoc/api/data_descriptors/time_range.py +4 -3
- esgvoc/api/data_descriptors/title.py +5 -0
- esgvoc/api/data_descriptors/tracking_id.py +5 -0
- esgvoc/api/data_descriptors/variable.py +25 -12
- esgvoc/api/data_descriptors/variant_label.py +3 -3
- esgvoc/api/data_descriptors/vertical_label.py +14 -0
- esgvoc/api/project_specs.py +117 -2
- esgvoc/api/projects.py +242 -279
- esgvoc/api/search.py +30 -3
- esgvoc/api/universe.py +42 -27
- esgvoc/apps/jsg/cmip6_template.json +74 -0
- esgvoc/apps/jsg/cmip6plus_template.json +74 -0
- esgvoc/apps/jsg/json_schema_generator.py +185 -0
- esgvoc/cli/config.py +500 -0
- esgvoc/cli/find.py +138 -0
- esgvoc/cli/get.py +43 -38
- esgvoc/cli/main.py +10 -3
- esgvoc/cli/status.py +27 -18
- esgvoc/cli/valid.py +10 -15
- esgvoc/core/db/models/project.py +11 -11
- esgvoc/core/db/models/universe.py +3 -3
- esgvoc/core/db/project_ingestion.py +40 -40
- esgvoc/core/db/universe_ingestion.py +36 -33
- esgvoc/core/logging_handler.py +24 -2
- esgvoc/core/repo_fetcher.py +61 -59
- esgvoc/core/service/data_merger.py +47 -34
- esgvoc/core/service/state.py +107 -83
- {esgvoc-0.4.0.dist-info → esgvoc-1.0.0.dist-info}/METADATA +7 -20
- esgvoc-1.0.0.dist-info/RECORD +95 -0
- esgvoc/core/logging.conf +0 -21
- esgvoc-0.4.0.dist-info/RECORD +0 -80
- {esgvoc-0.4.0.dist-info → esgvoc-1.0.0.dist-info}/WHEEL +0 -0
- {esgvoc-0.4.0.dist-info → esgvoc-1.0.0.dist-info}/entry_points.txt +0 -0
- {esgvoc-0.4.0.dist-info → esgvoc-1.0.0.dist-info}/licenses/LICENSE.txt +0 -0
esgvoc/cli/config.py
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
import toml
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.syntax import Syntax
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
import esgvoc.core.service as service
|
|
12
|
+
from esgvoc.core.service.configuration.setting import ServiceSettings
|
|
13
|
+
|
|
14
|
+
app = typer.Typer()
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def display(table):
|
|
19
|
+
"""
|
|
20
|
+
Function to display a rich table in the console.
|
|
21
|
+
|
|
22
|
+
:param table: The table to be displayed
|
|
23
|
+
"""
|
|
24
|
+
console = Console(record=True, width=200)
|
|
25
|
+
console.print(table)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@app.command()
|
|
29
|
+
def list():
|
|
30
|
+
"""
|
|
31
|
+
List all available configurations.
|
|
32
|
+
|
|
33
|
+
Displays all available configurations along with the active one.
|
|
34
|
+
"""
|
|
35
|
+
config_manager = service.get_config_manager()
|
|
36
|
+
configs = config_manager.list_configs()
|
|
37
|
+
active_config = config_manager.get_active_config_name()
|
|
38
|
+
|
|
39
|
+
table = Table(title="Available Configurations")
|
|
40
|
+
table.add_column("Name", style="cyan")
|
|
41
|
+
table.add_column("Path", style="green")
|
|
42
|
+
table.add_column("Status", style="magenta")
|
|
43
|
+
|
|
44
|
+
for name, path in configs.items():
|
|
45
|
+
status = "🟢 Active" if name == active_config else ""
|
|
46
|
+
table.add_row(name, path, status)
|
|
47
|
+
|
|
48
|
+
display(table)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command()
|
|
52
|
+
def show(
|
|
53
|
+
name: Optional[str] = typer.Argument(
|
|
54
|
+
None, help="Name of the configuration to show. If not provided, shows the active configuration."
|
|
55
|
+
),
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Show the content of a specific configuration.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
name: Name of the configuration to show. Shows the active configuration if not specified.
|
|
62
|
+
"""
|
|
63
|
+
config_manager = service.get_config_manager()
|
|
64
|
+
if name is None:
|
|
65
|
+
name = config_manager.get_active_config_name()
|
|
66
|
+
console.print(f"Showing active configuration: [cyan]{name}[/cyan]")
|
|
67
|
+
|
|
68
|
+
configs = config_manager.list_configs()
|
|
69
|
+
if name not in configs:
|
|
70
|
+
console.print(f"[red]Error: Configuration '{name}' not found.[/red]")
|
|
71
|
+
raise typer.Exit(1)
|
|
72
|
+
|
|
73
|
+
config_path = configs[name]
|
|
74
|
+
try:
|
|
75
|
+
with open(config_path, "r") as f:
|
|
76
|
+
content = f.read()
|
|
77
|
+
|
|
78
|
+
syntax = Syntax(content, "toml", theme="monokai", line_numbers=True)
|
|
79
|
+
console.print(syntax)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
console.print(f"[red]Error reading configuration file: {str(e)}[/red]")
|
|
82
|
+
raise typer.Exit(1)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@app.command()
|
|
86
|
+
def switch(name: str = typer.Argument(..., help="Name of the configuration to switch to.")):
|
|
87
|
+
"""
|
|
88
|
+
Switch to a different configuration.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
name: Name of the configuration to switch to.
|
|
92
|
+
"""
|
|
93
|
+
config_manager = service.get_config_manager()
|
|
94
|
+
configs = config_manager.list_configs()
|
|
95
|
+
|
|
96
|
+
if name not in configs:
|
|
97
|
+
console.print(f"[red]Error: Configuration '{name}' not found.[/red]")
|
|
98
|
+
raise typer.Exit(1)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
config_manager.switch_config(name)
|
|
102
|
+
console.print(f"[green]Successfully switched to configuration: [cyan]{name}[/cyan][/green]")
|
|
103
|
+
|
|
104
|
+
# Reset the state to use the new configuration
|
|
105
|
+
service.current_state = service.get_state()
|
|
106
|
+
except Exception as e:
|
|
107
|
+
console.print(f"[red]Error switching configuration: {str(e)}[/red]")
|
|
108
|
+
raise typer.Exit(1)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@app.command()
|
|
112
|
+
def create(
|
|
113
|
+
name: str = typer.Argument(..., help="Name for the new configuration."),
|
|
114
|
+
base: Optional[str] = typer.Option(
|
|
115
|
+
None, "--base", "-b", help="Base the new configuration on an existing one. Uses the default if not specified."
|
|
116
|
+
),
|
|
117
|
+
switch_to: bool = typer.Option(False, "--switch", "-s", help="Switch to the new configuration after creating it."),
|
|
118
|
+
):
|
|
119
|
+
"""
|
|
120
|
+
Create a new configuration.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
name: Name for the new configuration.
|
|
124
|
+
base: Base the new configuration on an existing one. Uses the default if not specified.
|
|
125
|
+
switch_to: Switch to the new configuration after creating it.
|
|
126
|
+
"""
|
|
127
|
+
config_manager = service.get_config_manager()
|
|
128
|
+
configs = config_manager.list_configs()
|
|
129
|
+
|
|
130
|
+
if name in configs:
|
|
131
|
+
console.print(f"[red]Error: Configuration '{name}' already exists.[/red]")
|
|
132
|
+
raise typer.Exit(1)
|
|
133
|
+
|
|
134
|
+
if base and base not in configs:
|
|
135
|
+
console.print(f"[red]Error: Base configuration '{base}' not found.[/red]")
|
|
136
|
+
raise typer.Exit(1)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
if base:
|
|
140
|
+
# Load the base configuration
|
|
141
|
+
base_config = config_manager.get_config(base)
|
|
142
|
+
config_data = base_config.dump()
|
|
143
|
+
else:
|
|
144
|
+
# Use default settings
|
|
145
|
+
config_data = ServiceSettings.DEFAULT_SETTINGS
|
|
146
|
+
|
|
147
|
+
# Add the new configuration
|
|
148
|
+
config_manager.add_config(name, config_data)
|
|
149
|
+
console.print(f"[green]Successfully created configuration: [cyan]{name}[/cyan][/green]")
|
|
150
|
+
|
|
151
|
+
if switch_to:
|
|
152
|
+
config_manager.switch_config(name)
|
|
153
|
+
console.print(f"[green]Switched to configuration: [cyan]{name}[/cyan][/green]")
|
|
154
|
+
# Reset the state to use the new configuration
|
|
155
|
+
service.current_state = service.get_state()
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
console.print(f"[red]Error creating configuration: {str(e)}[/red]")
|
|
159
|
+
raise typer.Exit(1)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@app.command()
|
|
163
|
+
def remove(name: str = typer.Argument(..., help="Name of the configuration to remove.")):
|
|
164
|
+
"""
|
|
165
|
+
Remove a configuration.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
name: Name of the configuration to remove.
|
|
169
|
+
"""
|
|
170
|
+
config_manager = service.get_config_manager()
|
|
171
|
+
configs = config_manager.list_configs()
|
|
172
|
+
|
|
173
|
+
if name not in configs:
|
|
174
|
+
console.print(f"[red]Error: Configuration '{name}' not found.[/red]")
|
|
175
|
+
raise typer.Exit(1)
|
|
176
|
+
|
|
177
|
+
if name == "default":
|
|
178
|
+
console.print("[red]Error: Cannot remove the default configuration.[/red]")
|
|
179
|
+
raise typer.Exit(1)
|
|
180
|
+
|
|
181
|
+
confirm = typer.confirm(f"Are you sure you want to remove configuration '{name}'?")
|
|
182
|
+
if not confirm:
|
|
183
|
+
console.print("Operation cancelled.")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
active_config = config_manager.get_active_config_name()
|
|
188
|
+
config_manager.remove_config(name)
|
|
189
|
+
console.print(f"[green]Successfully removed configuration: [cyan]{name}[/cyan][/green]")
|
|
190
|
+
|
|
191
|
+
if active_config == name:
|
|
192
|
+
console.print("[yellow]Active configuration was removed. Switched to default.[/yellow]")
|
|
193
|
+
# Reset the state to use the default configuration
|
|
194
|
+
service.current_state = service.get_state()
|
|
195
|
+
except Exception as e:
|
|
196
|
+
console.print(f"[red]Error removing configuration: {str(e)}[/red]")
|
|
197
|
+
raise typer.Exit(1)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@app.command()
|
|
201
|
+
def edit(
|
|
202
|
+
name: Optional[str] = typer.Argument(
|
|
203
|
+
None, help="Name of the configuration to edit. Edits the active configuration if not specified."
|
|
204
|
+
),
|
|
205
|
+
editor: Optional[str] = typer.Option(
|
|
206
|
+
None, "--editor", "-e", help="Editor to use. Uses the system default if not specified."
|
|
207
|
+
),
|
|
208
|
+
):
|
|
209
|
+
"""
|
|
210
|
+
Edit a configuration using the system's default editor or a specified one.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
name: Name of the configuration to edit. Edits the active configuration if not specified.
|
|
214
|
+
editor: Editor to use. Uses the system default if not specified.
|
|
215
|
+
"""
|
|
216
|
+
config_manager = service.get_config_manager()
|
|
217
|
+
if name is None:
|
|
218
|
+
name = config_manager.get_active_config_name()
|
|
219
|
+
console.print(f"Editing active configuration: [cyan]{name}[/cyan]")
|
|
220
|
+
|
|
221
|
+
configs = config_manager.list_configs()
|
|
222
|
+
if name not in configs:
|
|
223
|
+
console.print(f"[red]Error: Configuration '{name}' not found.[/red]")
|
|
224
|
+
raise typer.Exit(1)
|
|
225
|
+
|
|
226
|
+
config_path = configs[name]
|
|
227
|
+
|
|
228
|
+
editor_cmd = editor or os.environ.get("EDITOR", "vim")
|
|
229
|
+
try:
|
|
230
|
+
# Launch the editor properly by using a list of arguments instead of a string
|
|
231
|
+
import subprocess
|
|
232
|
+
|
|
233
|
+
result = subprocess.run([editor_cmd, str(config_path)], check=True)
|
|
234
|
+
if result.returncode == 0:
|
|
235
|
+
console.print(f"[green]Successfully edited configuration: [cyan]{name}[/cyan][/green]")
|
|
236
|
+
|
|
237
|
+
# Reset the state if we edited the active configuration
|
|
238
|
+
if name == config_manager.get_active_config_name():
|
|
239
|
+
service.current_state = service.get_state()
|
|
240
|
+
else:
|
|
241
|
+
console.print("[yellow]Editor exited with an error.[/yellow]")
|
|
242
|
+
except Exception as e:
|
|
243
|
+
console.print(f"[red]Error launching editor: {str(e)}[/red]")
|
|
244
|
+
raise typer.Exit(1)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@app.command()
|
|
248
|
+
def set(
|
|
249
|
+
changes: List[str] = typer.Argument(
|
|
250
|
+
...,
|
|
251
|
+
help="Changes in format 'component:key=value', where component is 'universe' or a project name. Multiple can be specified.",
|
|
252
|
+
),
|
|
253
|
+
config_name: Optional[str] = typer.Option(
|
|
254
|
+
None,
|
|
255
|
+
"--config",
|
|
256
|
+
"-c",
|
|
257
|
+
help="Name of the configuration to modify. Modifies the active configuration if not specified.",
|
|
258
|
+
),
|
|
259
|
+
):
|
|
260
|
+
"""
|
|
261
|
+
Modify configuration settings using a consistent syntax for universe and projects.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
changes: List of changes in format 'component:key=value'. For example:
|
|
265
|
+
'universe:branch=main' - Change the universe branch
|
|
266
|
+
'cmip6:github_repo=https://github.com/new/repo' - Change a project's repository
|
|
267
|
+
config_name: Name of the configuration to modify. Modifies the active configuration if not specified.
|
|
268
|
+
|
|
269
|
+
Examples:
|
|
270
|
+
# Change the universe branch in the active configuration
|
|
271
|
+
esgvoc set 'universe:branch=esgvoc_dev'
|
|
272
|
+
|
|
273
|
+
# Change multiple components at once
|
|
274
|
+
esgvoc set 'universe:branch=esgvoc_dev' 'cmip6:branch=esgvoc_dev'
|
|
275
|
+
|
|
276
|
+
# Change settings in a specific configuration
|
|
277
|
+
esgvoc set 'universe:local_path=repos/prod/universe' --config prod
|
|
278
|
+
|
|
279
|
+
# Change the GitHub repository URL for a project
|
|
280
|
+
esgvoc set 'cmip6:github_repo=https://github.com/WCRP-CMIP/CMIP6_CVs_new'
|
|
281
|
+
"""
|
|
282
|
+
config_manager = service.get_config_manager()
|
|
283
|
+
if config_name is None:
|
|
284
|
+
config_name = config_manager.get_active_config_name()
|
|
285
|
+
console.print(f"Modifying active configuration: [cyan]{config_name}[/cyan]")
|
|
286
|
+
|
|
287
|
+
configs = config_manager.list_configs()
|
|
288
|
+
if config_name not in configs:
|
|
289
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
290
|
+
raise typer.Exit(1)
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
# Load the configuration
|
|
294
|
+
config = config_manager.get_config(config_name)
|
|
295
|
+
modified = False
|
|
296
|
+
|
|
297
|
+
# Process all changes with the same format
|
|
298
|
+
for change in changes:
|
|
299
|
+
try:
|
|
300
|
+
# Format should be component:setting=value (where component is 'universe' or a project name)
|
|
301
|
+
component_part, setting_part = change.split(":", 1)
|
|
302
|
+
setting_key, setting_value = setting_part.split("=", 1)
|
|
303
|
+
|
|
304
|
+
# Handle universe settings
|
|
305
|
+
if component_part == "universe":
|
|
306
|
+
if setting_key == "github_repo":
|
|
307
|
+
config.universe.github_repo = setting_value
|
|
308
|
+
modified = True
|
|
309
|
+
elif setting_key == "branch":
|
|
310
|
+
config.universe.branch = setting_value
|
|
311
|
+
modified = True
|
|
312
|
+
elif setting_key == "local_path":
|
|
313
|
+
config.universe.local_path = setting_value
|
|
314
|
+
modified = True
|
|
315
|
+
elif setting_key == "db_path":
|
|
316
|
+
config.universe.db_path = setting_value
|
|
317
|
+
modified = True
|
|
318
|
+
else:
|
|
319
|
+
console.print(f"[yellow]Warning: Unknown universe setting '{setting_key}'. Skipping.[/yellow]")
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
console.print(f"[green]Updated universe.{setting_key} = {setting_value}[/green]")
|
|
323
|
+
|
|
324
|
+
# Handle project settings
|
|
325
|
+
elif component_part in config.projects:
|
|
326
|
+
project = config.projects[component_part]
|
|
327
|
+
if setting_key == "github_repo":
|
|
328
|
+
project.github_repo = setting_value
|
|
329
|
+
elif setting_key == "branch":
|
|
330
|
+
project.branch = setting_value
|
|
331
|
+
elif setting_key == "local_path":
|
|
332
|
+
project.local_path = setting_value
|
|
333
|
+
elif setting_key == "db_path":
|
|
334
|
+
project.db_path = setting_value
|
|
335
|
+
else:
|
|
336
|
+
console.print(f"[yellow]Warning: Unknown project setting '{setting_key}'. Skipping.[/yellow]")
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
modified = True
|
|
340
|
+
console.print(f"[green]Updated {component_part}.{setting_key} = {setting_value}[/green]")
|
|
341
|
+
else:
|
|
342
|
+
console.print(
|
|
343
|
+
f"[yellow]Warning: Component '{component_part}' not found in configuration. Skipping.[/yellow]"
|
|
344
|
+
)
|
|
345
|
+
continue
|
|
346
|
+
|
|
347
|
+
except ValueError:
|
|
348
|
+
console.print(
|
|
349
|
+
f"[yellow]Warning: Invalid change format '{change}'. Should be 'component:key=value'. Skipping.[/yellow]"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
if modified:
|
|
353
|
+
# Save the modified configuration
|
|
354
|
+
config_manager.save_active_config(config)
|
|
355
|
+
console.print(f"[green]Successfully updated configuration: [cyan]{config_name}[/cyan][/green]")
|
|
356
|
+
|
|
357
|
+
# Reset the state if we modified the active configuration
|
|
358
|
+
if config_name == config_manager.get_active_config_name():
|
|
359
|
+
service.current_state = service.get_state()
|
|
360
|
+
else:
|
|
361
|
+
console.print("[yellow]No changes were made to the configuration.[/yellow]")
|
|
362
|
+
|
|
363
|
+
except Exception as e:
|
|
364
|
+
console.print(f"[red]Error updating configuration: {str(e)}[/red]")
|
|
365
|
+
raise typer.Exit(1)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@app.command()
|
|
369
|
+
def add_project(
|
|
370
|
+
name: Optional[str] = typer.Argument(
|
|
371
|
+
None, help="Name of the configuration to modify. Modifies the active configuration if not specified."
|
|
372
|
+
),
|
|
373
|
+
project_name: str = typer.Option(..., "--name", "-n", help="Name of the project to add."),
|
|
374
|
+
github_repo: str = typer.Option(..., "--repo", "-r", help="GitHub repository URL for the project."),
|
|
375
|
+
branch: str = typer.Option("main", "--branch", "-b", help="Branch for the project repository."),
|
|
376
|
+
local_path: Optional[str] = typer.Option(None, "--local", "-l", help="Local path for the project repository."),
|
|
377
|
+
db_path: Optional[str] = typer.Option(None, "--db", "-d", help="Database path for the project."),
|
|
378
|
+
):
|
|
379
|
+
"""
|
|
380
|
+
Add a new project to a configuration.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
name: Name of the configuration to modify. Modifies the active configuration if not specified.
|
|
384
|
+
project_name: Name of the project to add.
|
|
385
|
+
github_repo: GitHub repository URL for the project.
|
|
386
|
+
branch: Branch for the project repository.
|
|
387
|
+
local_path: Local path for the project repository.
|
|
388
|
+
db_path: Database path for the project.
|
|
389
|
+
"""
|
|
390
|
+
config_manager = service.get_config_manager()
|
|
391
|
+
if name is None:
|
|
392
|
+
name = config_manager.get_active_config_name()
|
|
393
|
+
console.print(f"Modifying active configuration: [cyan]{name}[/cyan]")
|
|
394
|
+
|
|
395
|
+
configs = config_manager.list_configs()
|
|
396
|
+
if name not in configs:
|
|
397
|
+
console.print(f"[red]Error: Configuration '{name}' not found.[/red]")
|
|
398
|
+
raise typer.Exit(1)
|
|
399
|
+
|
|
400
|
+
try:
|
|
401
|
+
# Load the configuration
|
|
402
|
+
config = config_manager.get_config(name)
|
|
403
|
+
|
|
404
|
+
# Check if project already exists
|
|
405
|
+
if project_name in config.projects:
|
|
406
|
+
console.print(f"[red]Error: Project '{project_name}' already exists in configuration '{name}'.[/red]")
|
|
407
|
+
raise typer.Exit(1)
|
|
408
|
+
|
|
409
|
+
# Set default paths if not provided
|
|
410
|
+
if local_path is None:
|
|
411
|
+
local_path = f"repos/{project_name}"
|
|
412
|
+
if db_path is None:
|
|
413
|
+
db_path = f"dbs/{project_name}.sqlite"
|
|
414
|
+
|
|
415
|
+
# Create the project settings
|
|
416
|
+
from esgvoc.core.service.configuration.setting import ProjectSettings
|
|
417
|
+
|
|
418
|
+
project_settings = ProjectSettings(
|
|
419
|
+
project_name=project_name, github_repo=github_repo, branch=branch, local_path=local_path, db_path=db_path
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
# Add to configuration
|
|
423
|
+
config.projects[project_name] = project_settings
|
|
424
|
+
|
|
425
|
+
# Save the configuration
|
|
426
|
+
config_manager.save_active_config(config)
|
|
427
|
+
console.print(
|
|
428
|
+
f"[green]Successfully added project [cyan]{project_name}[/cyan] to configuration [cyan]{name}[/cyan][/green]"
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Reset the state if we modified the active configuration
|
|
432
|
+
if name == config_manager.get_active_config_name():
|
|
433
|
+
service.current_state = service.get_state()
|
|
434
|
+
|
|
435
|
+
except Exception as e:
|
|
436
|
+
console.print(f"[red]Error adding project: {str(e)}[/red]")
|
|
437
|
+
raise typer.Exit(1)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@app.command()
|
|
441
|
+
def remove_project(
|
|
442
|
+
name: Optional[str] = typer.Argument(
|
|
443
|
+
None, help="Name of the configuration to modify. Modifies the active configuration if not specified."
|
|
444
|
+
),
|
|
445
|
+
project_name: str = typer.Argument(..., help="Name of the project to remove."),
|
|
446
|
+
):
|
|
447
|
+
"""
|
|
448
|
+
Remove a project from a configuration.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
name: Name of the configuration to modify. Modifies the active configuration if not specified.
|
|
452
|
+
project_name: Name of the project to remove.
|
|
453
|
+
"""
|
|
454
|
+
config_manager = service.get_config_manager()
|
|
455
|
+
if name is None:
|
|
456
|
+
name = config_manager.get_active_config_name()
|
|
457
|
+
console.print(f"Modifying active configuration: [cyan]{name}[/cyan]")
|
|
458
|
+
|
|
459
|
+
configs = config_manager.list_configs()
|
|
460
|
+
if name not in configs:
|
|
461
|
+
console.print(f"[red]Error: Configuration '{name}' not found.[/red]")
|
|
462
|
+
raise typer.Exit(1)
|
|
463
|
+
|
|
464
|
+
try:
|
|
465
|
+
# Load the configuration
|
|
466
|
+
config = config_manager.get_config(name)
|
|
467
|
+
|
|
468
|
+
# Check if project exists
|
|
469
|
+
if project_name not in config.projects:
|
|
470
|
+
console.print(f"[red]Error: Project '{project_name}' not found in configuration '{name}'.[/red]")
|
|
471
|
+
raise typer.Exit(1)
|
|
472
|
+
|
|
473
|
+
# Confirm removal
|
|
474
|
+
confirm = typer.confirm(
|
|
475
|
+
f"Are you sure you want to remove project '{project_name}' from configuration '{name}'?"
|
|
476
|
+
)
|
|
477
|
+
if not confirm:
|
|
478
|
+
console.print("Operation cancelled.")
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
# Remove project
|
|
482
|
+
del config.projects[project_name]
|
|
483
|
+
|
|
484
|
+
# Save the configuration
|
|
485
|
+
config_manager.save_active_config(config)
|
|
486
|
+
console.print(
|
|
487
|
+
f"[green]Successfully removed project [cyan]{project_name}[/cyan] from configuration [cyan]{name}[/cyan][/green]"
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
# Reset the state if we modified the active configuration
|
|
491
|
+
if name == config_manager.get_active_config_name():
|
|
492
|
+
service.current_state = service.get_state()
|
|
493
|
+
|
|
494
|
+
except Exception as e:
|
|
495
|
+
console.print(f"[red]Error removing project: {str(e)}[/red]")
|
|
496
|
+
raise typer.Exit(1)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
if __name__ == "__main__":
|
|
500
|
+
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)
|