esgvoc 1.1.1__py3-none-any.whl → 1.1.3__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/cli/config.py CHANGED
@@ -9,9 +9,15 @@ from rich.console import Console
9
9
  from rich.syntax import Syntax
10
10
  from rich.table import Table
11
11
 
12
- import esgvoc.core.service as service
12
+ # Import service module but don't initialize it immediately
13
+ import esgvoc.core.service.configuration.config_manager as config_manager_module
13
14
  from esgvoc.core.service.configuration.setting import ServiceSettings
14
15
 
16
+ def get_service():
17
+ """Get the service module, importing it only when needed."""
18
+ import esgvoc.core.service as service
19
+ return service
20
+
15
21
  app = typer.Typer()
16
22
  console = Console()
17
23
 
@@ -78,6 +84,7 @@ def list():
78
84
 
79
85
  Displays all available configurations along with the active one.
80
86
  """
87
+ service = get_service()
81
88
  config_manager = service.get_config_manager()
82
89
  configs = config_manager.list_configs()
83
90
  active_config = config_manager.get_active_config_name()
@@ -106,6 +113,7 @@ def show(
106
113
  Args:
107
114
  name: Name of the configuration to show. Shows the active configuration if not specified.
108
115
  """
116
+ service = get_service()
109
117
  config_manager = service.get_config_manager()
110
118
  if name is None:
111
119
  name = config_manager.get_active_config_name()
