wuenlp_tools.run_pipeline

Run a configured wuenlp-tools pipeline from JSON or CLI flags.

  1"""Run a configured wuenlp-tools pipeline from JSON or CLI flags."""
  2
  3from __future__ import annotations
  4
  5import argparse
  6import json
  7import sys
  8from pathlib import Path
  9from typing import Any
 10
 11from loguru import logger
 12from wuenlp import UIMADocument
 13from wuenlp.impl.UIMANLPStructs import UIMASystemScene
 14
 15from wuenlp_tools.pipeline import Pipeline
 16from wuenlp_tools.pipeline_catalog import (
 17    PIPELINE_PRESET_STEP_NAMES,
 18    build_steps,
 19    catalog_as_json,
 20    get_pipeline_presets,
 21)
 22
 23
 24def _load_input(path: Path) -> UIMADocument:
 25    suffix = path.name.lower()
 26    if suffix.endswith(".xmi.zip") or suffix.endswith(".xmi"):
 27        return UIMADocument.from_xmi(path)
 28    return UIMADocument.from_text(path.read_text(encoding="utf-8"))
 29
 30
 31def _wire_moment_embedder(pipeline: Pipeline, steps: list) -> None:
 32    moment_step = next((step for step in steps if step.name == "MomentEmbedder"), None)
 33    if moment_step is None:
 34        return
 35    moment_step.processor.set_include_features(
 36        pipeline.added_additional_features[UIMASystemScene]
 37    )
 38
 39
 40def run_pipeline_config(config: dict[str, Any]) -> Path:
 41    input_path = Path(config["input"]).resolve()
 42    output_path = Path(config["output"]).resolve()
 43    if not input_path.exists():
 44        raise FileNotFoundError(f"Input not found: {input_path}")
 45
 46    step_specs = config.get("steps", [])
 47    if isinstance(step_specs, str):
 48        preset = step_specs
 49        presets = get_pipeline_presets()
 50        if preset not in presets:
 51            raise KeyError(f"Unknown pipeline preset: {preset}")
 52        step_specs = presets[preset]
 53
 54    steps = build_steps(step_specs)
 55    if not steps:
 56        raise ValueError("Pipeline has no steps configured.")
 57
 58    logger.info("Pipeline steps: {}", " → ".join(step.name for step in steps))
 59
 60    intermediate_dir = config.get("intermediate_dir")
 61    intermediate_path = Path(intermediate_dir).resolve() if intermediate_dir else None
 62    overwrite = bool(config.get("overwrite", False))
 63
 64    logger.info("Loading {}", input_path)
 65    doc = _load_input(input_path)
 66
 67    pipeline = Pipeline(
 68        steps=steps,
 69        intermediate_file_dir=intermediate_path,
 70        overwrite=overwrite,
 71    )
 72    _wire_moment_embedder(pipeline, steps)
 73
 74    logger.info("Running pipeline with {} steps", len(steps))
 75    doc = pipeline(doc)
 76
 77    output_path.parent.mkdir(parents=True, exist_ok=True)
 78    doc.serialize(output_path)
 79    logger.info("Wrote {}", output_path)
 80    return output_path
 81
 82
 83def main() -> int:
 84    parser = argparse.ArgumentParser(description="Run a wuenlp-tools pipeline from configuration.")
 85    parser.add_argument("--config", type=Path, help="JSON config file")
 86    parser.add_argument("--input", type=Path, help="Input .txt / .xmi / .xmi.zip file")
 87    parser.add_argument("--output", type=Path, help="Output .xmi / .xmi.zip path")
 88    parser.add_argument(
 89        "--steps",
 90        type=str,
 91        help="Comma-separated step ids or a preset name (process_file, example, coref_only)",
 92    )
 93    parser.add_argument("--intermediate-dir", type=Path, default=None, help="Cache directory for step outputs")
 94    parser.add_argument("--overwrite", action="store_true", help="Overwrite cached intermediate results")
 95    parser.add_argument("--catalog", action="store_true", help="Print step catalog as JSON and exit")
 96    parser.add_argument("--debug", action="store_true", help="Enable debug logging")
 97    args = parser.parse_args()
 98
 99    if args.debug:
