esgvoc 1.0.1__py3-none-any.whl → 1.1.1__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/__init__.py +0 -6
- esgvoc/api/data_descriptors/__init__.py +6 -0
- esgvoc/api/data_descriptors/archive.py +5 -0
- esgvoc/api/data_descriptors/citation_url.py +5 -0
- esgvoc/api/data_descriptors/experiment.py +2 -2
- esgvoc/api/data_descriptors/known_branded_variable.py +58 -5
- esgvoc/api/data_descriptors/regex.py +5 -0
- esgvoc/api/data_descriptors/vertical_label.py +2 -2
- esgvoc/api/project_specs.py +48 -130
- esgvoc/api/projects.py +104 -63
- esgvoc/apps/drs/generator.py +47 -42
- esgvoc/apps/drs/validator.py +22 -38
- esgvoc/apps/jsg/json_schema_generator.py +252 -136
- esgvoc/apps/jsg/templates/template.jinja +249 -0
- esgvoc/apps/test_cv/README.md +214 -0
- esgvoc/apps/test_cv/cv_tester.py +1368 -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/config.py +671 -86
- esgvoc/cli/drs.py +39 -21
- esgvoc/cli/main.py +2 -0
- esgvoc/cli/test_cv.py +257 -0
- esgvoc/core/constants.py +10 -7
- esgvoc/core/data_handler.py +24 -22
- esgvoc/core/db/connection.py +7 -0
- esgvoc/core/db/project_ingestion.py +34 -9
- esgvoc/core/db/universe_ingestion.py +1 -2
- esgvoc/core/service/configuration/setting.py +192 -21
- esgvoc/core/service/data_merger.py +1 -1
- esgvoc/core/service/state.py +18 -2
- {esgvoc-1.0.1.dist-info → esgvoc-1.1.1.dist-info}/METADATA +2 -1
- {esgvoc-1.0.1.dist-info → esgvoc-1.1.1.dist-info}/RECORD +40 -29
- esgvoc/apps/jsg/cmip6_template.json +0 -74
- /esgvoc/apps/{py.typed → test_cv/__init__.py} +0 -0
- {esgvoc-1.0.1.dist-info → esgvoc-1.1.1.dist-info}/WHEEL +0 -0
- {esgvoc-1.0.1.dist-info → esgvoc-1.1.1.dist-info}/entry_points.txt +0 -0
- {esgvoc-1.0.1.dist-info → esgvoc-1.1.1.dist-info}/licenses/LICENSE.txt +0 -0
esgvoc/cli/config.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import shutil
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import List, Optional
|
|
4
5
|
|
|
@@ -15,6 +16,51 @@ app = typer.Typer()
|
|
|
15
16
|
console = Console()
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
def _get_fresh_config(config_manager, config_name: str):
|
|
20
|
+
"""
|
|
21
|
+
Get a fresh configuration, bypassing any potential caching issues.
|
|
22
|
+
"""
|
|
23
|
+
# Force reload from file to ensure we have the latest state
|
|
24
|
+
configs = config_manager.list_configs()
|
|
25
|
+
config_path = configs[config_name]
|
|
26
|
+
|
|
27
|
+
# Load directly from file to avoid any caching
|
|
28
|
+
try:
|
|
29
|
+
data = toml.load(config_path)
|
|
30
|
+
projects = {p["project_name"]: ServiceSettings.ProjectSettings(**p) for p in data.pop("projects", [])}
|
|
31
|
+
from esgvoc.core.service.configuration.setting import UniverseSettings
|
|
32
|
+
|
|
33
|
+
return ServiceSettings(universe=UniverseSettings(**data["universe"]), projects=projects)
|
|
34
|
+
except Exception:
|
|
35
|
+
# Fallback to config manager if direct load fails
|
|
36
|
+
return config_manager.get_config(config_name)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _save_and_reload_config(config_manager, config_name: str, config):
|
|
40
|
+
"""
|
|
41
|
+
Save configuration and ensure proper state reload.
|
|
42
|
+
"""
|
|
43
|
+
config_manager.save_active_config(config)
|
|
44
|
+
|
|
45
|
+
# Reset the state if we modified the active configuration
|
|
46
|
+
if config_name == config_manager.get_active_config_name():
|
|
47
|
+
service.current_state = service.get_state()
|
|
48
|
+
|
|
49
|
+
# Clear any potential caches in the config manager
|
|
50
|
+
if hasattr(config_manager, "_cached_config"):
|
|
51
|
+
config_manager._cached_config = None
|
|
52
|
+
if hasattr(config_manager, "cache"):
|
|
53
|
+
config_manager.cache.clear()
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
Function to display a rich table in the console.
|
|
57
|
+
|
|
58
|
+
:param table: The table to be displayed
|
|
59
|
+
"""
|
|
60
|
+
console = Console(record=True, width=200)
|
|
61
|
+
console.print(table)
|
|
62
|
+
|
|
63
|
+
|
|
18
64
|
def display(table):
|
|
19
65
|
"""
|
|
20
66
|
Function to display a rich table in the console.
|
|
@@ -321,23 +367,14 @@ def set(
|
|
|
321
367
|
|
|
322
368
|
console.print(f"[green]Updated universe.{setting_key} = {setting_value}[/green]")
|
|
323
369
|
|
|
324
|
-
# Handle project settings
|
|
370
|
+
# Handle project settings using the new update_project method
|
|
325
371
|
elif component_part in config.projects:
|
|
326
|
-
|
|
327
|
-
if setting_key
|
|
328
|
-
|
|
329
|
-
|
|
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
|
|
372
|
+
# Use the new update_project method
|
|
373
|
+
if config.update_project(component_part, **{setting_key: setting_value}):
|
|
374
|
+
modified = True
|
|
375
|
+
console.print(f"[green]Updated {component_part}.{setting_key} = {setting_value}[/green]")
|
|
335
376
|
else:
|
|
336
377
|
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
378
|
else:
|
|
342
379
|
console.print(
|
|
343
380
|
f"[yellow]Warning: Component '{component_part}' not found in configuration. Skipping.[/yellow]"
|
|
@@ -365,73 +402,191 @@ def set(
|
|
|
365
402
|
raise typer.Exit(1)
|
|
366
403
|
|
|
367
404
|
|
|
405
|
+
# 🔹 NEW: Enhanced project management commands using ServiceSettings methods
|
|
406
|
+
|
|
407
|
+
|
|
368
408
|
@app.command()
|
|
369
|
-
def
|
|
370
|
-
|
|
371
|
-
|
|
409
|
+
def list_available_projects():
|
|
410
|
+
"""
|
|
411
|
+
List all available default projects that can be added.
|
|
412
|
+
"""
|
|
413
|
+
available_projects = ServiceSettings.DEFAULT_PROJECT_CONFIGS.keys()
|
|
414
|
+
|
|
415
|
+
table = Table(title="Available Default Projects")
|
|
416
|
+
table.add_column("Project Name", style="cyan")
|
|
417
|
+
table.add_column("Repository", style="green")
|
|
418
|
+
table.add_column("Branch", style="yellow")
|
|
419
|
+
|
|
420
|
+
for project_name in available_projects:
|
|
421
|
+
config = ServiceSettings.DEFAULT_PROJECT_CONFIGS[project_name]
|
|
422
|
+
table.add_row(project_name, config["github_repo"], config["branch"])
|
|
423
|
+
|
|
424
|
+
display(table)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
@app.command()
|
|
428
|
+
def list_projects(
|
|
429
|
+
config_name: Optional[str] = typer.Option(
|
|
430
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
372
431
|
),
|
|
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
432
|
):
|
|
379
433
|
"""
|
|
380
|
-
|
|
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.
|
|
434
|
+
List all projects in a configuration.
|
|
389
435
|
"""
|
|
390
436
|
config_manager = service.get_config_manager()
|
|
391
|
-
if
|
|
392
|
-
|
|
393
|
-
console.print(f"
|
|
437
|
+
if config_name is None:
|
|
438
|
+
config_name = config_manager.get_active_config_name()
|
|
439
|
+
console.print(f"Showing projects in active configuration: [cyan]{config_name}[/cyan]")
|
|
394
440
|
|
|
395
441
|
configs = config_manager.list_configs()
|
|
396
|
-
if
|
|
397
|
-
console.print(f"[red]Error: Configuration '{
|
|
442
|
+
if config_name not in configs:
|
|
443
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
398
444
|
raise typer.Exit(1)
|
|
399
445
|
|
|
400
446
|
try:
|
|
401
|
-
|
|
402
|
-
config = config_manager.get_config(name)
|
|
447
|
+
config = config_manager.get_config(config_name)
|
|
403
448
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
raise typer.Exit(1)
|
|
449
|
+
if not config.projects:
|
|
450
|
+
console.print(f"[yellow]No projects found in configuration '{config_name}'.[/yellow]")
|
|
451
|
+
return
|
|
408
452
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
453
|
+
table = Table(title=f"Projects in Configuration: {config_name}")
|
|
454
|
+
table.add_column("Project Name", style="cyan")
|
|
455
|
+
table.add_column("Repository", style="green")
|
|
456
|
+
table.add_column("Branch", style="yellow")
|
|
457
|
+
table.add_column("Local Path", style="blue")
|
|
458
|
+
table.add_column("DB Path", style="magenta")
|
|
414
459
|
|
|
415
|
-
|
|
416
|
-
|
|
460
|
+
for project_name, project in config.projects.items():
|
|
461
|
+
table.add_row(
|
|
462
|
+
project_name,
|
|
463
|
+
project.github_repo,
|
|
464
|
+
project.branch or "main",
|
|
465
|
+
project.local_path or "N/A",
|
|
466
|
+
project.db_path or "N/A",
|
|
467
|
+
)
|
|
417
468
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
469
|
+
display(table)
|
|
470
|
+
|
|
471
|
+
except Exception as e:
|
|
472
|
+
console.print(f"[red]Error listing projects: {str(e)}[/red]")
|
|
473
|
+
raise typer.Exit(1)
|
|
421
474
|
|
|
422
|
-
|
|
423
|
-
|
|
475
|
+
|
|
476
|
+
@app.command()
|
|
477
|
+
def add_project(
|
|
478
|
+
project_name: str = typer.Argument(..., help="Name of the project to add."),
|
|
479
|
+
config_name: Optional[str] = typer.Option(
|
|
480
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
481
|
+
),
|
|
482
|
+
from_default: bool = typer.Option(
|
|
483
|
+
True, "--from-default/--custom", help="Add from default configuration or specify custom settings."
|
|
484
|
+
),
|
|
485
|
+
# Custom project options (only used when --custom is specified)
|
|
486
|
+
github_repo: Optional[str] = typer.Option(
|
|
487
|
+
None, "--repo", "-r", help="GitHub repository URL (for custom projects)."
|
|
488
|
+
),
|
|
489
|
+
branch: Optional[str] = typer.Option("main", "--branch", "-b", help="Branch (for custom projects)."),
|
|
490
|
+
local_path: Optional[str] = typer.Option(None, "--local", "-l", help="Local path (for custom projects)."),
|
|
491
|
+
db_path: Optional[str] = typer.Option(None, "--db", "-d", help="Database path (for custom projects)."),
|
|
492
|
+
):
|
|
493
|
+
"""
|
|
494
|
+
Add a project to a configuration.
|
|
495
|
+
|
|
496
|
+
By default, adds from available default projects. Use --custom to specify custom settings.
|
|
497
|
+
|
|
498
|
+
Examples:
|
|
499
|
+
# Add a default project
|
|
500
|
+
esgvoc add-project input4mip
|
|
501
|
+
|
|
502
|
+
# Add a custom project
|
|
503
|
+
esgvoc add-project my_project --custom --repo https://github.com/me/repo
|
|
504
|
+
"""
|
|
505
|
+
config_manager = service.get_config_manager()
|
|
506
|
+
if config_name is None:
|
|
507
|
+
config_name = config_manager.get_active_config_name()
|
|
508
|
+
console.print(f"Modifying active configuration: [cyan]{config_name}[/cyan]")
|
|
509
|
+
|
|
510
|
+
configs = config_manager.list_configs()
|
|
511
|
+
if config_name not in configs:
|
|
512
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
513
|
+
raise typer.Exit(1)
|
|
514
|
+
|
|
515
|
+
try:
|
|
516
|
+
# 🔹 FORCE FRESH LOAD: Load configuration directly from file to bypass any caching
|
|
517
|
+
configs = config_manager.list_configs()
|
|
518
|
+
config_path = configs[config_name]
|
|
519
|
+
|
|
520
|
+
# Load fresh configuration from file
|
|
521
|
+
try:
|
|
522
|
+
config = ServiceSettings.load_from_file(config_path)
|
|
523
|
+
console.print(f"[blue]Debug: Loaded fresh config from file[/blue]")
|
|
524
|
+
except Exception as e:
|
|
525
|
+
console.print(f"[yellow]Debug: Failed to load from file ({e}), using config manager[/yellow]")
|
|
526
|
+
config = config_manager.get_config(config_name)
|
|
527
|
+
|
|
528
|
+
# 🔹 DEBUG: Show current projects before adding
|
|
529
|
+
current_projects = []
|
|
530
|
+
if hasattr(config, "projects") and config.projects:
|
|
531
|
+
current_projects = [name for name in config.projects.keys()]
|
|
532
|
+
console.print(f"[blue]Debug: Current projects: {current_projects}[/blue]")
|
|
533
|
+
|
|
534
|
+
if from_default:
|
|
535
|
+
# Add from default configuration
|
|
536
|
+
if config.add_project_from_default(project_name):
|
|
537
|
+
console.print(
|
|
538
|
+
f"[green]Successfully added default project [cyan]{project_name}[/cyan] to configuration [cyan]{config_name}[/cyan][/green]"
|
|
539
|
+
)
|
|
540
|
+
else:
|
|
541
|
+
if config.has_project(project_name):
|
|
542
|
+
console.print(
|
|
543
|
+
f"[red]Error: Project '{project_name}' already exists in configuration '{config_name}'.[/red]"
|
|
544
|
+
)
|
|
545
|
+
else:
|
|
546
|
+
available = config.get_available_default_projects()
|
|
547
|
+
console.print(f"[red]Error: '{project_name}' is not a valid default project.[/red]")
|
|
548
|
+
console.print(f"[yellow]Available default projects: {', '.join(available)}[/yellow]")
|
|
549
|
+
raise typer.Exit(1)
|
|
550
|
+
else:
|
|
551
|
+
# Add custom project
|
|
552
|
+
if not github_repo:
|
|
553
|
+
console.print("[red]Error: --repo is required when adding custom projects.[/red]")
|
|
554
|
+
raise typer.Exit(1)
|
|
555
|
+
|
|
556
|
+
# Set default paths if not provided
|
|
557
|
+
if local_path is None:
|
|
558
|
+
local_path = f"repos/{project_name}"
|
|
559
|
+
if db_path is None:
|
|
560
|
+
db_path = f"dbs/{project_name}.sqlite"
|
|
561
|
+
|
|
562
|
+
custom_config = {
|
|
563
|
+
"project_name": project_name,
|
|
564
|
+
"github_repo": github_repo,
|
|
565
|
+
"branch": branch,
|
|
566
|
+
"local_path": local_path,
|
|
567
|
+
"db_path": db_path,
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if config.add_project_custom(custom_config):
|
|
571
|
+
console.print(
|
|
572
|
+
f"[green]Successfully added custom project [cyan]{project_name}[/cyan] to configuration [cyan]{config_name}[/cyan][/green]"
|
|
573
|
+
)
|
|
574
|
+
else:
|
|
575
|
+
console.print(
|
|
576
|
+
f"[red]Error: Project '{project_name}' already exists in configuration '{config_name}'.[/red]"
|
|
577
|
+
)
|
|
578
|
+
raise typer.Exit(1)
|
|
424
579
|
|
|
425
580
|
# Save the configuration
|
|
426
581
|
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
582
|
|
|
431
583
|
# Reset the state if we modified the active configuration
|
|
432
|
-
if
|
|
584
|
+
if config_name == config_manager.get_active_config_name():
|
|
433
585
|
service.current_state = service.get_state()
|
|
434
586
|
|
|
587
|
+
except ValueError as e:
|
|
588
|
+
console.print(f"[red]Error: {str(e)}[/red]")
|
|
589
|
+
raise typer.Exit(1)
|
|
435
590
|
except Exception as e:
|
|
436
591
|
console.print(f"[red]Error adding project: {str(e)}[/red]")
|
|
437
592
|
raise typer.Exit(1)
|
|
@@ -439,62 +594,492 @@ def add_project(
|
|
|
439
594
|
|
|
440
595
|
@app.command()
|
|
441
596
|
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
597
|
project_name: str = typer.Argument(..., help="Name of the project to remove."),
|
|
598
|
+
config_name: Optional[str] = typer.Option(
|
|
599
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
600
|
+
),
|
|
601
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt."),
|
|
446
602
|
):
|
|
447
603
|
"""
|
|
448
604
|
Remove a project from a configuration.
|
|
605
|
+
"""
|
|
606
|
+
config_manager = service.get_config_manager()
|
|
607
|
+
if config_name is None:
|
|
608
|
+
config_name = config_manager.get_active_config_name()
|
|
609
|
+
console.print(f"Modifying active configuration: [cyan]{config_name}[/cyan]")
|
|
449
610
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
611
|
+
configs = config_manager.list_configs()
|
|
612
|
+
if config_name not in configs:
|
|
613
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
614
|
+
raise typer.Exit(1)
|
|
615
|
+
|
|
616
|
+
try:
|
|
617
|
+
# 🔹 FORCE FRESH LOAD for removal too
|
|
618
|
+
configs = config_manager.list_configs()
|
|
619
|
+
config_path = configs[config_name]
|
|
620
|
+
|
|
621
|
+
try:
|
|
622
|
+
config = ServiceSettings.load_from_file(config_path)
|
|
623
|
+
console.print(f"[blue]Debug: Loaded fresh config from file for removal[/blue]")
|
|
624
|
+
except Exception as e:
|
|
625
|
+
console.print(f"[yellow]Debug: Failed to load from file ({e}), using config manager[/yellow]")
|
|
626
|
+
config = config_manager.get_config(config_name)
|
|
627
|
+
|
|
628
|
+
if not config.has_project(project_name):
|
|
629
|
+
console.print(f"[red]Error: Project '{project_name}' not found in configuration '{config_name}'.[/red]")
|
|
630
|
+
raise typer.Exit(1)
|
|
631
|
+
|
|
632
|
+
# Confirm removal unless forced
|
|
633
|
+
if not force:
|
|
634
|
+
confirm = typer.confirm(
|
|
635
|
+
f"Are you sure you want to remove project '{project_name}' from configuration '{config_name}'?"
|
|
636
|
+
)
|
|
637
|
+
if not confirm:
|
|
638
|
+
console.print("Operation cancelled.")
|
|
639
|
+
return
|
|
640
|
+
|
|
641
|
+
# Remove project using the new method
|
|
642
|
+
if config.remove_project(project_name):
|
|
643
|
+
console.print(
|
|
644
|
+
f"[green]Successfully removed project [cyan]{project_name}[/cyan] from configuration [cyan]{config_name}[/cyan][/green]"
|
|
645
|
+
)
|
|
646
|
+
else:
|
|
647
|
+
console.print(f"[red]Error: Failed to remove project '{project_name}'.[/red]")
|
|
648
|
+
raise typer.Exit(1)
|
|
649
|
+
|
|
650
|
+
# Save the configuration
|
|
651
|
+
config_manager.save_active_config(config)
|
|
652
|
+
|
|
653
|
+
# 🔹 DEBUG: Verify the project was actually removed
|
|
654
|
+
remaining_projects = []
|
|
655
|
+
if hasattr(config, "projects") and config.projects:
|
|
656
|
+
remaining_projects = [name for name in config.projects.keys()]
|
|
657
|
+
console.print(f"[blue]Debug: Projects after removal: {remaining_projects}[/blue]")
|
|
658
|
+
|
|
659
|
+
# Reset the state if we modified the active configuration
|
|
660
|
+
if config_name == config_manager.get_active_config_name():
|
|
661
|
+
service.current_state = service.get_state()
|
|
662
|
+
|
|
663
|
+
except Exception as e:
|
|
664
|
+
console.print(f"[red]Error removing project: {str(e)}[/red]")
|
|
665
|
+
raise typer.Exit(1)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
@app.command()
|
|
669
|
+
def update_project(
|
|
670
|
+
project_name: str = typer.Argument(..., help="Name of the project to update."),
|
|
671
|
+
config_name: Optional[str] = typer.Option(
|
|
672
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
673
|
+
),
|
|
674
|
+
github_repo: Optional[str] = typer.Option(None, "--repo", "-r", help="New GitHub repository URL."),
|
|
675
|
+
branch: Optional[str] = typer.Option(None, "--branch", "-b", help="New branch."),
|
|
676
|
+
local_path: Optional[str] = typer.Option(None, "--local", "-l", help="New local path."),
|
|
677
|
+
db_path: Optional[str] = typer.Option(None, "--db", "-d", help="New database path."),
|
|
678
|
+
):
|
|
679
|
+
"""
|
|
680
|
+
Update settings for an existing project.
|
|
453
681
|
"""
|
|
454
682
|
config_manager = service.get_config_manager()
|
|
455
|
-
if
|
|
456
|
-
|
|
457
|
-
console.print(f"Modifying active configuration: [cyan]{
|
|
683
|
+
if config_name is None:
|
|
684
|
+
config_name = config_manager.get_active_config_name()
|
|
685
|
+
console.print(f"Modifying active configuration: [cyan]{config_name}[/cyan]")
|
|
458
686
|
|
|
459
687
|
configs = config_manager.list_configs()
|
|
460
|
-
if
|
|
461
|
-
console.print(f"[red]Error: Configuration '{
|
|
688
|
+
if config_name not in configs:
|
|
689
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
462
690
|
raise typer.Exit(1)
|
|
463
691
|
|
|
464
692
|
try:
|
|
465
|
-
|
|
466
|
-
config = config_manager.get_config(name)
|
|
693
|
+
config = config_manager.get_config(config_name)
|
|
467
694
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
console.print(f"[red]Error: Project '{project_name}' not found in configuration '{name}'.[/red]")
|
|
695
|
+
if not config.has_project(project_name):
|
|
696
|
+
console.print(f"[red]Error: Project '{project_name}' not found in configuration '{config_name}'.[/red]")
|
|
471
697
|
raise typer.Exit(1)
|
|
472
698
|
|
|
473
|
-
#
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if not
|
|
478
|
-
|
|
699
|
+
# Build update dict with non-None values
|
|
700
|
+
updates = {}
|
|
701
|
+
if github_repo is not None:
|
|
702
|
+
updates["github_repo"] = github_repo
|
|
703
|
+
if branch is not None:
|
|
704
|
+
updates["branch"] = branch
|
|
705
|
+
if local_path is not None:
|
|
706
|
+
updates["local_path"] = local_path
|
|
707
|
+
if db_path is not None:
|
|
708
|
+
updates["db_path"] = db_path
|
|
709
|
+
|
|
710
|
+
if not updates:
|
|
711
|
+
console.print(
|
|
712
|
+
"[yellow]No updates specified. Use --repo, --branch, --local, or --db to specify changes.[/yellow]"
|
|
713
|
+
)
|
|
479
714
|
return
|
|
480
715
|
|
|
481
|
-
#
|
|
482
|
-
|
|
716
|
+
# Update project using the new method
|
|
717
|
+
if config.update_project(project_name, **updates):
|
|
718
|
+
console.print(
|
|
719
|
+
f"[green]Successfully updated project [cyan]{project_name}[/cyan] in configuration [cyan]{config_name}[/cyan][/green]"
|
|
720
|
+
)
|
|
721
|
+
for key, value in updates.items():
|
|
722
|
+
console.print(f" [green]{key} = {value}[/green]")
|
|
723
|
+
else:
|
|
724
|
+
console.print(f"[red]Error: Failed to update project '{project_name}'.[/red]")
|
|
725
|
+
raise typer.Exit(1)
|
|
483
726
|
|
|
484
727
|
# Save the configuration
|
|
485
728
|
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
729
|
|
|
490
730
|
# Reset the state if we modified the active configuration
|
|
491
|
-
if
|
|
731
|
+
if config_name == config_manager.get_active_config_name():
|
|
492
732
|
service.current_state = service.get_state()
|
|
493
733
|
|
|
734
|
+
except Exception as e:
|
|
735
|
+
console.print(f"[red]Error updating project: {str(e)}[/red]")
|
|
736
|
+
raise typer.Exit(1)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
# 🔹 NEW: Simple config management commands
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
@app.command()
|
|
743
|
+
def add(
|
|
744
|
+
project_names: List[str] = typer.Argument(..., help="Names of the projects to add from defaults."),
|
|
745
|
+
config_name: Optional[str] = typer.Option(
|
|
746
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
747
|
+
),
|
|
748
|
+
):
|
|
749
|
+
"""
|
|
750
|
+
Add one or more default projects to the current configuration and install their CVs.
|
|
751
|
+
|
|
752
|
+
This will:
|
|
753
|
+
1. Add the projects to the configuration using default settings
|
|
754
|
+
2. Download the projects' CVs by running synchronize_all
|
|
755
|
+
|
|
756
|
+
Examples:
|
|
757
|
+
esgvoc config add input4mip
|
|
758
|
+
esgvoc config add input4mip obs4mip cordex-cmip6
|
|
759
|
+
esgvoc config add obs4mip --config my_config
|
|
760
|
+
"""
|
|
761
|
+
config_manager = service.get_config_manager()
|
|
762
|
+
if config_name is None:
|
|
763
|
+
config_name = config_manager.get_active_config_name()
|
|
764
|
+
console.print(f"Adding to active configuration: [cyan]{config_name}[/cyan]")
|
|
765
|
+
|
|
766
|
+
configs = config_manager.list_configs()
|
|
767
|
+
if config_name not in configs:
|
|
768
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
769
|
+
raise typer.Exit(1)
|
|
770
|
+
|
|
771
|
+
try:
|
|
772
|
+
# Load fresh configuration from file
|
|
773
|
+
configs = config_manager.list_configs()
|
|
774
|
+
config_path = configs[config_name]
|
|
775
|
+
config = ServiceSettings.load_from_file(config_path)
|
|
776
|
+
|
|
777
|
+
added_projects = []
|
|
778
|
+
skipped_projects = []
|
|
779
|
+
invalid_projects = []
|
|
780
|
+
|
|
781
|
+
# Process each project
|
|
782
|
+
for project_name in project_names:
|
|
783
|
+
# Check if project already exists
|
|
784
|
+
if config.has_project(project_name):
|
|
785
|
+
skipped_projects.append(project_name)
|
|
786
|
+
console.print(f"[yellow]⚠ Project '{project_name}' already exists - skipping[/yellow]")
|
|
787
|
+
continue
|
|
788
|
+
|
|
789
|
+
# Add the project from defaults
|
|
790
|
+
try:
|
|
791
|
+
if config.add_project_from_default(project_name):
|
|
792
|
+
added_projects.append(project_name)
|
|
793
|
+
console.print(f"[green]✓ Added project [cyan]{project_name}[/cyan][/green]")
|
|
794
|
+
else:
|
|
795
|
+
invalid_projects.append(project_name)
|
|
796
|
+
console.print(f"[red]✗ Invalid project '{project_name}'[/red]")
|
|
797
|
+
except ValueError as e:
|
|
798
|
+
invalid_projects.append(project_name)
|
|
799
|
+
console.print(f"[red]✗ Invalid project '{project_name}'[/red]")
|
|
800
|
+
|
|
801
|
+
# Show summary of what was processed
|
|
802
|
+
if added_projects:
|
|
803
|
+
console.print(
|
|
804
|
+
f"[green]Successfully added {len(added_projects)} project(s): {', '.join(added_projects)}[/green]"
|
|
805
|
+
)
|
|
806
|
+
if skipped_projects:
|
|
807
|
+
console.print(
|
|
808
|
+
f"[yellow]Skipped {len(skipped_projects)} existing project(s): {', '.join(skipped_projects)}[/yellow]"
|
|
809
|
+
)
|
|
810
|
+
if invalid_projects:
|
|
811
|
+
available = config.get_available_default_projects()
|
|
812
|
+
console.print(f"[red]Invalid project(s): {', '.join(invalid_projects)}[/red]")
|
|
813
|
+
console.print(f"[yellow]Available projects: {', '.join(available)}[/yellow]")
|
|
814
|
+
|
|
815
|
+
# Only proceed if we actually added something
|
|
816
|
+
if added_projects:
|
|
817
|
+
# Save the configuration to the correct file
|
|
818
|
+
if config_name == config_manager.get_active_config_name():
|
|
819
|
+
config_manager.save_active_config(config)
|
|
820
|
+
# Reset the state if we modified the active configuration
|
|
821
|
+
service.current_state = service.get_state()
|
|
822
|
+
else:
|
|
823
|
+
# Save to specific config file
|
|
824
|
+
config_path = configs[config_name]
|
|
825
|
+
config.save_to_file(config_path)
|
|
826
|
+
|
|
827
|
+
# Download the CVs for all added projects
|
|
828
|
+
console.print(f"[blue]Downloading CVs for {len(added_projects)} project(s)...[/blue]")
|
|
829
|
+
service.current_state.synchronize_all()
|
|
830
|
+
console.print(f"[green]✓ Successfully installed CVs for all added projects[/green]")
|
|
831
|
+
elif invalid_projects and not skipped_projects:
|
|
832
|
+
# Exit with error only if we had invalid projects and nothing was skipped
|
|
833
|
+
raise typer.Exit(1)
|
|
834
|
+
|
|
835
|
+
except ValueError as e:
|
|
836
|
+
console.print(f"[red]Error: {str(e)}[/red]")
|
|
837
|
+
raise typer.Exit(1)
|
|
838
|
+
except Exception as e:
|
|
839
|
+
console.print(f"[red]Error adding project: {str(e)}[/red]")
|
|
840
|
+
raise typer.Exit(1)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
@app.command()
|
|
844
|
+
def rm(
|
|
845
|
+
project_names: List[str] = typer.Argument(..., help="Names of the projects to remove."),
|
|
846
|
+
config_name: Optional[str] = typer.Option(
|
|
847
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
848
|
+
),
|
|
849
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt."),
|
|
850
|
+
keep_files: bool = typer.Option(
|
|
851
|
+
False, "--keep-files", help="Keep local repos and databases (only remove from config)."
|
|
852
|
+
),
|
|
853
|
+
):
|
|
854
|
+
"""
|
|
855
|
+
Remove one or more projects from the configuration and delete their repos/databases.
|
|
856
|
+
|
|
857
|
+
This will:
|
|
858
|
+
1. Remove the projects from the configuration
|
|
859
|
+
2. Delete the local repository directories (unless --keep-files)
|
|
860
|
+
3. Delete the database files (unless --keep-files)
|
|
861
|
+
|
|
862
|
+
Examples:
|
|
863
|
+
esgvoc config rm input4mip
|
|
864
|
+
esgvoc config rm input4mip obs4mip cordex-cmip6
|
|
865
|
+
esgvoc config rm obs4mip --force
|
|
866
|
+
esgvoc config rm cmip6 input4mip --keep-files # Remove from config but keep files
|
|
867
|
+
"""
|
|
868
|
+
config_manager = service.get_config_manager()
|
|
869
|
+
if config_name is None:
|
|
870
|
+
config_name = config_manager.get_active_config_name()
|
|
871
|
+
console.print(f"Removing from active configuration: [cyan]{config_name}[/cyan]")
|
|
872
|
+
|
|
873
|
+
configs = config_manager.list_configs()
|
|
874
|
+
if config_name not in configs:
|
|
875
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
876
|
+
raise typer.Exit(1)
|
|
877
|
+
|
|
878
|
+
try:
|
|
879
|
+
# Load fresh configuration from file
|
|
880
|
+
configs = config_manager.list_configs()
|
|
881
|
+
config_path = configs[config_name]
|
|
882
|
+
config = ServiceSettings.load_from_file(config_path)
|
|
883
|
+
|
|
884
|
+
# Check which projects exist and collect their details
|
|
885
|
+
valid_projects = []
|
|
886
|
+
invalid_projects = []
|
|
887
|
+
projects_to_remove = {} # project_name -> project_object
|
|
888
|
+
|
|
889
|
+
for project_name in project_names:
|
|
890
|
+
if config.has_project(project_name):
|
|
891
|
+
project = config.get_project(project_name)
|
|
892
|
+
projects_to_remove[project_name] = project
|
|
893
|
+
valid_projects.append(project_name)
|
|
894
|
+
else:
|
|
895
|
+
invalid_projects.append(project_name)
|
|
896
|
+
console.print(f"[red]✗ Project '{project_name}' not found in configuration[/red]")
|
|
897
|
+
|
|
898
|
+
if invalid_projects:
|
|
899
|
+
console.print(f"[red]Invalid project(s): {', '.join(invalid_projects)}[/red]")
|
|
900
|
+
|
|
901
|
+
if not valid_projects:
|
|
902
|
+
console.print("[red]No valid projects to remove.[/red]")
|
|
903
|
+
raise typer.Exit(1)
|
|
904
|
+
|
|
905
|
+
# Show what will be removed and confirm unless forced
|
|
906
|
+
console.print(f"[yellow]Projects to remove: {', '.join(valid_projects)}[/yellow]")
|
|
907
|
+
if not force:
|
|
908
|
+
action_desc = "remove from config only" if keep_files else "remove from config and delete all files"
|
|
909
|
+
project_word = "project" if len(valid_projects) == 1 else "projects"
|
|
910
|
+
confirm = typer.confirm(f"Are you sure you want to {action_desc} for {len(valid_projects)} {project_word}?")
|
|
911
|
+
if not confirm:
|
|
912
|
+
console.print("Operation cancelled.")
|
|
913
|
+
return
|
|
914
|
+
|
|
915
|
+
# Get base directory for file cleanup
|
|
916
|
+
base_dir = config_manager.data_config_dir or str(config_manager.data_dir)
|
|
917
|
+
|
|
918
|
+
removed_projects = []
|
|
919
|
+
# Remove each project
|
|
920
|
+
for project_name in valid_projects:
|
|
921
|
+
project = projects_to_remove[project_name]
|
|
922
|
+
|
|
923
|
+
if config.remove_project(project_name):
|
|
924
|
+
removed_projects.append(project_name)
|
|
925
|
+
console.print(f"[green]✓ Removed [cyan]{project_name}[/cyan] from configuration[/green]")
|
|
926
|
+
|
|
927
|
+
# Clean up filesystem unless --keep-files
|
|
928
|
+
if not keep_files and project:
|
|
929
|
+
# Clean up local repository
|
|
930
|
+
if project.local_path:
|
|
931
|
+
repo_path = Path(base_dir) / project.local_path
|
|
932
|
+
if repo_path.exists():
|
|
933
|
+
shutil.rmtree(repo_path)
|
|
934
|
+
console.print(f"[green] ✓ Deleted repository: {repo_path}[/green]")
|
|
935
|
+
else:
|
|
936
|
+
console.print(f"[yellow] Repository not found: {repo_path}[/yellow]")
|
|
937
|
+
|
|
938
|
+
# Clean up database
|
|
939
|
+
if project.db_path:
|
|
940
|
+
db_path = Path(base_dir) / project.db_path
|
|
941
|
+
if db_path.exists():
|
|
942
|
+
db_path.unlink()
|
|
943
|
+
console.print(f"[green] ✓ Deleted database: {db_path}[/green]")
|
|
944
|
+
else:
|
|
945
|
+
console.print(f"[yellow] Database not found: {db_path}[/yellow]")
|
|
946
|
+
else:
|
|
947
|
+
console.print(f"[red]✗ Failed to remove '{project_name}'[/red]")
|
|
948
|
+
|
|
949
|
+
if removed_projects:
|
|
950
|
+
console.print(
|
|
951
|
+
f"[green]Successfully removed {len(removed_projects)} project(s): {', '.join(removed_projects)}[/green]"
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
# Save the configuration to the correct file
|
|
955
|
+
if config_name == config_manager.get_active_config_name():
|
|
956
|
+
config_manager.save_active_config(config)
|
|
957
|
+
# Reset the state if we modified the active configuration
|
|
958
|
+
service.current_state = service.get_state()
|
|
959
|
+
else:
|
|
960
|
+
# Save to specific config file
|
|
961
|
+
config_path = configs[config_name]
|
|
962
|
+
config.save_to_file(config_path)
|
|
963
|
+
else:
|
|
964
|
+
console.print("[red]No projects were successfully removed.[/red]")
|
|
965
|
+
raise typer.Exit(1)
|
|
966
|
+
|
|
494
967
|
except Exception as e:
|
|
495
968
|
console.print(f"[red]Error removing project: {str(e)}[/red]")
|
|
496
969
|
raise typer.Exit(1)
|
|
497
970
|
|
|
498
971
|
|
|
972
|
+
@app.command()
|
|
973
|
+
def init(
|
|
974
|
+
name: str = typer.Argument(..., help="Name for the new empty configuration."),
|
|
975
|
+
no_switch: bool = typer.Option(
|
|
976
|
+
False, "--no-switch", help="Don't switch to the new configuration (stays on current)."
|
|
977
|
+
),
|
|
978
|
+
):
|
|
979
|
+
"""
|
|
980
|
+
Create a new empty configuration with only universe settings (no projects).
|
|
981
|
+
|
|
982
|
+
This creates a minimal configuration with just the universe component,
|
|
983
|
+
allowing you to add projects selectively using 'esgvoc config add'.
|
|
984
|
+
By default, switches to the new configuration after creation.
|
|
985
|
+
|
|
986
|
+
Examples:
|
|
987
|
+
esgvoc config init minimal
|
|
988
|
+
esgvoc config init test --no-switch # Create but don't switch
|
|
989
|
+
"""
|
|
990
|
+
config_manager = service.get_config_manager()
|
|
991
|
+
configs = config_manager.list_configs()
|
|
992
|
+
|
|
993
|
+
if name in configs:
|
|
994
|
+
console.print(f"[red]Error: Configuration '{name}' already exists.[/red]")
|
|
995
|
+
raise typer.Exit(1)
|
|
996
|
+
|
|
997
|
+
try:
|
|
998
|
+
# Create empty configuration with only universe settings
|
|
999
|
+
empty_config_data = {
|
|
1000
|
+
"universe": ServiceSettings.DEFAULT_SETTINGS["universe"],
|
|
1001
|
+
"projects": [], # No projects - completely empty
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
# Add the new configuration
|
|
1005
|
+
config_manager.add_config(name, empty_config_data)
|
|
1006
|
+
console.print(f"[green]✓ Created empty configuration: [cyan]{name}[/cyan][/green]")
|
|
1007
|
+
|
|
1008
|
+
# Switch to new config by default (unless --no-switch is used)
|
|
1009
|
+
if not no_switch:
|
|
1010
|
+
config_manager.switch_config(name)
|
|
1011
|
+
console.print(f"[green]✓ Switched to configuration: [cyan]{name}[/cyan][/green]")
|
|
1012
|
+
# Reset the state to use the new configuration
|
|
1013
|
+
service.current_state = service.get_state()
|
|
1014
|
+
|
|
1015
|
+
except Exception as e:
|
|
1016
|
+
console.print(f"[red]Error creating configuration: {str(e)}[/red]")
|
|
1017
|
+
raise typer.Exit(1)
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
@app.command()
|
|
1021
|
+
def avail(
|
|
1022
|
+
config_name: Optional[str] = typer.Option(
|
|
1023
|
+
None, "--config", "-c", help="Configuration name. Uses active configuration if not specified."
|
|
1024
|
+
),
|
|
1025
|
+
):
|
|
1026
|
+
"""
|
|
1027
|
+
Show a table of all available default projects and their status in the configuration.
|
|
1028
|
+
|
|
1029
|
+
Projects are marked as:
|
|
1030
|
+
- ✓ Active: Project is in the current configuration
|
|
1031
|
+
- ○ Available: Project can be added to the configuration
|
|
1032
|
+
|
|
1033
|
+
Examples:
|
|
1034
|
+
esgvoc config avail
|
|
1035
|
+
esgvoc config avail --config my_config
|
|
1036
|
+
"""
|
|
1037
|
+
config_manager = service.get_config_manager()
|
|
1038
|
+
if config_name is None:
|
|
1039
|
+
config_name = config_manager.get_active_config_name()
|
|
1040
|
+
console.print(f"Showing project availability for: [cyan]{config_name}[/cyan]")
|
|
1041
|
+
|
|
1042
|
+
configs = config_manager.list_configs()
|
|
1043
|
+
if config_name not in configs:
|
|
1044
|
+
console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
|
|
1045
|
+
raise typer.Exit(1)
|
|
1046
|
+
|
|
1047
|
+
try:
|
|
1048
|
+
# Load configuration
|
|
1049
|
+
config_path = configs[config_name]
|
|
1050
|
+
config = ServiceSettings.load_from_file(config_path)
|
|
1051
|
+
|
|
1052
|
+
# Get all available default projects
|
|
1053
|
+
available_projects = ServiceSettings.DEFAULT_PROJECT_CONFIGS
|
|
1054
|
+
|
|
1055
|
+
table = Table(title=f"Available Projects (Configuration: {config_name})")
|
|
1056
|
+
table.add_column("Status", style="bold")
|
|
1057
|
+
table.add_column("Project Name", style="cyan")
|
|
1058
|
+
table.add_column("Repository", style="green")
|
|
1059
|
+
table.add_column("Branch", style="yellow")
|
|
1060
|
+
|
|
1061
|
+
for project_name, project_config in available_projects.items():
|
|
1062
|
+
# Check if project is in current configuration
|
|
1063
|
+
if config.has_project(project_name):
|
|
1064
|
+
status = "[green]✓ Active[/green]"
|
|
1065
|
+
else:
|
|
1066
|
+
status = "[dim]○ Available[/dim]"
|
|
1067
|
+
|
|
1068
|
+
table.add_row(status, project_name, project_config["github_repo"], project_config["branch"])
|
|
1069
|
+
|
|
1070
|
+
display(table)
|
|
1071
|
+
|
|
1072
|
+
# Show summary
|
|
1073
|
+
active_count = len([p for p in available_projects.keys() if config.has_project(p)])
|
|
1074
|
+
total_count = len(available_projects)
|
|
1075
|
+
console.print(
|
|
1076
|
+
f"\n[blue]Summary: {active_count}/{total_count} projects active in configuration '{config_name}'[/blue]"
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
except Exception as e:
|
|
1080
|
+
console.print(f"[red]Error showing available projects: {str(e)}[/red]")
|
|
1081
|
+
raise typer.Exit(1)
|
|
1082
|
+
|
|
1083
|
+
|
|
499
1084
|
if __name__ == "__main__":
|
|
500
1085
|
app()
|