#!/usr/bin/env python3 """ /UPI file processing scheduler. Runs polling loop every 30 minutes to process new files. """ import signal import time import sys from datetime import datetime from logging_config import get_logger, setup_logging from config import get_config from db import OracleConnector, Repository from sftp import SFTPClient, FileMonitor from processors import FileProcessor logger = get_logger(__name__) class Scheduler: """Main scheduler for UPI file processing.""" def __init__(self): self.config = get_config() self.config.validate() self.running = True self.cycle_count = 0 signal.signal(signal.SIGTERM, self._signal_handler) signal.signal(signal.SIGINT, self._signal_handler) def _signal_handler(self, signum, frame): logger.info(f"Received signal {signum}, shutting down gracefully...") self.running = False def initialize_database(self): try: connector = OracleConnector() if connector.test_connection(): logger.info("Database connection test passed") repository = Repository() return True else: logger.error("Database connection test failed") return False except SystemExit as e: logger.error(f"Database initialization failed: {e}") raise except Exception as e: logger.error(f"Error initializing database: {e}", exc_info=True) return False def run_processing_cycle(self): self.cycle_count += 1 logger.info(f"=== Starting processing cycle {self.cycle_count} ===") sftp_client = SFTPClient() repository = Repository() try: if not sftp_client.connect(): logger.error("Failed to connect to SFTP server") return monitor = FileMonitor(sftp_client) new_files = [] today_str = datetime.now().strftime("%d%m%Y") logger.info(f"listing file for {today_str}") for bank_code in self.config.bank_codes: bank_processed = repository.get_processed_files(bank_code) remote_path = f"{self.config.sftp_base_path}/{bank_code}/UPI" pattern = f"*_{today_str}_*_UPI.txt.gz" files = sftp_client.list_files(remote_path, pattern=pattern) for filename in files: if filename not in bank_processed: full_path = f"{remote_path}/{filename}" new_files.append((filename, bank_code, full_path)) logger.info(f"Found new file: {filename} (bank: {bank_code})") else: logger.debug(f"Skipping already processed file for {bank_code}: {filename}") if not new_files: logger.info("No new files to process") return logger.info(f"Found {len(new_files)} new files to process") processor = FileProcessor(repository, sftp_client) stats = processor.process_files(new_files) logger.info(f"Cycle {self.cycle_count} complete:") logger.info(f" Total files: {stats['total']}") logger.info(f" Successful: {stats['successful']}") logger.info(f" Failed: {stats['failed']}") # FIXED INDENTATION BLOCK if stats['successful'] > 0: logger.info("Calling upi_api_txn_post procedure for all inserted transactions...") if repository.call_upi_api_txn_post(): logger.info("Transaction post-processing completed successfully") else: logger.error("Transaction post-processing failed") except Exception as e: logger.error(f"Error in processing cycle: {e}", exc_info=True) finally: sftp_client.disconnect() def run(self): logger.info("=" * 80) logger.info("UPI_INWARD File Processing Scheduler Started") logger.info(f"Poll Interval: {self.config.poll_interval_minutes} minutes") logger.info(f"Bank Codes: {', '.join(self.config.bank_codes)}") logger.info("=" * 80) try: if not self.initialize_database(): logger.error("Failed to initialize database. Exiting.") return except SystemExit as e: logger.error(f"Fatal error: {e}") raise poll_interval_seconds = self.config.poll_interval_minutes * 60 while self.running: try: self.run_processing_cycle() except Exception as e: logger.error(f"Unexpected error in processing cycle: {e}", exc_info=True) if self.running: logger.info(f"Waiting {self.config.poll_interval_minutes} minutes until next cycle...") time.sleep(poll_interval_seconds) logger.info("Scheduler shutdown complete") def main(): setup_logging() scheduler = Scheduler() scheduler.run() if __name__ == '__main__': main()