Skip to content

Commit fcccda2

Browse files
authored
Merge pull request #186 from rom-py/postprocess_cli
Added postprocess command to cli
2 parents 51cb869 + 6e1bf90 commit fcccda2

File tree

2 files changed

+103
-0
lines changed

2 files changed

+103
-0
lines changed

rompy/cli.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,60 @@ def generate(
479479
sys.exit(1)
480480

481481

482+
@cli.command()
483+
@click.argument("config", type=click.Path(exists=True), required=False)
484+
@click.option("--processor", default="noop", help="Postprocessor to use (default: noop)")
485+
@click.option("--output-dir", help="Override output directory for postprocessing")
486+
@click.option("--validate-outputs/--no-validate", default=True, help="Validate outputs exist (default: True)")
487+
@add_common_options
488+
def postprocess(
489+
config,
490+
processor,
491+
output_dir,
492+
validate_outputs,
493+
verbose,
494+
log_dir,
495+
show_warnings,
496+
ascii_only,
497+
simple_logs,
498+
config_from_env,
499+
):
500+
"""Run postprocessing on model outputs using the specified postprocessor."""
501+
configure_logging(verbose, log_dir, simple_logs, ascii_only, show_warnings)
502+
503+
# Validate config source
504+
if config_from_env and config:
505+
raise click.UsageError("Cannot specify both config file and --config-from-env")
506+
if not config_from_env and not config:
507+
raise click.UsageError("Must specify either config file or --config-from-env")
508+
509+
try:
510+
# Load configuration
511+
config_data = load_config(config, from_env=config_from_env)
512+
model_run = ModelRun(**config_data)
513+
514+
logger.info(f"Running postprocessing for: {model_run.config.model_type}")
515+
logger.info(f"Run ID: {model_run.run_id}")
516+
logger.info(f"Postprocessor: {processor}")
517+
518+
# Run postprocessing
519+
start_time = datetime.now()
520+
results = model_run.postprocess(
521+
processor=processor,
522+
output_dir=output_dir,
523+
validate_outputs=validate_outputs,
524+
)
525+
elapsed = datetime.now() - start_time
526+
527+
logger.info(f"✅ Postprocessing completed in {elapsed.total_seconds():.2f}s")
528+
logger.info(f"Results: {results}")
529+
530+
except Exception as e:
531+
logger.error(f"❌ Postprocessing failed: {e}")
532+
if verbose > 0:
533+
logger.exception("Full traceback:")
534+
sys.exit(1)
535+
482536
@cli.command()
483537
@click.argument("config", type=click.Path(exists=True), required=False)
484538
@add_common_options

tests/test_cli_config.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,16 @@ def test_pipeline_with_config_from_env(self):
194194
])
195195
assert result.exit_code == 0
196196

197+
def test_postprocess_with_config_from_env(self):
198+
"""Test postprocess command with config from environment variable."""
199+
with patch.dict(os.environ, {'ROMPY_CONFIG': self.config_json}):
200+
with patch('rompy.cli.ModelRun') as mock_model_run:
201+
result = self.runner.invoke(cli, [
202+
'postprocess', '--config-from-env', '--processor', 'noop'
203+
])
204+
assert result.exit_code == 0
205+
assert mock_model_run.called
206+
197207
def test_config_from_env_missing_variable(self):
198208
"""Test error when ROMPY_CONFIG environment variable is missing."""
199209
with patch.dict(os.environ, {}, clear=True):
@@ -232,6 +242,24 @@ def setup_method(self):
232242
"period": {"start": "2020-01-01", "end": "2020-01-02"}
233243
}
234244

245+
def test_postprocess_error_on_missing_config(self):
246+
"""Test error when neither config file nor --config-from-env is specified for postprocess."""
247+
result = self.runner.invoke(cli, ['postprocess'])
248+
assert result.exit_code != 0
249+
assert "Must specify either" in result.output
250+
251+
def test_postprocess_error_on_both_config_and_env(self):
252+
"""Test error when both config file and --config-from-env are specified for postprocess."""
253+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
254+
json.dump(self.config_data, f)
255+
config_path = f.name
256+
try:
257+
result = self.runner.invoke(cli, ['postprocess', config_path, '--config-from-env'])
258+
assert result.exit_code != 0
259+
assert "Cannot specify both" in result.output
260+
finally:
261+
os.unlink(config_path)
262+
235263
def test_validate_with_config_file(self):
236264
"""Test that validate command still works with config files."""
237265
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
@@ -283,6 +311,21 @@ def test_run_with_config_file(self):
283311
os.unlink(config_path)
284312
os.unlink(backend_path)
285313

314+
def test_postprocess_with_config_file(self):
315+
"""Test that postprocess command works with config files."""
316+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
317+
json.dump(self.config_data, f)
318+
config_path = f.name
319+
try:
320+
with patch('rompy.cli.ModelRun') as mock_model_run:
321+
result = self.runner.invoke(cli, [
322+
'postprocess', config_path, '--processor', 'noop'
323+
])
324+
assert result.exit_code == 0
325+
assert mock_model_run.called
326+
finally:
327+
os.unlink(config_path)
328+
286329

287330
class TestCLIHelpOutput:
288331
"""Test that help output includes information about environment variable option."""
@@ -315,3 +358,9 @@ def test_pipeline_help_includes_config_from_env(self):
315358
result = self.runner.invoke(cli, ['pipeline', '--help'])
316359
assert result.exit_code == 0
317360
assert '--config-from-env' in result.output
361+
362+
def test_postprocess_help_includes_config_from_env(self):
363+
"""Test that postprocess command help mentions --config-from-env option."""
364+
result = self.runner.invoke(cli, ['postprocess', '--help'])
365+
assert result.exit_code == 0
366+
assert '--config-from-env' in result.output

0 commit comments

Comments
 (0)