waste_classifier_robiatech / app /services /opportunity_batch_sync.py
angeetoile's picture
feat(opportunities): add protected batch synchronization endpoint
f55a6b5
Raw
History Blame Contribute Delete
4.13 kB
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