Files
redmine/redmine_crm/sync.py
T
Jason Thistlethwaite 9ee9eb3ec6 Organized
2026-10-17 20:50:35 -04:00

66 lines
1.9 KiB
Python

"""High-level Redmine -> CRM sync orchestrator."""
from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from redmine_crm.crm_client import CrmClient
from redmine_crm.mapper import enrich, map_contact
from redmine_crm.redmine_client import RedmineClient
logger = logging.getLogger(__name__)
class SyncState:
"""Minimal sync state tracker."""
def __init__(self, storage: Optional[Dict[str, Any]] = None) -> None:
self._data = storage if storage is not None else {}
def get_last_sync(self) -> Optional[str]:
return self._data.get("last_sync_timestamp")
def update(self, timestamp: str, stats: Dict[str, Any]) -> None:
self._data["last_sync_timestamp"] = timestamp
self._data["last_run_stats"] = stats
def run_sync(
redmine: RedmineClient,
crm: CrmClient,
sync_state: SyncState,
*,
base_url: str,
batch_size: int = 100,
) -> Dict[str, Any]:
"""Run the full ETL pipeline: Extract, Transform, Load."""
last_sync = sync_state.get_last_sync()
logger.info("Starting Redmine -> CRM sync; last_sync=%s", last_sync)
# Extract
contacts = redmine.iter_all_contacts(
updated_since=last_sync,
batch_size=batch_size,
)
logger.info("Extracted %d Redmine contacts", len(contacts))
# Transform
records: List[Dict[str, Any]] = []
for contact in contacts:
try:
mapped = map_contact(contact, base_url=base_url)
enriched, _enrich_meta = enrich(mapped, contact)
records.append(enriched)
except Exception as exc:
logger.exception("Failed to map Redmine contact %s: %s", contact.get("id"), exc)
# Load
stats = crm.sync_batch(records)
logger.info("CRM upsert stats: %s", stats)
import datetime
sync_state.update(datetime.datetime.now(datetime.timezone.utc).isoformat(), stats)
return stats