| import logging |
| from dataclasses import dataclass, field |
|
|
| from sqlalchemy.ext.asyncio import AsyncSession |
|
|
| from app.collectors.registry import ( |
| get_opportunity_collectors, |
| ) |
| from app.services.opportunity_sync import ( |
| OpportunitySyncResult, |
| OpportunitySyncService, |
| ) |
|
|
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| @dataclass(slots=True, frozen=True) |
| class OpportunitySyncFailure: |
| source_slug: str |
| error_type: str |
| message: str |
|
|
|
|
| @dataclass(slots=True) |
| class OpportunityBatchSyncResult: |
| results: list[OpportunitySyncResult] = field( |
| default_factory=list |
| ) |
|
|
| failures: list[OpportunitySyncFailure] = field( |
| default_factory=list |
| ) |
|
|
| @property |
| def total_sources(self) -> int: |
| return len(self.results) + len( |
| self.failures |
| ) |
|
|
| @property |
| def successful_sources(self) -> int: |
| return len(self.results) |
|
|
| @property |
| def failed_sources(self) -> int: |
| return len(self.failures) |
|
|
| @property |
| def collected(self) -> int: |
| return sum( |
| result.collected |
| for result in self.results |
| ) |
|
|
| @property |
| def created(self) -> int: |
| return sum( |
| result.created |
| for result in self.results |
| ) |
|
|
| @property |
| def updated(self) -> int: |
| return sum( |
| result.updated |
| for result in self.results |
| ) |
|
|
| @property |
| def failed_opportunities(self) -> int: |
| return sum( |
| result.failed |
| for result in self.results |
| ) |
|
|
| @property |
| def is_successful(self) -> bool: |
| return not self.failures |
|
|
|
|
| class OpportunityBatchSyncService: |
| def __init__( |
| self, |
| session: AsyncSession, |
| ) -> None: |
| self.session = session |
| self.sync_service = OpportunitySyncService( |
| session |
| ) |
|
|
| async def synchronize_all( |
| self, |
| ) -> OpportunityBatchSyncResult: |
| batch_result = ( |
| OpportunityBatchSyncResult() |
| ) |
|
|
| collectors = ( |
| get_opportunity_collectors() |
| ) |
|
|
| for collector in collectors: |
| source_slug = ( |
| collector.source_slug |
| ) |
|
|
| logger.info( |
| "Starting opportunity synchronization " |
| "for source '%s'.", |
| source_slug, |
| ) |
|
|
| try: |
| sync_result = ( |
| await self.sync_service |
| .synchronize(collector) |
| ) |
|
|
| except Exception as error: |
| failure = OpportunitySyncFailure( |
| source_slug=source_slug, |
| error_type=type(error).__name__, |
| message=str(error), |
| ) |
|
|
| batch_result.failures.append( |
| failure |
| ) |
|
|
| logger.exception( |
| "Opportunity synchronization " |
| "failed for source '%s'.", |
| source_slug, |
| ) |
|
|
| continue |
|
|
| batch_result.results.append( |
| sync_result |
| ) |
|
|
| logger.info( |
| ( |
| "Opportunity synchronization " |
| "completed for source '%s': " |
| "collected=%s created=%s " |
| "updated=%s failed=%s." |
| ), |
| source_slug, |
| sync_result.collected, |
| sync_result.created, |
| sync_result.updated, |
| sync_result.failed, |
| ) |
|
|
| logger.info( |
| ( |
| "Opportunity batch synchronization " |
| "completed: sources=%s successful=%s " |
| "failed=%s collected=%s created=%s " |
| "updated=%s." |
| ), |
| batch_result.total_sources, |
| batch_result.successful_sources, |
| batch_result.failed_sources, |
| batch_result.collected, |
| batch_result.created, |
| batch_result.updated, |
| ) |
|
|
| return batch_result |