#!/usr/bin/env python3 """CLI entry point for contract creation.""" import argparse import logging import sys import app from config import ConfigError, RunOptions, load_config from connectors import ApiError, GraphQLError from infra import MissingColumnsError logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) def _setup_argument_parser() -> argparse.ArgumentParser: """Configure and return the argument parser.""" parser = argparse.ArgumentParser( description='Create contracts from input files using the Abacus GraphQL API' ) parser.add_argument( '--input', required=True, help='Path to input file (CSV, JSON, or XLSX)' ) parser.add_argument( '--config', required=True, help='Path to JSON config file with bearer token and mappings', ) parser.add_argument( '--execute', action='store_true', help='Execute real API calls (default is dry-run)', ) parser.add_argument('--limit', type=int, help='Limit number of rows to process') parser.add_argument( '--throttle-capacity', type=int, default=None, help='Token bucket capacity / burst size (default: 1)', ) parser.add_argument( '--throttle-rate', type=float, default=None, help='Token bucket refill rate in requests/sec (default: 2.0)', ) parser.add_argument( '--skip-if-missing', action=argparse.BooleanOptionalAction, default=None, help='Skip rows with missing required fields instead of using defaults', ) parser.add_argument( '--output', help='Output CSV file for results (success + failures)' ) parser.add_argument( '--resume', help='Path to previous output CSV to resume from ' '(skips already-processed rows)', ) return parser def _print_summary(results) -> None: """Print summary of contract creation results.""" success = len(results.success) errors = len(results.errors) skipped = len(results.skipped) total = success + errors + skipped logger.info('\n' + '=' * 60) logger.info('SUMMARY') logger.info('=' * 60) logger.info(f'Total rows processed: {total}') logger.info(f'Successful: {success}') logger.info(f'Errors: {errors}') logger.info(f'Skipped: {skipped}') logger.info('=' * 60) def main(): args = _setup_argument_parser().parse_args() try: config = load_config(args.config) options = RunOptions.resolve( config, dry_run=not args.execute, limit=args.limit, skip_if_missing=args.skip_if_missing, throttle_capacity=args.throttle_capacity, throttle_rate=args.throttle_rate, output=args.output, resume=args.resume, ) results = app.run( config=config, input_file=args.input, options=options, ) except ConfigError as e: logger.error(str(e)) sys.exit(1) except FileNotFoundError as e: logger.error(str(e)) sys.exit(1) except MissingColumnsError as e: logger.error(str(e)) sys.exit(1) except (ApiError, GraphQLError) as e: logger.error(f'\n[API ERROR] {e}') sys.exit(1) except KeyboardInterrupt: logger.warning('\n[INTERRUPTED] Interrupted by user.') sys.exit(130) _print_summary(results) if __name__ == '__main__': main()