import logging import uuid from typing import Any, Dict, Optional from tenacity import ( retry, retry_if_exception_type, stop_after_attempt, wait_exponential, ) from ..services.garmin_client_service import GarminClientService logger = logging.getLogger(__name__) # Define a common retry strategy for Garmin API calls GARMIN_RETRY_STRATEGY = retry( stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=4, max=10), retry=retry_if_exception_type(Exception), # Broad exception for now, refine later reraise=True, ) class GarminWorkoutService: def __init__(self, garmin_client_service: GarminClientService): self.garmin_client_service = garmin_client_service @GARMIN_RETRY_STRATEGY async def upload_workout(self, workout_id: uuid.UUID) -> Optional[Dict[str, Any]]: try: # Get workout from CentralDB from ..config import settings from .central_db_service import CentralDBService central_db = CentralDBService(base_url=settings.CENTRAL_DB_URL) workout = await central_db.get_workout_by_id( workout_id ) # Assuming this method exists if not workout: logger.error(f"Workout with ID {workout_id} not found in CentralDB.") return None garmin_client = self.garmin_client_service.get_client() if not garmin_client: raise Exception("Garmin client not authenticated.") logger.info( f"Simulating upload of workout {workout.name} (ID: {workout_id}) " "to Garmin Connect." ) garmin_workout_id = f"GARMIN_WORKOUT_{workout_id}" # Mock ID # Here we would update the workout in CentralDB with the garmin_workout_id # await central_db.update_workout( # workout_id, {"garmin_workout_id": garmin_workout_id, "upload_status": "completed"} # ) logger.info( f"Successfully uploaded workout {workout.name} to Garmin Connect " f"with ID {garmin_workout_id}." ) # We need to add garmin_workout_id to the workout dictionary before returning workout_dict = workout.dict() workout_dict["garmin_workout_id"] = garmin_workout_id return workout_dict except Exception as e: logger.error(f"Error uploading workout {workout_id}: {e}", exc_info=True) # We could update the workout in CentralDB to set the status to "failed" # await central_db.update_workout(workout_id, {"upload_status": "failed"}) return None async def upload_workout_in_background( self, user_id: int, current_sync_job_manager, workout_id: uuid.UUID, ): try: uploaded_workout = await self.upload_workout(workout_id) if uploaded_workout: await current_sync_job_manager.complete_sync() else: await current_sync_job_manager.fail_sync( error_message=f"Failed to upload workout {workout_id}" ) except Exception as e: logger.error(f"Error during workout upload: {e}", exc_info=True) await current_sync_job_manager.fail_sync(error_message=str(e))