100        logger.remove()
101        logger.add(sink=sys.stdout, level="DEBUG")
102
103    if args.catalog:
104        logger.remove()
105        sys.stdout.write(catalog_as_json())
106        sys.stdout.write("\n")
107        return 0
108
109    if args.config:
110        config = json.loads(Path(args.config).read_text(encoding="utf-8"))
111    else:
112        if not args.input or not args.output or not args.steps:
113            parser.error("Provide --config or (--input, --output, and --steps)")
114        step_specs: str | list[str]
115        if args.steps in PIPELINE_PRESET_STEP_NAMES:
116            step_specs = args.steps
117        else:
118            step_specs = [part.strip() for part in args.steps.split(",") if part.strip()]
119        config = {
120            "input": str(args.input),
121            "output": str(args.output),
122            "steps": step_specs,
123            "intermediate_dir": str(args.intermediate_dir) if args.intermediate_dir else None,
124            "overwrite": args.overwrite,
125        }
126
127    try:
128        run_pipeline_config(config)
129    except Exception as exc:
130        logger.error("Pipeline failed: {}", exc)
131        return 1
132    return 0
133
134
135if __name__ == "__main__":
136    sys.exit(main())
def run_pipeline_config(config: dict[str, typing.Any]) -> pathlib.Path:
41def run_pipeline_config(config: dict[str, Any]) -> Path:
42    input_path = Path(config["input"]).resolve()
43    output_path = Path(config["output"]).resolve()
44    if not input_path.exists():
45        raise FileNotFoundError(f"Input not found: {input_path}")
46
47    step_specs = config.get("steps", [])
48    if isinstance(step_specs, str):
49        preset = step_specs
50        presets = get_pipeline_presets()
51        if preset not in presets:
52            raise KeyError(f"Unknown pipeline preset: {preset}")
53        step_specs = presets[preset]
54
55    steps = build_steps(step_specs)
56    if not steps:
57        raise ValueError("Pipeline has no steps configured.")
58
59    logger.info("Pipeline steps: {}", " → ".join(step.name for step in steps))
60
61    intermediate_dir = config.get("intermediate_dir")
62    intermediate_path = Path(intermediate_dir).resolve() if intermediate_dir else None
63    overwrite = bool(config.get("overwrite", False))
64
65    logger.info("Loading {}", input_path)
66    doc = _load_input(input_path)
67
68    pipeline = Pipeline(
69        steps=steps,
70        intermediate_file_dir=intermediate_path,
71        overwrite=overwrite,
72    )
73    _wire_moment_embedder(pipeline, steps)
74
75    logger.info("Running pipeline with {} steps", len(steps))
76    doc = pipeline(doc)
77
78    output_path.parent.mkdir(parents=True, exist_ok=True)
79    doc.serialize(output_path)
80    logger.info("Wrote {}", output_path)
81    return output_path
def main() -> int:
 84def main() -> int:
 85    parser = argparse.ArgumentParser(description="Run a wuenlp-tools pipeline from configuration.")
 86    parser.add_argument("--config", type=Path, help="JSON config file")
 87    parser.add_argument("--input", type=Path, help="Input .txt / .xmi / .xmi.zip file")
 88    parser.add_argument("--output", type=Path, help="Output .xmi / .xmi.zip path")
 89    parser.add_argument(
 90        "--steps",
 91        type=str,
 92        help="Comma-separated step ids or a preset name (process_file, example, coref_only)",
 93    )
 94    parser.add_argument("--intermediate-dir", type=Path, default=None, help="Cache directory for step outputs")
 95    parser.add_argument("--overwrite", action="store_true", help="Overwrite cached intermediate results")
 96    parser.add_argument("--catalog", action="store_true", help="Print step catalog as JSON and exit")
 97    parser.add_argument("--debug", action="store_true", help="Enable debug logging")
 98    args = parser.parse_args()
 99
100    if args.debug:
101        logger.remove()
102        logger.add(sink=sys.stdout, level="DEBUG")
103
104    if args.catalog:
105        logger.remove()
106        sys.stdout.write(catalog_as_json())
107        sys.stdout.write("\n")
108        return 0
109
110    if args.config:
111        config = json.loads(Path(args.config).read_text(encoding="utf-8"))
112    else:
113        if not args.input or not args.output or not args.steps:
114            parser.error("Provide --config or (--input, --output, and --steps)")
115        step_specs: str | list[str]
116        if args.steps in PIPELINE_PRESET_STEP_NAMES:
117            step_specs = args.steps
118        else:
119            step_specs = [part.strip() for part in args.steps.split(",") if part.strip()]
120        config = {
121            "input": str(args.input),
122            "output": str(args.output),
123            "steps": step_specs,
124            "intermediate_dir": str(args.intermediate_dir) if args.intermediate_dir else None,
125            "overwrite": args.overwrite,
126        }
127
128    try:
129        run_pipeline_config(config)
130    except Exception as exc:
131        logger.error("Pipeline failed: {}", exc)
132        return 1
133    return 0