"""Lambda Handler.""" import time from dataclasses import asdict from typing import Any import sentry_sdk from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration from config import config from sync_contract_sap import constants from sync_contract_sap.db import mysql_connection from sync_contract_sap.processor import SyncContractSAPProcessor from sync_contract_sap.repository import Repository from sync_contract_sap.schemas import SapSyncContractEvent if config.sentry_dsn: sentry_sdk.init( dsn=config.sentry_dsn, environment=config.env, integrations=[AwsLambdaIntegration(timeout_warning=True)], ) def handler(event: dict[str, Any], context: Any) -> dict[str, Any]: """Lambda entry point. Routes to single-contract mode when the event contains a target_id (event-driven), or to batch mode for manual and scheduled invocations. """ try: if event and 'target_id' in event: return _handle_single(SapSyncContractEvent(**event)) else: return _handle_batch(context) except Exception as e: print(e) raise e def _handle_single(event: SapSyncContractEvent) -> dict[str, Any]: """Process a single contract from a validated event.""" processor = SyncContractSAPProcessor() processor.process(event.target_id) return {'status': 'OK'} def _handle_batch(context: Any) -> dict[str, Any]: """Discover and process contracts pending SAP sync.""" # Get contracts to sync. Use and release # DB connection before long-running SAP processing. with mysql_connection(**asdict(config.mysql)) as conn: repo = Repository(conn) rows = repo.get_contracts_pending_sap_sync( config.batch.MAX_BATCH_SIZE, config.batch.STALE_SYNC_MINUTES ) processor = SyncContractSAPProcessor() return processor.process_batch(rows, _batch_deadline(context)).model_dump() def _batch_deadline(context: Any) -> float | None: """Monotonic timestamp after which the batch should stop processing. The Lambda deadline is fixed at invocation start, so it is computed once here rather than polled per contract. """ if context is None: return None remaining_ms: int = context.get_remaining_time_in_millis() return time.monotonic() + ( (remaining_ms - constants.BATCH_REMAINING_TIME_FLOOR_MS) / 1000 )