scripts/tabular_profile.py
3.0 KB| 1 | #!/usr/bin/env python3 |
| 2 | """Bounded aggregate schema/profile CLI for local CSV and TSV files.""" |
| 3 | |
| 4 | from __future__ import annotations |
| 5 | |
| 6 | import argparse |
| 7 | |
| 8 | from _capabilities import capability_for_path |
| 9 | from _common import ( |
| 10 | DEFAULT_MAX_FILE_BYTES, |
| 11 | DEFAULT_MAX_ROWS, |
| 12 | CliError, |
| 13 | bounded_file_limit, |
| 14 | checked_input_file, |
| 15 | emit_json, |
| 16 | run_cli, |
| 17 | ) |
| 18 | from _tabular import profile_table |
| 19 | |
| 20 | |
| 21 | def build_parser() -> argparse.ArgumentParser: |
| 22 | parser = argparse.ArgumentParser( |
| 23 | description=( |
| 24 | "Profile bounded local CSV/TSV rows with aggregate statistics. " |
| 25 | "Raw cell values and full paths are never emitted." |
| 26 | ) |
| 27 | ) |
| 28 | parser.add_argument("input", help="Local .csv or .tsv path inside --root") |
| 29 | parser.add_argument( |
| 30 | "--root", |
| 31 | default=".", |
| 32 | help="Existing local directory that bounds all input/output paths", |
| 33 | ) |
| 34 | parser.add_argument( |
| 35 | "--max-bytes", |
| 36 | type=int, |
| 37 | default=DEFAULT_MAX_FILE_BYTES, |
| 38 | help=f"Maximum input bytes (hard ceiling: {512 * 1024 * 1024})", |
| 39 | ) |
| 40 | parser.add_argument( |
| 41 | "--max-rows", |
| 42 | type=int, |
| 43 | default=DEFAULT_MAX_ROWS, |
| 44 | help="Maximum rows to scan (hard ceiling: 1000000)", |
| 45 | ) |
| 46 | parser.add_argument( |
| 47 | "--missing-token", |
| 48 | action="append", |
| 49 | help=( |
| 50 | "Explicit additional missing code; repeat as needed. Empty/whitespace " |
| 51 | "is always missing. Values such as NA are not assumed automatically." |
| 52 | ), |
| 53 | ) |
| 54 | parser.add_argument( |
| 55 | "--reveal-identifiers", |
| 56 | action="store_true", |
| 57 | help="Emit sanitized column identifiers; values remain redacted", |
| 58 | ) |
| 59 | parser.add_argument("--output", help="Optional local .json output path") |
| 60 | parser.add_argument( |
| 61 | "--force", |
| 62 | action="store_true", |
| 63 | help="Allow replacement of an existing regular output file", |
| 64 | ) |
| 65 | return parser |
| 66 | |
| 67 | |
| 68 | def _main() -> None: |
| 69 | args = build_parser().parse_args() |
| 70 | max_bytes = bounded_file_limit(args.max_bytes) |
| 71 | path = checked_input_file( |
| 72 | args.input, |
| 73 | root=args.root, |
| 74 | suffixes={".csv", ".tsv"}, |
| 75 | max_bytes=max_bytes, |
| 76 | ) |
| 77 | capability = capability_for_path(path) |
| 78 | if capability["tier"] != "automated_core": |
| 79 | raise CliError("the selected file has no core tabular capability") |
| 80 | report = { |
| 81 | "schema_version": "1.1", |
| 82 | "capability": capability, |
| 83 | "analysis": profile_table( |
| 84 | path, |
| 85 | max_rows=args.max_rows, |
| 86 | missing_tokens=args.missing_token, |
| 87 | reveal_identifiers=args.reveal_identifiers, |
| 88 | ), |
| 89 | "security": { |
| 90 | "local_only": True, |
| 91 | "file_text_is_untrusted_data": True, |
| 92 | "embedded_instructions_followed": False, |
| 93 | "raw_values_and_full_paths_emitted": False, |
| 94 | }, |
| 95 | } |
| 96 | emit_json( |
| 97 | report, |
| 98 | output=args.output, |
| 99 | root=args.root, |
| 100 | force=args.force, |
| 101 | ) |
| 102 | |
| 103 | |
| 104 | def main() -> int: |
| 105 | return run_cli(_main) |
| 106 | |
| 107 | |
| 108 | if __name__ == "__main__": |
| 109 | raise SystemExit(main()) |
文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。

