"""Application entry point — dependency wiring and processor invocation. This module is framework-agnostic: it can be called from the CLI, a Lambda handler, or a test. All infrastructure concerns (argument parsing, logging configuration, process lifecycle) belong in the caller. """ import logging from config import Config, RunOptions from connectors.abacus import AbacusClient from connectors.graphql import GraphQLClient from domain import ContractInputBuilder, ContractProcessor from infra.output import CsvOutputSink, NullOutputSink from infra.readers import CsvFileReader, JsonFileReader, XlsxFileReader from infra.row_reader import RowReader from infra.throttler import Throttler, TokenBucketStrategy logger = logging.getLogger(__name__) GRAPHQL_CLIENT_NAME = 'contracts' GRAPHQL_CLIENT_VERSION = '1.0.0' GRAPHQL_PROFILE_TYPE = 'AbacusProfile' GRAPHQL_ROLE = 'administrator' def run( config: Config, input_file: str, options: RunOptions | None = None, ): """Create client and processor, run the batch. Args: config: Validated configuration. input_file: Path to input file (CSV, JSON, or XLSX). options: Resolved runtime options. If None, defaults are used. Raises FileNotFoundError if the input file doesn't exist. Raises ApiError / GraphQLError on API failures. """ opts = options or RunOptions() throttler = Throttler( TokenBucketStrategy( capacity=opts.throttle_capacity, refill_rate=opts.throttle_rate, ) ) abacus = AbacusClient( GraphQLClient( url=config.graphql_url, headers={ 'Authorization': f'Bearer {config.bearer_token}', 'apollographql-client-name': GRAPHQL_CLIENT_NAME, 'apollographql-client-version': GRAPHQL_CLIENT_VERSION, 'orchard-profile-type': GRAPHQL_PROFILE_TYPE, 'orchard-roles': GRAPHQL_ROLE, 'orchard-profile-uuid': config.profile_uuid, }, throttler=throttler, ) ) signing_entities = abacus.get_reference_signing_entities() if not signing_entities: raise RuntimeError( 'No signing entities returned from GraphQL — ' 'cannot proceed without entity data' ) logger.info(f'Loaded {len(signing_entities)} signing entities') run_controllers = abacus.get_run_controllers() logger.info(f'Loaded {len(run_controllers)} run controllers') output_sink = ( CsvOutputSink(success_csv=opts.output) if opts.output else NullOutputSink() ) processor = ContractProcessor( abacus=abacus, contract_builder=ContractInputBuilder( signing_entities=signing_entities, run_controllers=run_controllers, ), row_reader=RowReader( readers={ '.csv': CsvFileReader(), '.json': JsonFileReader(), '.xlsx': XlsxFileReader(), } ), dry_run=opts.dry_run, output_sink=output_sink, ) return processor.process( input_file=input_file, limit=opts.limit, policy=opts.policy, resume_csv=opts.resume, )