Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / codegen / cli.py: 0%
126 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
1# Copyright 2026 Huawei Technologies Co., Ltd
2# Licensed under the Apache License, Version 2.0
3# ============================================================================
4"""Codegen CLI: thin command-line wrapper over manager / check / artifact.
6``python -m hyper_parallel.codegen`` dispatches to :func:`main`. No business
7logic — argument parsing, YAML discovery, and exit-code mapping only.
9Exit codes are 0 for success or a skipped pin check, 1 for a check/clean
10failure, and 2 for a usage error.
11"""
12from __future__ import annotations
14import argparse
15import logging
16import os
17import sys
18from typing import Iterator, Optional
20logger = logging.getLogger(__name__)
23def main(argv: Optional[list[str]] = None) -> int:
24 """argparse entry; returns the process exit code."""
25 parser = _build_parser()
26 args, config_overrides = parser.parse_known_args(argv)
27 setattr(args, "_config_overrides", config_overrides)
28 if not getattr(args, "command", None):
29 parser.print_usage(sys.stderr)
30 return 2
31 _configure_logging(getattr(args, "verbose", False))
32 try:
33 return args.func(args)
34 except Exception as exc: # noqa: BLE001 - a CLI reports, does not traceback
35 logger.error("codegen: %s", exc)
36 return 1
39# ---------------------------------------------------------------------------
40# commands
41# ---------------------------------------------------------------------------
43def cmd_generate(args: argparse.Namespace) -> int:
44 """``generate --config <yaml>`` — build/reuse the bundle for one YAML."""
45 config = _load_config(args.config, _cli_overrides(args))
46 from hyper_parallel.codegen.manager import ensure_codegen_artifact
48 if not getattr(config, "codegen", False):
49 logger.info("codegen: generate skipped — codegen is false")
50 return 0
51 layout = ensure_codegen_artifact(config, args.config)
52 if layout is None:
53 return 0
54 from hyper_parallel.codegen.meta import load_codegen_meta
56 meta = load_codegen_meta(layout.meta_path)
57 signature = meta.signature if meta is not None else "?"
58 logger.info("codegen: artifact %s (signature %s)", layout.artifact_dir, signature)
59 return 0
62def cmd_check(args: argparse.Namespace) -> int:
63 """``check --config <yaml>`` — full preflight for one YAML."""
64 config = _load_config(args.config, _cli_overrides(args))
65 if not getattr(config, "codegen", False):
66 logger.info("codegen: SKIP (codegen disabled)")
67 return 0
68 from hyper_parallel.codegen.manager import preflight_integrity_check
70 preflight_integrity_check(config, args.config)
71 logger.info("codegen: PASS %s", args.config)
72 return 0
75def cmd_check_all(args: argparse.Namespace) -> int:
76 """Run preflight for every discovered YAML and check cached artifact drift."""
77 if _cli_overrides(args):
78 raise ValueError("check --all does not accept per-config CLI overrides")
79 failed = 0
80 for yaml_path in discover_yaml_files(args.root):
81 try:
82 config = _load_config(yaml_path)
83 if not getattr(config, "codegen", False):
84 logger.info("codegen: SKIP (codegen disabled) %s", yaml_path)
85 continue
86 from hyper_parallel.codegen.manager import preflight_integrity_check
88 preflight_integrity_check(config, yaml_path)
89 logger.info("codegen: PASS %s", yaml_path)
90 # Drift can only be checked when an artifact already exists.
91 from hyper_parallel.codegen.check.drift import verify_drift_intact
92 from hyper_parallel.codegen.meta import load_codegen_meta
93 from hyper_parallel.codegen.artifact import resolve_artifact_layout
95 layout = resolve_artifact_layout(yaml_path)
96 meta = load_codegen_meta(layout.meta_path)
97 if meta is not None:
98 drifts = verify_drift_intact(meta, yaml_path)
99 if drifts:
100 failed = 1
101 logger.error("codegen: drift in %s: %s", yaml_path, "; ".join(drifts))
102 else:
103 logger.info("codegen: drift intact %s", yaml_path)
104 except Exception as exc: # noqa: BLE001 - aggregate, keep scanning
105 failed = 1
106 logger.error("codegen: FAIL %s: %s", yaml_path, exc)
107 return failed
110def cmd_check_pin(args: argparse.Namespace) -> int:
111 """Validate a Transformers version constraint without regenerating artifacts."""
112 from hyper_parallel.codegen.check.pin import verify_transformers_pin
114 status, installed, spec = verify_transformers_pin(args.transformers_pin)
115 logger.info("codegen: transformers pin %s — installed %s (%s)", spec, installed, status)
116 return 0 # OK and SKIP both exit 0
119# ---------------------------------------------------------------------------
120# discovery & helpers
121# ---------------------------------------------------------------------------
123_SKIP_DIRS = {"generated", ".git", ".venv", "venv", "__pycache__", ".tmp"}
126def discover_yaml_files(root: str) -> Iterator[str]:
127 """Walk ``root`` for ``*.yaml`` / ``*.yml``, skipping generated/venv/git dirs."""
128 if not os.path.isdir(root):
129 return
130 for dirpath, dirnames, filenames in os.walk(root):
131 dirnames[:] = [d for d in dirnames if d not in _SKIP_DIRS]
132 for name in sorted(filenames):
133 if name.endswith((".yaml", ".yml")):
134 yield os.path.join(dirpath, name)
137def _load_config(yaml_path: str, overrides: Optional[list[str]] = None):
138 """Load a ``TrainerConfig`` from a YAML path."""
139 from hyper_parallel.trainer.config.parser import load_training_config
141 return load_training_config(yaml_path, overrides or [])
144def _model_name(config: object) -> Optional[str]:
145 from hyper_parallel.codegen.manager import ( # pylint: disable=import-outside-toplevel
146 _model_name as _manager_model_name,
147 _resolve_hf_config_for_layout,
148 )
150 return _manager_model_name(config, _resolve_hf_config_for_layout(config))
153def _model_name_for(yaml_path: str, overrides: Optional[list[str]] = None) -> Optional[str]:
154 """Model name from a YAML (used by ``clean`` before config load)."""
155 try:
156 return _model_name(_load_config(yaml_path, overrides))
157 except Exception: # noqa: BLE001 - clean falls back to default naming
158 return None
161def _cli_overrides(args: argparse.Namespace) -> list[str]:
162 """Typed config overrides captured after argparse consumes codegen flags."""
163 return list(getattr(args, "_config_overrides", None) or [])
166def _configure_logging(verbose: bool) -> None:
167 logging.basicConfig(
168 level=logging.INFO if verbose else logging.WARNING,
169 format="%(levelname)s:%(name)s:%(message)s",
170 )
173def _build_parser() -> argparse.ArgumentParser:
174 """Build the argparse parser for the codegen CLI."""
175 parser = argparse.ArgumentParser(
176 prog="hyper_parallel.codegen",
177 description="Codegen: generate/check/clean codegen artifacts.",
178 )
179 sub = parser.add_subparsers(dest="command")
180 parser.add_argument("--verbose", action="store_true", help="verbose logging")
182 gen = sub.add_parser("generate", help="generate or reuse the bundle for one YAML")
183 gen.add_argument("--config", required=True, help="path to the training YAML")
184 gen.set_defaults(func=cmd_generate)
186 check = sub.add_parser("check", help="preflight integrity check")
187 check.add_argument("--config", help="path to the training YAML")
188 check.add_argument("--all", action="store_true", help="check every discovered YAML")
189 check.add_argument("--root", default=".", help="root to search when --all")
190 check.add_argument("--transformers-pin", help="Transformers version constraint (e.g. ==5.15.0)")
191 check.set_defaults(func=_cmd_check_dispatch)
193 return parser
196def _cmd_check_dispatch(args: argparse.Namespace) -> int:
197 """Route the ``check`` subcommand's mutually-exclusive modes."""
198 modes = [bool(args.all), bool(args.transformers_pin)]
199 if sum(modes) > 1:
200 raise ValueError("check accepts only one of --all / --transformers-pin")
201 if args.all:
202 return cmd_check_all(args)
203 if args.transformers_pin:
204 return cmd_check_pin(args)
205 if not args.config:
206 raise ValueError("check needs --config, --all, or --transformers-pin")
207 return cmd_check(args)
210__all__ = [
211 "cmd_check",
212 "cmd_check_all",
213 "cmd_check_pin",
214 "cmd_generate",
215 "discover_yaml_files",
216 "main",
217]