ece4-exp 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.
ece4_exp/__init__.py ADDED
File without changes
ece4_exp/cli.py ADDED
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CLI entry point for ece4-exp.
4
+
5
+ This replaces the bash wrapper with a proper Python console_scripts entry point.
6
+ """
7
+
8
+ import argparse
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ from . import paths
14
+ from .yaml_util import log_info, log_warn, log_error, COLOR_CYAN, COLOR_NC
15
+
16
+
17
+ def cmd_list(args):
18
+ """List available recipes."""
19
+ print(f"{COLOR_CYAN}Available Recipes:{COLOR_NC}\n")
20
+ print("Main Recipes:")
21
+
22
+ if paths.RECIPES_DIR.exists():
23
+ recipes = sorted(paths.RECIPES_DIR.glob("*.yml")) + sorted(paths.RECIPES_DIR.glob("*.yaml"))
24
+ for recipe in recipes:
25
+ print(f" - {recipe.name}")
26
+ else:
27
+ log_warn(f"Recipes directory not found: {paths.RECIPES_DIR}")
28
+
29
+ weekly_tests = paths.RECIPES_DIR / "weekly_tests"
30
+ if weekly_tests.exists():
31
+ print("\nWeekly Tests:")
32
+ tests = sorted(weekly_tests.glob("*.yml")) + sorted(weekly_tests.glob("*.yaml"))
33
+ for test in tests:
34
+ print(f" - {test.name}")
35
+
36
+ print()
37
+
38
+
39
+ def cmd_info(args):
40
+ """Show current configuration info."""
41
+ from .yaml_util import set_quiet_mode
42
+
43
+ if args.quiet:
44
+ set_quiet_mode(True)
45
+
46
+ # Get EXPID from environment or guess
47
+ expid = os.environ.get("EXPID", "unknown")
48
+ if expid == "unknown":
49
+ # Try to guess from path structure
50
+ cwd_parts = Path.cwd().parts
51
+ if len(cwd_parts) >= 3:
52
+ guess = cwd_parts[-3]
53
+ if len(guess) == 4 and guess.isalnum():
54
+ expid = guess
55
+
56
+ conf_path = os.environ.get("CONF_PATH", f"/esarchive/autosubmit/{expid}/conf")
57
+ expdef_file = Path(conf_path) / f"expdef_{expid}.yml"
58
+ jobs_file = Path(conf_path) / f"jobs_{expid}.yml"
59
+
60
+ # Import and run generate with --info flag
61
+ import importlib
62
+ gec = importlib.import_module("ece4_exp.generate-experiment-config")
63
+
64
+ sys.argv = [
65
+ "ece4-exp",
66
+ "--expdef", str(expdef_file),
67
+ "--jobs", str(jobs_file),
68
+ "--info"
69
+ ]
70
+
71
+ if args.quiet:
72
+ sys.argv.append("--quiet")
73
+
74
+ gec.main()
75
+
76
+
77
+ def cmd_init_user(args):
78
+ """Initialize user configuration."""
79
+ log_info("Initializing user configuration in ~/.config/ece4-exp")
80
+
81
+ from . import init_config
82
+ init_config.main()
83
+
84
+
85
+ def cmd_generate(args):
86
+ """Generate experiment configuration."""
87
+ from .yaml_util import set_quiet_mode
88
+
89
+ if args.quiet:
90
+ set_quiet_mode(True)
91
+ os.environ["COLOR_NC"] = ""
92
+ os.environ["COLOR_GREEN"] = ""
93
+ os.environ["COLOR_CYAN"] = ""
94
+ os.environ["COLOR_YELLOW"] = ""
95
+ os.environ["COLOR_RED"] = ""
96
+
97
+ # Build sys.argv for the generate module
98
+ import importlib
99
+ gec = importlib.import_module("ece4_exp.generate-experiment-config")
100
+
101
+ # Get EXPID for default paths
102
+ expid = args.expid if hasattr(args, 'expid') and args.expid else os.environ.get("EXPID", "unknown")
103
+ conf_path = os.environ.get("CONF_PATH", f"/esarchive/autosubmit/{expid}/conf")
104
+
105
+ sys.argv = ["ece4-exp"]
106
+
107
+ # Add autosubmit files if they exist (backward compatibility)
108
+ expdef_file = Path(conf_path) / f"expdef_{expid}.yml"
109
+ jobs_file = Path(conf_path) / f"jobs_{expid}.yml"
110
+
111
+ if expdef_file.exists() and jobs_file.exists():
112
+ sys.argv.extend(["--expdef", str(expdef_file)])
113
+ sys.argv.extend(["--jobs", str(jobs_file)])
114
+
115
+ # Forward all args to generate module
116
+ if args.recipe:
117
+ sys.argv.extend(["--recipe", args.recipe])
118
+ if args.sim_procs:
119
+ sys.argv.extend(["--sim-procs", str(args.sim_procs)])
120
+ if args.expid:
121
+ sys.argv.extend(["--expid", args.expid])
122
+ if args.platform:
123
+ sys.argv.extend(["--platform", args.platform])
124
+ if args.launcher:
125
+ sys.argv.extend(["--launcher", args.launcher])
126
+ if args.kind:
127
+ sys.argv.extend(["--kind", args.kind])
128
+ if args.account:
129
+ sys.argv.extend(["--account", args.account])
130
+ if args.walltime:
131
+ sys.argv.extend(["--walltime", str(args.walltime)])
132
+ if args.description:
133
+ sys.argv.extend(["--description", args.description])
134
+ if args.repo_owner:
135
+ sys.argv.extend(["--repo-owner", args.repo_owner])
136
+ if args.repo_branch:
137
+ sys.argv.extend(["--repo-branch", args.repo_branch])
138
+ if args.output:
139
+ sys.argv.extend(["--output", args.output])
140
+ if args.dry_run:
141
+ sys.argv.append("--dry-run")
142
+ if args.quiet:
143
+ sys.argv.append("--quiet")
144
+
145
+ gec.main()
146
+
147
+
148
+ def cmd_validate(args):
149
+ """Validate experiment configuration."""
150
+ import importlib
151
+ vec = importlib.import_module("ece4_exp.validate-experiment-config")
152
+
153
+ config_file = args.config_file
154
+ if not config_file:
155
+ # Default to EXPID_experiment.yml
156
+ expid = os.environ.get("EXPID", "unknown")
157
+ config_file = f"{expid}_experiment.yml"
158
+
159
+ if not Path(config_file).exists():
160
+ log_error(f"Configuration file not found: {config_file}")
161
+ sys.exit(1)
162
+
163
+ sys.argv = ["ece4-exp", config_file]
164
+ vec.main()
165
+
166
+
167
+ def cmd_save(args):
168
+ """Save changes as a recipe."""
169
+ import importlib
170
+ srd = importlib.import_module("ece4_exp.save_recipe_from_diff")
171
+
172
+ # Get EXPID
173
+ expid = args.expid if args.expid else os.environ.get("EXPID", "unknown")
174
+ conf_path = os.environ.get("CONF_PATH", f"/esarchive/autosubmit/{expid}/conf")
175
+ expdef_file = Path(conf_path) / f"expdef_{expid}.yml"
176
+
177
+ # Output file
178
+ output_file = args.output if args.output else f"{expid}.yml"
179
+
180
+ log_info(f"Saving recipe: {COLOR_CYAN}{output_file}{COLOR_NC} (Expid: {expid})")
181
+
182
+ sys.argv = ["ece4-exp", "--expid", expid]
183
+
184
+ if expdef_file.exists():
185
+ sys.argv.extend(["--expdef", str(expdef_file)])
186
+
187
+ if args.recipe:
188
+ sys.argv.extend(["--recipe", args.recipe])
189
+
190
+ sys.argv.extend(["--output", output_file])
191
+
192
+ srd.main()
193
+
194
+
195
+ def main():
196
+ """Main CLI entry point."""
197
+ parser = argparse.ArgumentParser(
198
+ prog="ece4-exp",
199
+ description="EC-Earth4 experiment configuration tool",
200
+ formatter_class=argparse.RawDescriptionHelpFormatter,
201
+ epilog="""
202
+ Examples:
203
+ ece4-exp list
204
+ ece4-exp generate --recipe gcm-sr.yml --sim-procs 1120 --expid test001
205
+ ece4-exp validate myexp.yml
206
+ ece4-exp save --expid myexp --recipe gcm-sr.yml -o my-recipe.yml
207
+
208
+ For more help: ece4-exp <command> --help
209
+ """
210
+ )
211
+
212
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
213
+
214
+ # --- list ---
215
+ parser_list = subparsers.add_parser("list", help="List available recipes")
216
+ parser_list.set_defaults(func=cmd_list)
217
+
218
+ # --- info ---
219
+ parser_info = subparsers.add_parser("info", help="Show current configuration info")
220
+ parser_info.add_argument("--quiet", action="store_true", help="Suppress colored output")
221
+ parser_info.set_defaults(func=cmd_info)
222
+
223
+ # --- init-user ---
224
+ parser_init = subparsers.add_parser("init-user", help="Initialize user configuration")
225
+ parser_init.set_defaults(func=cmd_init_user)
226
+
227
+ # --- generate ---
228
+ parser_gen = subparsers.add_parser("generate", help="Generate experiment configuration")
229
+
230
+ # Core parameters
231
+ parser_gen.add_argument("--recipe", help="Recipe name (e.g. gcm-sr.yml)")
232
+ parser_gen.add_argument("--sim-procs", type=int, dest="sim_procs", help="Number of processors for SIM job")
233
+ parser_gen.add_argument("--expid", help="Experiment ID")
234
+
235
+ # Platform parameters
236
+ parser_gen.add_argument("--platform", help="HPC Platform (e.g. bsc-marenostrum5)")
237
+ parser_gen.add_argument("--launcher", help="Launcher type (e.g. slurm-wrapper-taskset)")
238
+ parser_gen.add_argument("--kind", help="Launcher kind (e.g. CPLD-SR, auto)")
239
+
240
+ # User parameters
241
+ parser_gen.add_argument("--account", help="HPC account/project")
242
+ parser_gen.add_argument("--walltime", type=int, help="Walltime in hours")
243
+ parser_gen.add_argument("--description", help="Experiment description")
244
+
245
+ # Repository parameters
246
+ parser_gen.add_argument("--repo-owner", dest="repo_owner", help="ECE4 repository owner")
247
+ parser_gen.add_argument("--repo-branch", dest="repo_branch", help="ECE4 repository branch")
248
+
249
+ # Output control
250
+ parser_gen.add_argument("-o", "--output", help="Output file name")
251
+ parser_gen.add_argument("--dry-run", action="store_true", help="Preview without writing")
252
+ parser_gen.add_argument("--quiet", action="store_true", help="Suppress colored output")
253
+
254
+ parser_gen.set_defaults(func=cmd_generate)
255
+
256
+ # --- validate ---
257
+ parser_val = subparsers.add_parser("validate", help="Validate experiment configuration")
258
+ parser_val.add_argument("config_file", nargs="?", help="Path to configuration file")
259
+ parser_val.set_defaults(func=cmd_validate)
260
+
261
+ # --- save ---
262
+ parser_save = subparsers.add_parser("save", help="Save changes as a recipe")
263
+ parser_save.add_argument("--expid", help="Experiment ID")
264
+ parser_save.add_argument("--recipe", help="Current user recipe name")
265
+ parser_save.add_argument("-o", "--output", help="Recipe file name")
266
+ parser_save.set_defaults(func=cmd_save)
267
+
268
+ # Parse args
269
+ args = parser.parse_args()
270
+
271
+ if not args.command:
272
+ parser.print_help()
273
+ sys.exit(1)
274
+
275
+ # Run command
276
+ try:
277
+ args.func(args)
278
+ except KeyboardInterrupt:
279
+ print("\nInterrupted by user")
280
+ sys.exit(130)
281
+ except Exception as e:
282
+ log_error(f"Command failed: {e}")
283
+ if "--debug" in sys.argv or os.environ.get("DEBUG"):
284
+ raise
285
+ sys.exit(1)
286
+
287
+
288
+ if __name__ == "__main__":
289
+ main()