@@ -136,6 +144,7 @@ def switch(name: str = typer.Argument(..., help="Name of the configuration to sw
136
144
  Args:
137
145
  name: Name of the configuration to switch to.
138
146
  """
147
+ service = get_service()
139
148
  config_manager = service.get_config_manager()
140
149
  configs = config_manager.list_configs()
141
150
 
@@ -170,6 +179,7 @@ def create(
170
179
  base: Base the new configuration on an existing one. Uses the default if not specified.
171
180
  switch_to: Switch to the new configuration after creating it.
172
181
  """
182
+ service = get_service()
173
183
  config_manager = service.get_config_manager()
174
184
  configs = config_manager.list_configs()
175
185
 
@@ -188,7 +198,7 @@ def create(
188
198
  config_data = base_config.dump()
189
199
  else:
190
200
  # Use default settings
191
- config_data = ServiceSettings.DEFAULT_SETTINGS
201
+ config_data = ServiceSettings._get_default_settings()
192
202
 
193
203
  # Add the new configuration
194
204
  config_manager.add_config(name, config_data)
@@ -213,6 +223,7 @@ def remove(name: str = typer.Argument(..., help="Name of the configuration to re
213
223
  Args:
214
224
  name: Name of the configuration to remove.
215
225
  """
226
+ service = get_service()
216
227
  config_manager = service.get_config_manager()
217
228
  configs = config_manager.list_configs()
218
229
 
@@ -259,6 +270,7 @@ def edit(
259
270
  name: Name of the configuration to edit. Edits the active configuration if not specified.
260
271
  editor: Editor to use. Uses the system default if not specified.
261
272
  """
273
+ service = get_service()
262
274
  config_manager = service.get_config_manager()
263
275
  if name is None:
264
276
  name = config_manager.get_active_config_name()
@@ -314,17 +326,24 @@ def set(
314
326
 
315
327
  Examples:
316
328
  # Change the universe branch in the active configuration
317
- esgvoc set 'universe:branch=esgvoc_dev'
329
+ esgvoc config set 'universe:branch=esgvoc_dev'
330
+
331
+ # Enable offline mode for universe
332
+ esgvoc config set 'universe:offline_mode=true'
333
+
334
+ # Enable offline mode for a specific project
335
+ esgvoc config set 'cmip6:offline_mode=true'
318
336
 
319
337
  # Change multiple components at once
320
- esgvoc set 'universe:branch=esgvoc_dev' 'cmip6:branch=esgvoc_dev'
338
+ esgvoc config set 'universe:branch=esgvoc_dev' 'cmip6:branch=esgvoc_dev'
321
339
 
322
340
  # Change settings in a specific configuration
323
- esgvoc set 'universe:local_path=repos/prod/universe' --config prod
341
+ esgvoc config set 'universe:local_path=repos/prod/universe' --config prod
324
342
 
325
343
  # Change the GitHub repository URL for a project
326
- esgvoc set 'cmip6:github_repo=https://github.com/WCRP-CMIP/CMIP6_CVs_new'
344
+ esgvoc config set 'cmip6:github_repo=https://github.com/WCRP-CMIP/CMIP6_CVs_new'
327
345
  """
346
+ service = get_service()
328
347
  config_manager = service.get_config_manager()
329
348
  if config_name is None:
330
349
  config_name = config_manager.get_active_config_name()
@@ -361,6 +380,9 @@ def set(
361
380
  elif setting_key == "db_path":
362
381
  config.universe.db_path = setting_value
363
382
  modified = True
383
+ elif setting_key == "offline_mode":
384
+ config.universe.offline_mode = setting_value.lower() in ("true", "1", "yes", "on")
385
+ modified = True
364
386
  else:
365
387
  console.print(f"[yellow]Warning: Unknown universe setting '{setting_key}'. Skipping.[/yellow]")
366
388
  continue
@@ -410,15 +432,14 @@ def list_available_projects():
410
432
  """
411
433
  List all available default projects that can be added.
412
434
  """
413
- available_projects = ServiceSettings.DEFAULT_PROJECT_CONFIGS.keys()
435
+ available_projects = ServiceSettings._get_default_project_configs()
414
436
 
415
437
  table = Table(title="Available Default Projects")
416
438
  table.add_column("Project Name", style="cyan")
417
439
  table.add_column("Repository", style="green")
418
440
  table.add_column("Branch", style="yellow")
419
441
 
420
- for project_name in available_projects:
421
- config = ServiceSettings.DEFAULT_PROJECT_CONFIGS[project_name]
442
+ for project_name, config in available_projects.items():
422
443
  table.add_row(project_name, config["github_repo"], config["branch"])
423
444
 
424
445
  display(table)
@@ -433,6 +454,7 @@ def list_projects(
433
454
  """
434
455
  List all projects in a configuration.
435
456
  """
457
+ service = get_service()
436
458
  config_manager = service.get_config_manager()
437
459
  if config_name is None:
438
460
  config_name = config_manager.get_active_config_name()
@@ -502,6 +524,7 @@ def add_project(
502
524
  # Add a custom project
503
525
  esgvoc add-project my_project --custom --repo https://github.com/me/repo
504
526
  """
527
+ service = get_service()
505
528
  config_manager = service.get_config_manager()
506
529
  if config_name is None:
507
530
  config_name = config_manager.get_active_config_name()
@@ -603,6 +626,7 @@ def remove_project(
603
626
  """
604
627
  Remove a project from a configuration.
605
628
  """
629
+ service = get_service()
606
630
  config_manager = service.get_config_manager()
607
631
  if config_name is None:
608
632
  config_name = config_manager.get_active_config_name()
@@ -679,6 +703,7 @@ def update_project(
679
703
  """
680
704
  Update settings for an existing project.
681
705
  """
706
+ service = get_service()
682
707
  config_manager = service.get_config_manager()
683
708
  if config_name is None:
684
709
  config_name = config_manager.get_active_config_name()
@@ -758,6 +783,7 @@ def add(
758
783
  esgvoc config add input4mip obs4mip cordex-cmip6
759
784
  esgvoc config add obs4mip --config my_config
760
785
  """
786
+ service = get_service()
761
787
  config_manager = service.get_config_manager()
762
788
  if config_name is None:
763
789
  config_name = config_manager.get_active_config_name()
@@ -865,6 +891,7 @@ def rm(
865
891
  esgvoc config rm obs4mip --force
866
892
  esgvoc config rm cmip6 input4mip --keep-files # Remove from config but keep files
867
893
  """
894
+ service = get_service()
868
895
  config_manager = service.get_config_manager()
869
896
  if config_name is None:
870
897
  config_name = config_manager.get_active_config_name()
@@ -987,6 +1014,7 @@ def init(
987
1014
  esgvoc config init minimal
988
1015
  esgvoc config init test --no-switch # Create but don't switch
989
1016
  """
1017
+ service = get_service()
990
1018
  config_manager = service.get_config_manager()
991
1019
  configs = config_manager.list_configs()
992
1020
 
@@ -996,8 +1024,9 @@ def init(
996
1024
 
997
1025
  try:
998
1026
  # Create empty configuration with only universe settings
1027
+ default_settings = ServiceSettings._get_default_settings()
999
1028
  empty_config_data = {
1000
- "universe": ServiceSettings.DEFAULT_SETTINGS["universe"],
1029
+ "universe": default_settings["universe"],
1001
1030
  "projects": [], # No projects - completely empty
1002
1031
  }
1003
1032
 
@@ -1017,6 +1046,191 @@ def init(
1017
1046
  raise typer.Exit(1)
1018
1047
 
1019
1048
 
1049
+ @app.command()
1050
+ def migrate(
1051
+ config_name: Optional[str] = typer.Option(
1052
+ None, "--config", "-c", help="Configuration name to migrate. Migrates all configs if not specified."
1053
+ ),
1054
+ dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be changed without making changes."),
1055
+ ):
1056
+ """
1057
+ Migrate configuration(s) to convert relative paths to absolute paths.
1058
+
1059
+ This command is needed when upgrading to newer versions that require absolute paths.
1060
+ By default, migrates all configurations. Use --config to migrate only a specific one.
1061
+
1062
+ Examples:
1063
+ esgvoc config migrate # Migrate all configurations
1064
+ esgvoc config migrate --config user_config # Migrate specific configuration
1065
+ esgvoc config migrate --dry-run # Show what would be changed
1066
+ """
1067
+ import os
1068
+ from pathlib import Path
1069
+
1070
+ # Enable migration mode to allow loading configs with relative paths
1071
+ os.environ['ESGVOC_MIGRATION_MODE'] = '1'
1072
+
1073
+ try:
1074
+ # Use config manager directly to avoid service initialization issues
1075
+ from esgvoc.core.service.configuration.config_manager import ConfigManager
1076
+ config_manager = ConfigManager(ServiceSettings, app_name="esgvoc", app_author="ipsl", default_settings=ServiceSettings._get_default_settings())
1077
+ configs = config_manager.list_configs()
1078
+
1079
+ # Determine which configs to migrate
1080
+ if config_name:
1081
+ if config_name not in configs:
1082
+ console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
1083
+ raise typer.Exit(1)
1084
+ configs_to_migrate = {config_name: configs[config_name]}
1085
+ else:
1086
+ configs_to_migrate = configs
1087
+
1088
+ console.print(f"[blue]Migrating {len(configs_to_migrate)} configuration(s)...[/blue]")
1089
+
1090
+ migrated_count = 0
1091
+ for name, config_path in configs_to_migrate.items():
1092
+ console.print(f"\n[cyan]Processing configuration: {name}[/cyan]")
1093
+
1094
+ try:
1095
+ # Load the raw TOML data first to check for relative paths
1096
+ with open(config_path, 'r') as f:
1097
+ raw_data = toml.load(f)
1098
+
1099
+ changes_made = []
1100
+
1101
+ # Check universe paths
1102
+ if 'universe' in raw_data:
1103
+ universe = raw_data['universe']
1104
+ for path_field in ['local_path', 'db_path']:
1105
+ if path_field in universe and universe[path_field]:
1106
+ path_val = universe[path_field]
1107
+ if not Path(path_val).is_absolute():
1108
+ changes_made.append(f"universe.{path_field}: {path_val} -> <absolute>")
1109
+
1110
+ # Check project paths
1111
+ if 'projects' in raw_data:
1112
+ for project in raw_data['projects']:
1113
+ project_name = project.get('project_name', 'unknown')
1114
+ for path_field in ['local_path', 'db_path']:
1115
+ if path_field in project and project[path_field]:
1116
+ path_val = project[path_field]
1117
+ if not Path(path_val).is_absolute():
1118
+ changes_made.append(f"{project_name}.{path_field}: {path_val} -> <absolute>")
1119
+
1120
+ if changes_made:
1121
+ console.print(f"[yellow]Found {len(changes_made)} relative paths to migrate:[/yellow]")
1122
+ for change in changes_made:
1123
+ console.print(f" • {change}")
1124
+
1125
+ if not dry_run:
1126
+ # Load using ServiceSettings which will auto-convert to absolute paths
1127
+ migrated_config = ServiceSettings.load_from_file(config_path)
1128
+
1129
+ # Save back to file (now with absolute paths)
1130
+ migrated_config.save_to_file(config_path)
1131
+ console.print(f"[green]✓ Successfully migrated configuration: {name}[/green]")
1132
+ migrated_count += 1
1133
+ else:
1134
+ console.print(f"[blue] (dry-run: would migrate configuration: {name})[/blue]")
1135
+ migrated_count += 1
1136
+ else:
1137
+ console.print(f"[dim]No relative paths found in {name} - already migrated[/dim]")
1138
+
1139
+ except Exception as e:
1140
+ console.print(f"[red]Error processing {name}: {str(e)}[/red]")
1141
+ continue
1142
+
1143
+ # Summary
1144
+ action = "would be migrated" if dry_run else "migrated"
1145
+ if migrated_count > 0:
1146
+ console.print(f"\n[green]✓ {migrated_count} configuration(s) {action} successfully[/green]")
1147
+ if not dry_run:
1148
+ console.print("[blue]All relative paths have been converted to absolute paths.[/blue]")
1149
+ console.print("[blue]You can now use the configuration system normally.[/blue]")
1150
+ else:
1151
+ console.print(f"\n[blue]No configurations needed migration - all paths are already absolute[/blue]")
1152
+
1153
+ except Exception as e:
1154
+ console.print(f"[red]Error during migration: {str(e)}[/red]")
1155
+ raise typer.Exit(1)
1156
+ finally:
1157
+ # Disable migration mode
1158
+ if 'ESGVOC_MIGRATION_MODE' in os.environ:
1159
+ del os.environ['ESGVOC_MIGRATION_MODE']
1160
+
1161
+
1162
+ @app.command()
1163
+ def offline(
1164
+ enable: Optional[bool] = typer.Option(
1165
+ None, "--enable/--disable", help="Enable or disable offline mode. If not specified, shows current status."
1166
+ ),
1167
+ component: Optional[str] = typer.Option(
1168
+ "universe", "--component", "-c", help="Component to modify: 'universe' or project name (default: universe)"
1169
+ ),
1170
+ config_name: Optional[str] = typer.Option(
1171
+ None, "--config", help="Configuration name. Uses active configuration if not specified."
1172
+ ),
1173
+ ):
1174
+ """
1175
+ Enable, disable, or show offline mode status.
1176
+
1177
+ Examples:
1178
+ esgvoc config offline --enable # Enable offline mode for universe
1179
+ esgvoc config offline --disable # Disable offline mode for universe
1180
+ esgvoc config offline --enable -c cmip6 # Enable offline mode for cmip6 project
1181
+ esgvoc config offline # Show current offline mode status
1182
+ """
1183
+ service = get_service()
1184
+ config_manager = service.get_config_manager()
1185
+
1186
+ if config_name is None:
1187
+ config_name = config_manager.get_active_config_name()
1188
+
1189
+ configs = config_manager.list_configs()
1190
+ if config_name not in configs:
1191
+ console.print(f"[red]Error: Configuration '{config_name}' not found.[/red]")
1192
+ raise typer.Exit(1)
1193
+
1194
+ try:
1195
+ config = config_manager.get_config(config_name)
1196
+
1197
+ if enable is None:
1198
+ # Show current status
1199
+ if component == "universe":
1200
+ status = "enabled" if config.universe.offline_mode else "disabled"
1201
+ console.print(f"Universe offline mode is [cyan]{status}[/cyan] in configuration '{config_name}'")
1202
+ elif component in config.projects:
1203
+ status = "enabled" if config.projects[component].offline_mode else "disabled"
1204
+ console.print(f"Project '{component}' offline mode is [cyan]{status}[/cyan] in configuration '{config_name}'")
1205
+ else:
1206
+ console.print(f"[red]Error: Component '{component}' not found.[/red]")
1207
+ raise typer.Exit(1)
1208
+ else:
1209
+ # Update offline mode
1210
+ if component == "universe":
1211
+ config.universe.offline_mode = enable
1212
+ status = "enabled" if enable else "disabled"
1213
+ console.print(f"[green]Universe offline mode {status} in configuration '{config_name}'[/green]")
1214
+ elif component in config.projects:
1215
+ config.projects[component].offline_mode = enable
1216
+ status = "enabled" if enable else "disabled"
1217
+ console.print(f"[green]Project '{component}' offline mode {status} in configuration '{config_name}'[/green]")
1218
+ else:
1219
+ console.print(f"[red]Error: Component '{component}' not found.[/red]")
1220
+ raise typer.Exit(1)
1221
+
1222
+ # Save the configuration
1223
+ config_manager.save_active_config(config)
1224
+
1225
+ # Reset the state if we modified the active configuration
1226
+ if config_name == config_manager.get_active_config_name():
1227
+ service.current_state = service.get_state()
1228
+
1229
+ except Exception as e:
1230
+ console.print(f"[red]Error managing offline mode: {str(e)}[/red]")
1231
+ raise typer.Exit(1)
1232
+
1233
+
1020
1234
  @app.command()
1021
1235
  def avail(
1022
1236
  config_name: Optional[str] = typer.Option(
@@ -1034,6 +1248,7 @@ def avail(
1034
1248
  esgvoc config avail
1035
1249
  esgvoc config avail --config my_config
1036
1250
  """
1251
+ service = get_service()
1037
1252
  config_manager = service.get_config_manager()
1038
1253
  if config_name is None:
1039
1254
  config_name = config_manager.get_active_config_name()
@@ -1050,7 +1265,7 @@ def avail(
1050
1265
  config = ServiceSettings.load_from_file(config_path)
1051
1266
 
1052
1267
  # Get all available default projects
1053
- available_projects = ServiceSettings.DEFAULT_PROJECT_CONFIGS
1268
+ available_projects = ServiceSettings._get_default_project_configs()
1054
1269
 
1055
1270
  table = Table(title=f"Available Projects (Configuration: {config_name})")
1056
1271
  table.add_column("Status", style="bold")
esgvoc/cli/install.py CHANGED
@@ -8,7 +8,31 @@ def install():
8
8
  """Initialize default config and apply settings"""
9
9
  try:
10
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
+
11
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
+
12
36
  except Exception as e:
13
37
  typer.echo(f"Error during installation: {str(e)}", err=True)
14
38
  raise typer.Exit(1)
esgvoc/cli/main.py CHANGED
@@ -1,10 +1,12 @@
1
1
  import typer
2
2
 
3
+ from esgvoc.cli.clean import app as clean_app
3
4
  from esgvoc.cli.config import app as config_app
4
5
  from esgvoc.cli.drs import app as drs_app
5
6
  from esgvoc.cli.find import app as find_app
6
7
  from esgvoc.cli.get import app as get_app
7
8
  from esgvoc.cli.install import app as install_app
9
+ from esgvoc.cli.offline import app as offline_app
8
10
  from esgvoc.cli.status import app as status_app
9
11
  from esgvoc.cli.test_cv import app as test_cv_app
10
12
  from esgvoc.cli.valid import app as valid_app
@@ -18,6 +20,8 @@ app.add_typer(valid_app)
18
20
  app.add_typer(install_app)
19
21
  app.add_typer(drs_app)
20
22
  app.add_typer(config_app, name="config")
23
+ app.add_typer(offline_app, name="offline")
24
+ app.add_typer(clean_app, name="clean")
21
25
  app.add_typer(test_cv_app, name="test")
22
26
  app.add_typer(find_app)
23
27