-
Notifications
You must be signed in to change notification settings - Fork 2
[python] Migrate CLI from argparse to click #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,50 +6,17 @@ | |
| This module provides command-line interface for validating CUTracer trace files. | ||
| """ | ||
|
|
||
| import argparse | ||
| import json | ||
| import sys | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| import click | ||
|
|
||
| from .json_validator import validate_json_trace | ||
| from .text_validator import validate_text_trace | ||
|
|
||
|
|
||
| def _add_validate_args(parser: argparse.ArgumentParser) -> None: | ||
| """Add arguments for the validate subcommand.""" | ||
| parser.add_argument( | ||
| "file", | ||
| type=Path, | ||
| help="Path to the trace file to validate", | ||
| ) | ||
| parser.add_argument( | ||
| "--format", | ||
| "-f", | ||
| choices=["json", "text", "auto"], | ||
| default="auto", | ||
| help="File format. Default: auto-detect from extension.", | ||
| ) | ||
| parser.add_argument( | ||
| "--quiet", | ||
| "-q", | ||
| action="store_true", | ||
| help="Quiet mode. Only return exit code.", | ||
| ) | ||
| parser.add_argument( | ||
| "--json", | ||
| dest="json_output", | ||
| action="store_true", | ||
| help="Output results in JSON format.", | ||
| ) | ||
| parser.add_argument( | ||
| "--verbose", | ||
| "-v", | ||
| action="store_true", | ||
| help="Verbose output with additional details.", | ||
| ) | ||
|
|
||
|
|
||
| def _detect_format(file_path: Path) -> str: | ||
| """Auto-detect file format from extension.""" | ||
| suffixes = "".join(file_path.suffixes).lower() | ||
|
|
@@ -87,43 +54,76 @@ def _format_trace_format(result: dict[str, Any]) -> str: | |
| def _print_validation_result(result: dict[str, Any], verbose: bool = False) -> None: | ||
| """Print validation result in human-readable format.""" | ||
| if result["valid"]: | ||
| print("\u2705 Valid trace file") | ||
| print(f" Format: {_format_trace_format(result)}") | ||
| print(f" Records: {result['record_count']}") | ||
| click.echo("\u2705 Valid trace file") | ||
| click.echo(f" Format: {_format_trace_format(result)}") | ||
| click.echo(f" Records: {result['record_count']}") | ||
| if result.get("message_type"): | ||
| print(f" Message type: {result['message_type']}") | ||
| click.echo(f" Message type: {result['message_type']}") | ||
| if result.get("file_size"): | ||
| print(f" File size: {_format_size(result['file_size'])}") | ||
| click.echo(f" File size: {_format_size(result['file_size'])}") | ||
| if verbose and result.get("compression") == "zstd": | ||
| print(" Compression: zstd") | ||
| click.echo(" Compression: zstd") | ||
| else: | ||
| print("\u274c Validation failed") | ||
| click.echo("\u274c Validation failed") | ||
| for error in result.get("errors", []): | ||
| print(f" {error}") | ||
|
|
||
|
|
||
| def validate_command(args: argparse.Namespace) -> int: | ||
| """Execute the validate subcommand.""" | ||
| file_path: Path = args.file | ||
|
|
||
| # Check file exists | ||
| if not file_path.exists(): | ||
| if not args.quiet: | ||
| print(f"Error: File not found: {file_path}", file=sys.stderr) | ||
| return 2 | ||
| click.echo(f" {error}") | ||
|
|
||
|
|
||
| @click.command(name="validate") | ||
| @click.argument("file", type=click.Path(exists=True, path_type=Path)) | ||
| @click.option( | ||
| "--format", | ||
| "-f", | ||
| "file_format", | ||
| type=click.Choice(["json", "text", "auto"]), | ||
| default="auto", | ||
| help="File format. Default: auto-detect from extension.", | ||
| ) | ||
| @click.option( | ||
| "--quiet", | ||
| "-q", | ||
| is_flag=True, | ||
| help="Quiet mode. Only return exit code.", | ||
| ) | ||
| @click.option( | ||
| "--json", | ||
| "json_output", | ||
| is_flag=True, | ||
| help="Output results in JSON format.", | ||
| ) | ||
| @click.option( | ||
| "--verbose", | ||
| "-v", | ||
| is_flag=True, | ||
| help="Verbose output with additional details.", | ||
| ) | ||
| def validate_command( | ||
| file: Path, | ||
| file_format: str, | ||
| quiet: bool, | ||
| json_output: bool, | ||
| verbose: bool, | ||
| ) -> None: | ||
|
Comment on lines
+100
to
+106
|
||
| """Validate a CUTracer trace file. | ||
|
|
||
| Checks syntax and schema compliance for NDJSON, Zstd-compressed, | ||
| and text format trace files. | ||
|
|
||
| FILE is the path to the trace file to validate. | ||
| """ | ||
| file_path = file | ||
|
||
|
|
||
| # Detect format | ||
| file_format = args.format | ||
| if file_format == "auto": | ||
| file_format = _detect_format(file_path) | ||
| if file_format == "unknown": | ||
| if not args.quiet: | ||
| print( | ||
| if not quiet: | ||
| click.echo( | ||
| f"Error: Cannot auto-detect format for {file_path}. " | ||
| "Use --format to specify.", | ||
| file=sys.stderr, | ||
| err=True, | ||
| ) | ||
| return 2 | ||
| sys.exit(2) | ||
|
|
||
| # Run validation | ||
| if file_format == "json": | ||
|
|
@@ -132,17 +132,17 @@ def validate_command(args: argparse.Namespace) -> int: | |
| result = validate_text_trace(file_path) | ||
|
|
||
| # Handle quiet mode | ||
| if args.quiet: | ||
| return 0 if result["valid"] else 1 | ||
| if quiet: | ||
| sys.exit(0 if result["valid"] else 1) | ||
|
|
||
| # Handle JSON output | ||
| if args.json_output: | ||
| if json_output: | ||
| # Convert Path objects to strings for JSON serialization | ||
| output = {k: str(v) if isinstance(v, Path) else v for k, v in result.items()} | ||
| print(json.dumps(output, indent=2)) | ||
| return 0 if result["valid"] else 1 | ||
| click.echo(json.dumps(output, indent=2)) | ||
| sys.exit(0 if result["valid"] else 1) | ||
|
|
||
| # Human-readable output | ||
| _print_validation_result(result, args.verbose) | ||
| _print_validation_result(result, verbose) | ||
|
|
||
| return 0 if result["valid"] else 1 | ||
| sys.exit(0 if result["valid"] else 1) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return type annotation has changed from
inttoNone, but the existing tests inpython/tests/test_cli.pyexpectmain()to return an integer exit code. Tests liketest_validate_valid_json()callexit_code = main()and assert on the value. With click, the function returns None and usessys.exit()internally. This will break all existing CLI tests. Consider either updating the tests to useassertRaises(SystemExit)or making click'sstandalone_mode=Falseto preserve the return value behavior for testing.