import json import logging import os import tempfile from datetime import datetime from typing import Optional, TextIO from garminconnect import Garmin from tenacity import ( retry, retry_if_exception_type, stop_after_attempt, wait_exponential, ) from ..models.central_db_models import GarminCredentials # Configure debug logging for garminconnect logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) # Define a retry strategy for Garmin login GARMIN_LOGIN_RETRY_STRATEGY = retry( stop=stop_after_attempt(10), # Increased attempts wait=wait_exponential( multiplier=1, min=10, max=60 ), # Increased min and max wait times retry=retry_if_exception_type(Exception), # Retry on any exception for now reraise=True, ) class GarminAuthService: def __init__(self): pass @GARMIN_LOGIN_RETRY_STRATEGY # Apply retry strategy here async def _perform_login(self, username: str, password: str) -> Garmin: """Helper to perform the actual garminconnect login with retry.""" client = Garmin(username, password) client.login() return client async def initial_login( self, username: str, password: str ) -> Optional[GarminCredentials]: """Performs initial login to Garmin Connect and returns GarminCredentials.""" try: garmin_client = await self._perform_login( username, password ) # Use the retried login helper if not garmin_client: return None logger.info(f"Successful Garmin login for {username}") with tempfile.TemporaryDirectory() as temp_dir: session_file = os.path.join(temp_dir, "garth_session.json") garmin_client.garth.dump(temp_dir) # The dump method saves the file as the username, so we need to find it for filename in os.listdir(temp_dir): if filename.endswith(".json"): session_file = os.path.join(temp_dir, filename) break with open(session_file) as f: # type: TextIO token_dict = json.load(f) # type: ignore # Extract tokens and cookies access_token = token_dict.get("access_token", "") access_token_secret = token_dict.get("access_token_secret", "") token_expiration_date = datetime.fromtimestamp( token_dict.get("token_expiration_date", 0) ) garmin_credentials = GarminCredentials( garmin_username=username, garmin_password_plaintext=password, # Storing plaintext for re-auth, consider encryption access_token=access_token, access_token_secret=access_token_secret, token_expiration_date=token_expiration_date, display_name=garmin_client.display_name, full_name=garmin_client.full_name, unit_system=garmin_client.unit_system, token_dict=token_dict, ) return garmin_credentials except Exception as e: logger.error(f"Garmin initial login failed for {username}: {e}") return None