
    ie                       d Z ddlZddlZddlZddlZddlmZ ddlmZmZm	Z	m
Z
 ddlmZmZ ddlmZ ddlmZ ddlZddlZddlmZmZ dd	lmZ d
dlmZ  ej        e          ZdZdZdZdZ ddhZ!d)de"de"de"fdZ#	 d*de$e%z  de"de$e%z  fdZ&d*de$de"de$fdZ'd*de$de"de$fdZ(dede"fdZ)dej*        de+e"ef         dz  fdZ, G d d           Z- G d! d"e.          Z/ G d# d$e.          Z0 G d% d&e.          Z1 G d' d(e.          Z2dS )+z(Python 3 API wrapper for Garmin Connect.    N)Callable)datedatetime	timedeltatimezone)Enumauto)Path)Any)GarthExceptionGarthHTTPError)	HTTPError   )FitEncoderWeight  i'  z^\d{4}-\d{2}-\d{2}$z%Y-%m-%dkglbsr   date_str
param_namereturnc                 `   t          | t                    st          | d          |                                 } t	          j        t          |           st          | d|            	 t          j        | t                     n(# t          $ r}t          d| d|           |d}~ww xY w| S )z'Validate date string format YYYY-MM-DD.z must be a stringz& must be in format 'YYYY-MM-DD', got: zinvalid : N)

isinstancestr
ValueErrorstripre	fullmatchDATE_FORMAT_REGEXr   strptimeDATE_FORMAT_STR)r   r   es      Q/root/projects/butler/venv/lib/python3.11/site-packages/garminconnect/__init__.py_validate_date_formatr$      s    h$$ ;J999::: ~~H<)844 
KKKK
 
 	
>(O4444 > > >5J55!5566A=> Os   +B 
B+B&&B+valuec                     t          | t          j                  st          | d          t          | t                    rt          | d          | dk    rt          | d|            | S )z#Validate that a number is positive.z must be a numberz must be a number, not boolr   z must be positive, got: )r   numbersRealr   boolr%   r   s     r#   _validate_positive_numberr+   5   s     eW\** ;J999:::% EJCCCDDDzzJGGGGHHHL    c                     t          | t                    rt          | t                    rt          | d          | dk     rt          | d|            | S )z0Validate that a value is a non-negative integer. must be an integerr   z must be non-negative, got: r   intr)   r   r*   s     r#   _validate_non_negative_integerr1   E   sf    eS!! =Zt%<%< =J;;;<<<qyyJKKEKKLLLLr,   c                     t          | t                    rt          | t                    rt          | d          | dk    rt          | d|            | S )z,Validate that a value is a positive integer.r.   r   z" must be a positive integer, got: r/   r*   s     r#   _validate_positive_integerr3   P   sf    eS!! =Zt%<%< =J;;;<<<zzJQQ%QQRRRLr,   dtc                 d    |                      d                               d          d d         S )Ntzinfoz%Y-%m-%dT%H:%M:%S.%f)replacestrftime)r4   s    r#   _fmt_tsr;   Y   s.    ::T:""++,BCCCRCHHr,   responsec                 D    | j         dk    rd S |                                 S )N   )status_codejson)r<   s    r#   _validate_json_existsrA   ^   s#    s""t==??r,   c                   l   e Zd ZdZ	 	 	 	 	 ddedz  dedz  dedeg ef         dz  ded	dfd
Zdeded	efdZ	deded	efdZ
ddedz  d	eedz  edz  f         fdZdeeef         ded	eeef         fdZd	edz  fdZd	edz  fdZded	eeef         fdZded	eeef         fdZded	eeeef                  fdZded	eeef         fdZdeded	eeeef                  fdZddeded	eeeef                  fd Zddeded	eeeef                  fd!Zdeded	eeeef                  fd"Zded	eeef         fd#Zded	eeef         fd$Z	 dd%ed&edz  d	eeef         fd'Z	 	 	 	 	 	 	 	 	 	 	 dd(edz  d)ed*edz  d+edz  d,edz  d-edz  d.edz  d/edz  d0edz  d1edz  d2edz  d3edz  d4edz  d	eeef         fd5Z	 dd)eez  d8ed(ed	eeef         dz  fd9Z 	 	 	 dd)eez  d8ed:ed;ed	eeef         dz  f
d<Z!d%ed&ed	eeef         fd=Z"ded	eeef         fd>Z#d?eded	efd@Z$ddedAed	edz  fdBZ%	 dd%ed&edz  d	eeeef                  fdCZ&ded	eeeef                  fdDZ'	 	 ddEedFedGed(edHed	eeef         fdIZ(	 dd%ed&edz  d	eeef         fdJZ)dKeded	eeef         fdLZ*ded	eeef         fdMZ+dNdddOdPdQedRee,z  dz  dSee,z  dz  dTed	eeef         f
dUZ-	 	 ddVed(edz  dedz  d	eeef         fdWZ.ded	eeef         fdXZ/ded	eeef         fdYZ0ded	eeef         fdZZ1ded	eeef         fd[Z2ded	eeef         fd\Z3ded	eeef         fd]Z4d	eeef         fd^Z5d	eeeef                  fd_Z6d	eeeef                  fd`Z7d	eeeef                  fdaZ8dedbed	eeef         fdcZ9dedbed	eeef         fddZ:dedbed	eeef         fdeZ;dedbed	eeef         fdfZ<dedbed	eeef         fdgZ=ded	eeef         fdhZ>ded	eeef         fdiZ?ded	eeef         fdjZ@ded	eeef         fdkZAded	eeef         dz  fdlZBded	eeef         fdmZCded	eeef         dz  fdnZD	 dd%ed&edz  d	eeef         fdoZE	 	 	 dd%edz  d&edz  dpedz  d	eeef         fdqZFded	eeef         fdrZGded	eeef         fdsZH	 dd%ed&edz  d	eeef         fdtZId	eeeef                  fduZJdved	eeef         fdwZKd	eeef         fdxZL	 ddved%ed&edz  d	eeeef                  fdyZMd	ee         fdzZNd	eeef         fd{ZOd	efd|ZP	 	 	 ddedbededz  d	eeef         ee         z  fdZQded	eeef         fdZRdeded	efdZSdedededed	ef
dZTdeeef         d	efdZUdedededededed	efdZVd	eeef         dz  fdZWded	efdZXded	efdZY	 	 	 dd%ed&edz  dedz  dedz  d	eeeef                  f
dZZ	 	 dd%ed&ededed	eeef         f
dZ[d	eeef         fdZ\	 ddededbed	eeeef                  fdZ]ded	eeef         fdZ^ded	eeef         fdZ_ded	eeef         fdZ`	 ddededed	efdZa G d deb          Zc G d deb          Zdecje        fdedecd	effdZgded	eeef         fdZhded	eeef         fdZided	eeef         fdZjded	eeef         fdZkded	eeef         fdZlded	eeef         fdZmd	eeef         eeeef                  z  fdZnded	eeef         fdZo	 ddededed	eeef         fdZpdeez  d	eeef         fdZqdeez  d	eeef         fdZr	 ddedbed	eeeef                  fdZsdedeez  d	eeef         fdZtdedeez  d	eeef         fdZud	eeef         fdZvd	eeef         fdÄZwded	eeef         fdĄZxddedbed	eeef         fdƄZydeez  d	eeef         fdȄZzdeez  d	effdɄZ{deeef         ee         z  ez  d	eeef         fd˄Z|ded	eeef         fd̈́Z}ded	eeef         fd΄Z~ded	eeef         fdτZded	eeef         fdЄZded	eeef         fdфZdeez  d	eeef         fdӄZded	eeef         fdԄZd%ed&ed	eeef         fdՄZd	eeef         fdքZdeeef         d	eeef         fd؄ZddلZd	eeef         fdڄZdeez  d	eeef         fd܄Zdeez  d	eeef         fd݄ZdS )Garminz,Class for fetching data from Garmin Connect.NFemailpasswordis_cn
prompt_mfareturn_on_mfar   c                    |$t          |t                    st          d          |$t          |t                    st          d          t          |t                    st          d          t          |t                    st          d          || _        || _        || _        || _        || _        d| _	        d| _
        d| _        d	| _        d
| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _         d| _!        d| _"        d | _#        d!| _$        d"| _%        d#| _&        d$| _'        d%| _(        d&| _)        d'| _*        d(| _+        d)| _,        d*| _-        d+| _.        d,| _/        d-| _0        d.| _1        d/| _2        d0| _3        d1| _4        d2| _5        d3| _6        d4| _7        d5| _8        d6| _9        d7| _:        d8| _;        d9| _<        d:| _=        d;| _>        d<| _?        d=| _@        d>| _A        d?| _B        d@| _C        dA| _D        dB| _E        dC| _F        dD| _G        dE| _H        dF| _I        dG| _J        dH| _K        | jK         dI| _L        d:| _M        dJ| _N        dK| _O        dL| _P        t          jR        |rdMndNdOdOP          | _Q        d| _S        d| _T        d| _U        dS )QzCreate a new class instance.Nzemail must be a string or Nonez!password must be a string or Nonezis_cn must be a booleanzreturn_on_mfa must be a booleanz./userprofile-service/userprofile/user-settingsz)/userprofile-service/userprofile/settingsz*/device-service/deviceregistration/devicesz/device-service/deviceservicez0/web-gateway/device-info/primary-training-devicez/web-gateway/solarz/weight-servicez&/usersummary-service/usersummary/dailyz%/metrics-service/metrics/maxmet/dailyz/biometric-service/biometricz/biometric-service/statsz0/usersummary-service/usersummary/hydration/dailyz./usersummary-service/usersummary/hydration/logz&/usersummary-service/stats/steps/dailyz'/usersummary-service/stats/steps/weeklyz(/usersummary-service/stats/stress/weeklyz$/usersummary-service/stats/im/weeklyz*/personalrecord-service/personalrecord/prsz/badge-service/badge/earnedz/badge-service/badge/availablez1/adhocchallenge-service/adHocChallenge/historicalz0/badgechallenge-service/badgeChallenge/completedz0/badgechallenge-service/badgeChallenge/availablez4/badgechallenge-service/badgeChallenge/non-completedz3/badgechallenge-service/virtualChallenge/inProgressz)/wellness-service/wellness/dailySleepDataz&/wellness-service/wellness/dailyStressz"/metrics-service/metrics/hillscorez4/wellness-service/wellness/bodyBattery/reports/dailyz-/wellness-service/wellness/bodyBattery/eventsz*/bloodpressure-service/bloodpressure/rangez$/bloodpressure-service/bloodpressurez'/metrics-service/metrics/endurancescorez//periodichealth-service/menstrualcycle/calendarz./periodichealth-service/menstrualcycle/dayviewz8/periodichealth-service/menstrualcycle/pregnancysnapshotz/goal-service/goal/goalsz!/userstats-service/wellness/dailyz/hrv-service/hrvz*/metrics-service/metrics/trainingreadinessz(/metrics-service/metrics/racepredictionsz2/metrics-service/metrics/trainingstatus/aggregatedz,/wellness-service/wellness/dailySummaryChartz0/wellness-service/wellness/floorsChartData/dailyz)/wellness-service/wellness/dailyHeartRatez,/wellness-service/wellness/daily/respirationz%/wellness-service/wellness/daily/spo2z#/wellness-service/wellness/daily/imz&/wellness-service/wellness/dailyEventsz2/activitylist-service/activities/search/activitiesz&/activitylist-service/activities/countz!/activitylist-service/activities/z/activity-service/activityz(/activity-service/activity/activityTypesz!/mobile-gateway/heartRate/forDatez/fitnessstats-service/activityz/fitnessage-service/fitnessagez /download-service/files/activityz%/download-service/export/tcx/activityz%/download-service/export/gpx/activityz%/download-service/export/kml/activityz%/download-service/export/csv/activityz/upload-service/uploadz/gear-service/gear/filterGearz/gear-service/gearz(/wellness-service/wellness/epoch/requestz/workout-servicez	/schedulezgraphql-gateway/graphqlz"/trainingplan-service/trainingplanz"/lifestylelogging-service/dailyLogz	garmin.cnz
garmin.com   )domainpool_connectionspool_maxsize)Vr   r   r   r)   usernamerE   rF   rG   rH    garmin_connect_user_settings_url'garmin_connect_userprofile_settings_urlgarmin_connect_devices_urlgarmin_connect_device_url!garmin_connect_primary_device_urlgarmin_connect_solar_urlgarmin_connect_weight_url garmin_connect_daily_summary_urlgarmin_connect_metrics_urlgarmin_connect_biometric_url"garmin_connect_biometric_stats_url"garmin_connect_daily_hydration_url garmin_connect_set_hydration_url$garmin_connect_daily_stats_steps_url%garmin_connect_weekly_stats_steps_url&garmin_connect_weekly_stats_stress_url1garmin_connect_weekly_stats_intensity_minutes_url"garmin_connect_personal_record_url garmin_connect_earned_badges_url#garmin_connect_available_badges_url#garmin_connect_adhoc_challenges_url#garmin_connect_badge_challenges_url-garmin_connect_available_badge_challenges_url1garmin_connect_non_completed_badge_challenges_url0garmin_connect_inprogress_virtual_challenges_urlgarmin_connect_daily_sleep_urlgarmin_connect_daily_stress_urlgarmin_connect_hill_score_url%garmin_connect_daily_body_battery_url&garmin_connect_body_battery_events_url&garmin_connect_blood_pressure_endpoint*garmin_connect_set_blood_pressure_endpoint"garmin_connect_endurance_score_url%garmin_connect_menstrual_calendar_url$garmin_connect_menstrual_dayview_url%garmin_connect_pregnancy_snapshot_urlgarmin_connect_goals_urlgarmin_connect_rhr_urlgarmin_connect_hrv_url%garmin_connect_training_readiness_url!garmin_connect_race_predictor_url"garmin_connect_training_status_url!garmin_connect_user_summary_chart%garmin_connect_floors_chart_daily_url#garmin_connect_heartrates_daily_url$garmin_connect_daily_respiration_urlgarmin_connect_daily_spo2_url&garmin_connect_daily_intensity_minutesgarmin_daily_events_urlgarmin_connect_activitiesgarmin_connect_activities_count!garmin_connect_activities_baseurlgarmin_connect_activitygarmin_connect_activity_typesgarmin_connect_activity_fordategarmin_connect_fitnessstatsgarmin_connect_fitnessagegarmin_connect_fit_downloadgarmin_connect_tcx_downloadgarmin_connect_gpx_downloadgarmin_connect_kml_downloadgarmin_connect_csv_downloadgarmin_connect_uploadgarmin_connect_geargarmin_connect_gear_baseurlgarmin_request_reload_urlgarmin_workoutsgarmin_workouts_schedule_url"garmin_connect_delete_activity_urlgarmin_graphql_endpoint garmin_connect_training_plan_url*garmin_connect_daily_lifestyle_logging_urlgarthClientdisplay_name	full_nameunit_system)selfrD   rE   rF   rG   rH   s         r#   __init__zGarmin.__init__g   s    Zs%;%;=>>>
8S(A(A@AAA%&& 	86777-.. 	@>??? 
$* = 	- 8 	4 +W')H& ? 	. )=%):&0X-*Q',J)2L/> 	/ = 	- 5 	1 6 	2 7 	3 3 	> 9 	/ 1N-3S0? 	0 ? 	0 ? 	: C 	> B 	= 8 	+ 0X,-Q* C 	2
 < 	3
 9 	3
 3 	7
 6 	/ > 	2
 = 	1 G 	2 )C%&I#&8# 9 	2
 7 	. A 	/ ; 	. ? 	2 8 	0 ; 	1 .U*1 	3 (P$@ 	& 0X,1T.'C$-W*/R,+K()I&+M(+R(+R(+R(+R(%="#B +?()S&1/3/C,N,N,N)2N/'@$0T- 1 	7 \"'9;;\
 
 

 !r,   pathkwargsc                 l   	  | j         j        |fi |S # t          $ r`}t          |                                          }d|v r5d|v sd|v r-t
                              d           t          d|           | d}~wt          t          f$ r}t          |t                    r&t          t          |j        dd          dd          }n t          t          |dd          dd          }t
                              d	|||           |d
k    rt          d|           ||dk    rt          d|           ||r&d|cxk    rdk     rn nt          d| d|           |t          d|           |d}~wt          $ r3}t
                              d|           t          d|           |d}~ww xY w)z1Wrapper for garth connectapi with error handling.oauthoauth1oauth2z+OAuth token refresh failed during API call.z>Token refresh failed. Please re-authenticate. Original error: Nr<   r?   z-API call failed for path '%s': %s (status=%s)  Authentication failed:   Rate limit exceeded:     zAPI client error (): zHTTP error: z*Connection error during connectapi path=%szConnection error: )r   
connectapiAssertionErrorr   lowerlogger	exception GarminConnectAuthenticationErrorr   r   r   getattrerror!GarminConnectTooManyRequestsErrorGarminConnectConnectionError	Exception)r   r   r   r"   	error_msgstatuss         r#   r   zGarmin.connectapi.  sY   +	P(4:(88888 	 	 	 AI)##I%%Y)>)>  !NOOO6XUVXX  >* 	J 	J 	J!^,, T AGZ66t  !J!=!=}dSS?q&   }}61a11  }}7/A//   #----#-----2777A77  //Aa/A/ABBI 	P 	P 	PI4PPP./GA/G/GHHaO	Ps.    
F3AA::F3C%E33F3 .F..F3c                    	  | j         j        |fi |S # t          t          f$ r}t	          |t                    r&t          t          |j        dd          dd          }n t          t          |dd          dd          }t                              d||           |dk    rt          d|           ||dk    rt          d|           ||r&d|cxk    rd	k     rn nt          d
| d|           |t          d|           |d}~wt          $ r3}t                              d|           t          d|           |d}~ww xY w)z/Wrapper for garth download with error handling.r<   Nr?   z)Download failed for path '%s' (status=%s)r   zDownload error: r   r   r   zDownload client error (r   zDownload failed for path '%s')r   downloadr   r   r   r   r   r   r   r   r   r   r   )r   r   r   r"   r   s        r#   r   zGarmin.download]  s   	N&4:&t66v666>* 	N 	N 	N!^,, T AGZ66t  !J!=!=}dSSH$PVWWW}}67M!7M7MNNTUU}}78N18N8NOOUVV #----#-----2<f<<<<  //E!/E/EFFAM 	N 	N 	N<dCCC./E!/E/EFFAM	Ns!    E
C$D

E
.EE

tokenstorec                
   |pt          j        d          }	 d}d}d}|r.	 t          |          dk    r| j                            |           nyt          |                                                                          }t          |          }t          
                    d|            | j                            |           d}n# t          $ rv}t          |                                          }d|v rFd|v sd	|v r>t                              d
           | j        r| j        st#          d|           |d}n Y d}~nd}~ww xY w|s| j        r| j        st#          d          | j        r3| j                            | j        | j        | j                  \  }}||fS | j                            | j        | j        | j                  \  }}t+          | j        dd          s	 | j                            d          }	n"# t.          $ r}t#          d          |d}~ww xY w|	rt1          |	t2                    rd|	vrt#          d          |	                    d          | _        |	                    d          | _        nU| j        j        }
t1          |
t2                    r4|
                    d          | _        |
                    d          | _        | j                            | j                  }|st#          d          t1          |t2                    rd|vrt#          d          |d                             d          | _        ||fS # t@          tB          j"        j         tF          f$ r}t+          t+          |dd          dd          }t          $                    d||           |dk    rt#          d|           ||dk    rtK          d|           |t          |                                          g d }tM          fd!|D                       rt#          d|           |tO          d"|           |d}~wtP          $ r  t.          $ r}t1          |t"                    r t          |                                          g d#}tM          fd$|D                       }|rt#          d|           |t          $                    d%           tO          d"|           |d}~ww xY w)&zLog in using Garth.

        Returns:
            Tuple[str | None, str | None]: (access_token, refresh_token) when using credential flow;
            (None, None) when loading from tokenstore.

        GARMINTOKENSNFi   z%Loading tokens from normalized path: Tr   r   r   zToken refresh failed (OAuth token mismatch). This may occur on Windows due to path or token file issues. Re-authentication required.z^Stored tokens are invalid and credentials are required for re-authentication. Original error: z"Username and password are required)rH   )rG   profilez(/userprofile-service/userprofile/profilezFailed to retrieve profiledisplayNamezInvalid profile data foundfullNamez Failed to retrieve user settingsuserDatazInvalid user settings foundmeasurementSystemr<   r?   zLogin failed: %s (status=%s)r   r   r   r   )401unauthorizedzauthentication failedc              3       K   | ]}|v V  	d S N .0	indicator	error_strs     r#   	<genexpr>zGarmin.login.<locals>.<genexpr>  s(      KKi9	)KKKKKKr,   zLogin failed: )r   r   authenticationzlogin failedc              3       K   | ]}|v V  	d S r   r   r   s     r#   r   zGarmin.login.<locals>.<genexpr>  s(      XX9	Y 6XXXXXXr,   zLogin failed))osgetenvlenr   loadsr
   
expanduserresolver   r   debugloadr   r   warningrN   rE   r   rH   loginrG   r   r   r   r   dictgetr   r   r   rO   r   r   requests
exceptionsr   r   r   anyr   FileNotFoundError)r   r   token1token2tokens_loadedtokenstore_pathnormalized_pathr"   r   profr   settingsr   auth_indicatorsis_auth_errorr   s                  @r#   r   zGarmin.loginy  s     <29^#<#<
U	LFF "M )(:,,
((4444
 +/z*:*:*E*E*G*G*O*O*Q*Q*-o*>*>UOUU   
888$(MM%   
 !$AI)++ I--Y1F1F:    $} %DM %"B!734!7 !7# # $%%
 ).  &+6 ! } DM :<   % *%)Z%5%5&*&8 &6 & &NFF "6>)!%!1!1MM# "2 " " 4:y$77 =:00B DD !   :4   Y:dD#9#9 Y]RV=V=V:;WXXX$(HH]$;$;!!%*!5!5*,gt,, =(/M(B(BD%%,[[%<%<DNz,,T-RSSH 66   h-- V81K1K67TUUU'
3778KLLD6>!8.8.I 	L 	L 	LWQ
D99=$OOF;QGGG }}61a11  }}7/A// 
 AINNNOKKKK?KKKKK 61a11 
 //C/C/CDD!K  	 	 	 	L 	L 	L!=>>  egg  XWWOXXXXXXXXXM 61a11  ^,,,./C/C/CDD!K	Ls}   	M B)C M 
EA,EM EAM )AM /H
 	M 

H)H$$H))D2M !T=CQT(BT  Tclient_statemfa_codec                    | j                             ||          \  }}| j         j        r| j         j        r	 | j         j        }|rIt          |t                    r4|                    d          | _        |                    d          | _	        n*# t          $ r t                              d           Y nw xY w| j                             | j                  }|r,t          |t                    rd|v r|d         d         | _        ||fS )zResume login using Garth.r   r   z4Profile fetch failed during resume_login, continuingr   r   )r   resume_loginoauth1_tokenoauth2_tokenr   r   r   r   r   r   r   r   r   r   rO   r   )r   r   r   result1result2r   r   s          r#   r   zGarmin.resume_login  s!     :22<JJ:" 	Utz'> 	UU*, =z'488 =(/M(B(BD%%,[[%<%<DN U U USTTTTTU :(()NOO 	I
8T22 	IzX7M7M'
34GHDs   AB $B76B7c                     | j         S )zReturn full name.)r   r   s    r#   get_full_namezGarmin.get_full_name/  s
    ~r,   c                     | j         S )zReturn unit system.)r   r   s    r#   get_unit_systemzGarmin.get_unit_system3  s    r,   cdatec                 ,    |                      |          S )ziReturn user activity summary for 'cdate' format 'YYYY-MM-DD'
        (compat for garminconnect).
        )get_user_summary)r   r   s     r#   	get_statszGarmin.get_stats7  s     $$U+++r,   c                     t          |d          }| j         d| j         }d|i}t                              d           |                     ||          }|st          d          |                    d          du rt          d	          |S )
z=Return user activity summary for 'cdate' format 'YYYY-MM-DD'.r   /calendarDatezRequesting user summaryparamszNo data received from serverprivacyProtectedTzAuthentication error)	r$   rV   r   r   r   r   r   r   r   r   r   urlr   r<   s        r#   r   zGarmin.get_user_summary=  s     &eW556LL9JLL %(.///??3v?66 	O./MNNN<<*++t3323IJJJr,   c                     t          |d          }| j         d| j         }d|i}t                              d           |                     ||          }|t                              d           g S |S )z7Fetch available steps data 'cDate' format 'YYYY-MM-DD'.r   r   r   zRequesting steps datar   NzNo steps data received)r$   ry   r   r   r   r   r   r   s        r#   get_steps_datazGarmin.get_steps_dataP  s     &eW557MM$:KMM%,---??3v?66NN3444Ir,   c                     t          |d          }| j         d| }t                              d           |                     |          }|t          d          |S )z8Fetch available floors data 'cDate' format 'YYYY-MM-DD'.r   r   zRequesting floors dataNzNo floors data received)r$   rz   r   r   r   r   )r   r   r   r<   s       r#   
get_floorszGarmin.get_floorsa  sg     &eW55;EEeEE-...??3''./HIIIr,   startendc                    t          |d          }t          |d          }t          j        |t                                                    }t          j        |t                                                    }||k    rt          d          ||z
  j        dz   }|dk    r>| j         d| d| }t          	                    d           | 
                    |          S t          	                    d| d	           g }|}||k    rt          |t          d
          z   |          }	|                                }
|	                                }| j         d|
 d| }t          	                    d|
 d|            | 
                    |          }|r|                    |           |	t          d          z   }||k    |S )a1  Fetch available steps data 'start' and 'end' format 'YYYY-MM-DD'.

        Note: The Garmin Connect API has a 28-day limit per request. For date ranges
        exceeding 28 days, this method automatically splits the range into chunks
        and makes multiple API calls, then merges the results.
        r  r  z#start date cannot be after end dater      r   zRequesting daily steps datazDate range (z. days) exceeds 28-day limit, chunking requests   )daysz'Requesting daily steps data for chunk: z to )r$   r   r    r!   r   r   r	  r\   r   r   r   minr   	isoformatextend)r   r  r  
start_dateend_date	days_diffr   all_resultscurrent_start	chunk_endchunk_start_strchunk_end_strchunk_resultss                r#   get_daily_stepszGarmin.get_daily_stepsp  s    &eW55#C// &uo>>CCEE
$S/::??AA  BCCC 
*014	 ??>NNNNNNCLL6777??3''' 	T9TTT	
 	
 	
 "x''MI2,>,>,>>III+5577O%//11M < 5 5"5 5%25 5  LL8"8 8(58 8  
 !OOC00M 2""=111 &	q(9(9(99M) x'', r,   4   weeksc                     t          |d          }t          |d          }| j         d| d| }t                              d||           |                     |          S )a  Fetch weekly steps aggregates.

        Args:
            end: End date string in format 'YYYY-MM-DD'
            weeks: Number of weeks to fetch (default 52 = 1 year)

        Returns:
            List of weekly step aggregates containing:
            - totalSteps: Total steps for the week
            - averageSteps: Average daily steps
            - totalDistance: Total distance in meters
            - averageDistance: Average daily distance
            - wellnessDataDaysCount: Days with data

        r  r  r   z3Requesting weekly steps data for %d weeks ending %s)r$   r3   r]   r   r   r   r   r  r  r   s       r#   get_weekly_stepszGarmin.get_weekly_steps  sj      $C//*5'::;KKcKKEKKJESVWWWs###r,   c                     t          |d          }t          |d          }| j         d| d| }t                              d||           |                     |          S )a`  Fetch weekly stress aggregates.

        Args:
            end: End date string in format 'YYYY-MM-DD'
            weeks: Number of weeks to fetch (default 52 = 1 year)

        Returns:
            List of weekly stress aggregates containing:
            - value: Overall stress value for the week
            - calendarDate: Week start date

        r  r  r   z4Requesting weekly stress data for %d weeks ending %s)r$   r3   r^   r   r   r   r  s       r#   get_weekly_stresszGarmin.get_weekly_stress  sj     $C//*5'::<LLsLLULLKUTWXXXs###r,   c                     t          |d          }t          |d          }| j         d| d| }t                              d||           |                     |          S )a  Fetch weekly intensity minutes aggregates.

        Args:
            start: Start date string in format 'YYYY-MM-DD'
            end: End date string in format 'YYYY-MM-DD'

        Returns:
            List of weekly intensity minute aggregates containing:
            - weeklyGoal: Weekly intensity minutes goal
            - moderateValue: Moderate intensity minutes
            - vigorousValue: Vigorous intensity minutes
            - calendarDate: Week start date

        r  r  r   z1Requesting weekly intensity minutes from %s to %s)r$   r_   r   r   r   )r   r  r  r   s       r#   get_weekly_intensity_minutesz#Garmin.get_weekly_intensity_minutes  sk    " &eW55#C//GWW%WWRUWWH%QTUUUs###r,   c                     t          |d          }| j         d| j         }d|i}t                              d           |                     ||          }|t          d          |S )a  Fetch available heart rates data 'cDate' format 'YYYY-MM-DD'.

        Args:
            cdate: Date string in format 'YYYY-MM-DD'

        Returns:
            Dictionary containing heart rate data for the specified date

        Raises:
            ValueError: If cdate format is invalid
            GarminConnectConnectionError: If no data received
            GarminConnectAuthenticationError: If authentication fails

        r   r   r   zRequesting heart ratesr   NzNo heart rate data received)r$   r{   r   r   r   r   r   r   s        r#   get_heart_rateszGarmin.get_heart_rates  sw      &eW559OOD<MOO%-...??3v?66./LMMMr,   c                     |                      |          }|                     |          }|                    d          pi }t          |t                    si }i ||S )zEReturn activity data and body composition (compat for garminconnect).totalAverage)r   get_body_compositionr   r   r   )r   r   statsbodybody_avgs        r#   get_stats_and_bodyzGarmin.get_stats_and_body  sc    u%%((//88N++1r(D)) 	H$%$8$$r,   	startdateenddatec                    t          |d          }||nt          |d          }t          j        |t                                                    t          j        |t                                                    k    rt          d          | j         d}t          |          t          |          d}t          	                    d           | 
                    ||          S )	zyReturn available body composition data for 'startdate' format
        'YYYY-MM-DD' through enddate 'YYYY-MM-DD'.
        r)  Nr*  z!startdate cannot be after enddatez/weight/dateRange	startDateendDatezRequesting body compositionr   )r$   r   r    r!   r   r   rU   r   r   r   r   r   r)  r*  r   r   s        r#   r$  zGarmin.get_body_composition  s     *)[AA	 II.CGY.W.W 	 i99>>@@99>>@@A A @AAA/BBB"9~~#g,,GG2333s6222r,   	timestampweightpercent_fatpercent_hydrationvisceral_fat_mass	bone_massmuscle_mass	basal_met
active_metphysique_ratingmetabolic_agevisceral_fat_ratingbmic                    t          |d          }|rt          j        |          nt          j                    }t	                      }|                                 |                                 |                    |           |                    |||||||||	|
|||           |	                                 | j
        }dd|                                fi}| j                            d||d                                          S )Nr1  )r1  r2  r3  r4  r5  r6  r7  r8  r9  r:  r;  r<  filezbody_composition.fitr   Tfilesapi)r+   r   fromisoformatnowr   write_file_infowrite_file_creatorwrite_device_infowrite_weight_scalefinishr   getvaluer   postr@   )r   r0  r1  r2  r3  r4  r5  r6  r7  r8  r9  r:  r;  r<  r4   
fitEncoderr   r@  s                     r#   add_body_compositionzGarmin.add_body_composition+  s     +68<<2;OX#I...%''
""$$$%%'''$$R(((%%#//#!+' 3 	& 	
 	
 	
 	(+Z-@-@-B-BC
 z|S4HHMMOOOr,   r    unitKeyc                     t          |d          }|t          vrt          dt                     | j         d}	 |rt	          j        |          nt	          j                    }n%# t          $ r}t          d|           |d}~ww xY w|                    t          j	                  }t          |          t          |          |d|d}t                              d           t          | j                            d	||
                    S )zAdd a weigh-in (default to kg).r1  unitKey must be one of /user-weightzinvalid timestamp format: NMANUALdateTimestampgmtTimestamprN  
sourceTyper%   zAdding weigh-inr   r@   )r+   VALID_WEIGHT_UNITSr   rU   r   rB  rC  
astimezoner   utcr;   r   r   rA   r   rJ  )	r   r1  rN  r0  r   r4   r"   dtGMTpayloads	            r#   add_weigh_inzGarmin.add_weigh_inX  s   
 +68<<,,,K7IKKLLL/===	F6?S'	222X\^^BB 	F 	F 	F=!==>>AE	F hl++$R[[#ENN"
 
 	&'''$TZ__\3W_%U%UVVVs   )A& &
B0BBrT  rU  c                    | j          d}|t          vrt          dt                     |r&t          j        |                                          n$t          j                                                    }|r[t          j        |          }|j         |                    t          j
                  }|                    t          j
                  }n|                    t          j
                  }t          |d          }t          |          t          |          |d|d}	t                              d|	           t          | j                            d	||	
                    S )z8Add a weigh-in with explicit timestamps (default to kg).rQ  rP  Nr6   r1  rR  rS  z,Adding weigh-in with explicit timestamps: %sr   rW  )rU   rX  r   r   rB  rY  rC  r7   r9   r   rZ  r+   r;   r   r   rA   r   rJ  )
r   r1  rN  rT  rU  r   r4   gr[  r\  s
             r#   add_weigh_in_with_timestampsz#Garmin.add_weigh_in_with_timestampsu  sM    /===,,,K7IKKLLL -H"=11<<>>>**,, 	
  	0&|44AxIIX\I22LL..EEMM(,//E +68<< %R[[#ENN"
 
 	CWMMM %TZ__\3W_%U%UVVVr,   c                     t          |d          }t          |d          }| j         d| d| }ddi}t                              d           |                     ||          S )	zFGet weigh-ins between startdate and enddate using format 'YYYY-MM-DD'.r)  r*  z/weight/range/r   
includeAllTRequesting weigh-insr   r$   rU   r   r   r   r/  s        r#   get_weigh_inszGarmin.get_weigh_ins  sq    ))[AA	';;/TTyTT7TT%+,,,s6222r,   c                     t          |d          }| j         d| }ddi}t                              d           |                     ||          S )z.Get weigh-ins for 'cdate' format 'YYYY-MM-DD'.r   z/weight/dayview/rb  Trc  r   rd  r   r   r   r   s       r#   get_daily_weigh_inszGarmin.get_daily_weigh_ins  sZ    %eW55/HHHH%+,,,s6222r,   	weight_pkc                     t          |d          }| j         d| d| }t                              d           | j                            dd|d          S )	zDelete specific weigh-in.r   z/weight/z/byversion/zDeleting weigh-inDELETEr   TrA  )r$   rU   r   r   r   request)r   ri  r   r   s       r#   delete_weigh_inzGarmin.delete_weigh_in  sm    %eW55/VVVV9VV()))z!!	 " 
 
 	
r,   
delete_allc                    |                      |          }|                    dg           }|rt          |          dk    rt                              d|            dS t          |          dk    rLt                              d|            |s-t                              dt          |           d           dS |D ]}|                     |d	         |           t          |          S )
zxDelete weigh-in for 'cdate' format 'YYYY-MM-DD'.
        Includes option to delete all weigh-ins for that date.
        dateWeightListr   zNo weigh-ins found on Nr   zMultiple weigh-ins found for z%Set delete_all to True to delete all z
 weigh-inssamplePk)rh  r   r   r   r   rn  )r   r   ro  daily_weigh_ins	weigh_insws         r#   delete_weigh_inszGarmin.delete_weigh_ins  s    22599#''(8"==	 	C	NNa//NN;E;;<<<4y>>ANNB5BBCCC VC	NNVVV   t 	7 	7A  :66669~~r,   c                     t          |d          }||}nt          |d          }| j        }t          |          t          |          d}t                              d           |                     ||          S )ztReturn body battery values by day for 'startdate' format
        'YYYY-MM-DD' through enddate 'YYYY-MM-DD'.
        r)  Nr*  r,  zRequesting body battery datar   )r$   rk   r   r   r   r   r/  s        r#   get_body_batteryzGarmin.get_body_battery  sx     *)[AA	?GG+GY??G8"9~~#g,,GG3444s6222r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )a  Return body battery events for date 'cdate' format 'YYYY-MM-DD'.
        The return value is a list of dictionaries, where each dictionary contains event data for a specific event.
        Events can include sleep, recorded activities, auto-detected activities, and naps.
        r   r   z"Requesting body battery event data)r$   rl   r   r   r   r   r   r   s      r#   get_body_battery_eventszGarmin.get_body_battery_events  sN    
 &eW55<FFuFF9:::s###r,   systolic	diastolicpulsenotesc           	      &   | j          }|rt          j        |          nt          j                    }|                    t
          j                  }t          |          t          |          |||d|d}	d|ddfd|ddfd	|d
dffD ]C\  }
}}}t          |t                    r||cxk    r|k    sn t          |
 d| d| d          Dt                              d           | j                            d||	                                          S )zAdd blood pressure measurement.rR  )measurementTimestampLocalmeasurementTimestampGMTr|  r}  r~  rV  r  r|  F   i  r}  (      r~  rJ      z must be an int in [, ]zAdding blood pressurer   rW  )rn   r   rB  rC  rY  r   rZ  r;   r   r0   r   r   r   r   rJ  r@   )r   r|  r}  r~  r0  r  r   r4   r[  r\  namevallohis                 r#   set_blood_pressurezGarmin.set_blood_pressure  sB    @B2;OX#I...hl++)0'.u~~ ""
 
 2s+)R-eR%"
 	K 	KD#r2
 c3'' KcR D!I!Ib!I!IB!I!I!IJJJ 1@,---z|Sw??DDFFFr,   c                     t          |d          }||}nt          |d          }| j         d| d| }ddi}t                              d           |                     ||          S )	zpReturns blood pressure by day for 'startdate' format
        'YYYY-MM-DD' through enddate 'YYYY-MM-DD'.
        r)  Nr*  r   rb  TzRequesting blood pressure datar   )r$   rm   r   r   r   r/  s        r#   get_blood_pressurezGarmin.get_blood_pressure  s     *)[AA	?GG+GY??G<TTyTT7TT%5666s6222r,   versionc                     | j          d| d| }t                              d           | j                            dd|d                                          S )z+Delete specific blood pressure measurement.r   z#Deleting blood pressure measurementrk  r   Trl  )rn   r   r   r   rm  r@   )r   r  r   r   s       r#   delete_blood_pressurezGarmin.delete_blood_pressure%  si    @TT5TT7TT:;;;z!!	 " 
 

 $&&	r,   c                     t          |d          }| j         d| d| }t                              d           |                     |          S )zAReturn available max metric data for 'cdate' format 'YYYY-MM-DD'.r   r   zRequesting max metrics)r$   rW   r   r   r   rz  s      r#   get_max_metricszGarmin.get_max_metrics1  sT    %eW550BB5BB5BB-...s###r,   Tdaily)latestr  r  aggregationr  r  r  r  c                   |r5| j          d}| j          dt          j                     d}|                     |          }t	          |t
                    r|r	|d         }nt	          |t                    r|}ni }|                     |          }	dddddddd}
|	D ]}|                    d          }|1|d         |
d<   |d	         |
d	<   |d
         |
d
<   |d         |
d<   ||
d<   |                    d          p|                    d          }|||
d<   |                    d          }|||
d<   |
|dS |t          d          |%t          j                    	                                }t	          |t                    r|	                                }nt          |d          }t	          |t                    r|	                                }nt          |d          }h d}||vrt          d|           | j         d| d| d| d}| j         d| d| d| d}| j         d| d| d| d}|                     |          }|                     |          }|                     |          }|||dS )a  Returns Running Lactate Threshold information, including heart rate, power, and speed.

        :param bool (Required) - latest: Whether to query for the latest Lactate Threshold info or a range.  False if querying a range
        :param date (Optional) - start_date: The first date in the range to query, format 'YYYY-MM-DD'.  Required if `latest` is False.  Ignored if `latest` is True
        :param date (Optional) - end_date: The last date in the range to query, format 'YYYY-MM-DD'. Defaults to current data. Ignored if `latest` is True
        :param str (Optional) - aggregation: How to aggregate the data. Must be one of `daily`, `weekly`, `monthly`, `yearly`.
        z/latestLactateThresholdz/powerToWeight/latest/z?sport=Runningr   N)userProfilePKr  r   sequencespeed	heartRateheartRateCyclingr  r  r  r   r  r  hearRater  )speed_and_heart_ratepowerz5you must either specify 'latest=True' or a start_dater  r  >   r  weeklyyearlymonthlyzaggregation must be one of z/lactateThresholdSpeed/range/r   z?sport=RUNNING&aggregation=z&aggregationStrategy=LATESTz!/lactateThresholdHeartRate/range/z /functionalThresholdPower/range/)r  
heart_rater  )rX   r   todayr   r   listr   r   r   r  r$   rY   )r   r  r  r  r  speed_and_heart_rate_url	power_urlr  
power_dictr  speed_and_heart_rate_dictentryr  hrhrc_valid_aggregations	speed_urlheart_rate_urlr  s                      r#   get_lactate_thresholdzGarmin.get_lactate_threshold9  s     2	4MMM %  <ppTXT^T`T`pppIOOI..E%&&  5  "1X

E4((  "


#'??3K#L#L  "& $ !$() )% . H H		'**$AFAW-o>;@;K-i8@En@U-n=<A*<M-j99>-g6 YY{++Duyy/D/D>=?-k: ii 233?DG-.@A(A#  
 TUUUz||--//H j$'' 	I#--//JJ.z<HHJh%% 	C))++HH,XzBBHFFF111P;NPPQQQ>  x  x]g  x  xjr  x  x  P[  x  x  x	 C  A  Afp  A  As{  A  A  Yd  A  A  A>  {  {`j  {  {mu  {  {  S^  {  {  {		**__^44
	**j5IIIr,   value_in_mlc                    t          |t          j                  st          d          t	          |          t
          k    rt          dt
           d          | j        }|H|Ft          j                    }t          |          }t          j                    }t          |          }n|=|;t          |d          }t          j        |t                    }t          |          }n||t          |t                    st          d          	 	 t          j        |          }n%# t          $ r t          j        |d          }Y nw xY w|                                                                }t          |          }n# t          $ r}t          d          |d}~ww xY wt          |d          }t          |t                    st          d          	 	 t          j        |          }n%# t          $ r t          j        |d          }Y nw xY w|                                                                }||k    rt          d	| d
| d          t          |          }n# t          $ r  w xY w|||d}	t$                              d           | j                            d||	                                          S )a  Add hydration data in ml.  Defaults to current date and current timestamp if left empty
        :param float required - value_in_ml: The number of ml of water you wish to add (positive) or subtract (negative)
        :param timestamp optional - timestamp: The timestamp of the hydration update, format 'YYYY-MM-DDThh:mm:ss.ms' Defaults to current timestamp
        :param date optional - cdate: The date of the weigh in, format 'YYYY-MM-DD'. Defaults to current date.
        zvalue_in_ml must be a numberz&value_in_ml seems unreasonably high (>zml)Nr   ztimestamp must be a stringz%Y-%m-%dT%H:%M:%Sz,Invalid timestamp format (expected ISO 8601)ztimestamp date (z) doesn't match cdate ())r   timestampLocal	valueInMLzAdding hydration datar   rW  )r   r'   r(   r   absMAX_HYDRATION_MLr[   r   r  r   r   rC  r;   r$   r    r!   rB  r  r   r   r   putr@   )
r   r  r0  r   r   raw_dateraw_tsr"   ts_dater\  s
             r#   add_hydration_datazGarmin.add_hydration_data  s    +w|44 	=;<<< {...N9INNN   3z||HMME\^^FII9#4)%99E&uo>>FII]y4i-- ? !=>>>XO%3I>>FF! O O O%.y:MNNFFFO//11#FOO		 X X X !OPPVWWX *%99Ei-- ? !=>>>O%3I>>FF! O O O%.y:MNNFFFO ++--1133e##$S7SS5SSS   $FOO		    "'$
 
 	,---z~~lCg~>>CCEEEsa   D) (F )EF 
E8F 
F#FF#G1 0I( 1HI( HAI( (I4c                     t          |d          }| j         d| }t                              d           |                     |          S )z<Return available hydration data 'cdate' format 'YYYY-MM-DD'.r   r   zRequesting hydration data)r$   rZ   r   r   r   rz  s      r#   get_hydration_datazGarmin.get_hydration_data  sL    %eW558BB5BB0111s###r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )z>Return available respiration data 'cdate' format 'YYYY-MM-DD'.r   r   zRequesting respiration data)r$   r|   r   r   r   rz  s      r#   get_respiration_datazGarmin.get_respiration_data  sL    %eW55:DDUDD2333s###r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )z7Return available SpO2 data 'cdate' format 'YYYY-MM-DD'.r   r   zRequesting SpO2 data)r$   r}   r   r   r   rz  s      r#   get_spo2_datazGarmin.get_spo2_data  sL    %eW553==e==+,,,s###r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )zDReturn available Intensity Minutes data 'cdate' format 'YYYY-MM-DD'.r   r   z!Requesting Intensity Minutes data)r$   r~   r   r   r   rz  s      r#   get_intensity_minutes_dataz!Garmin.get_intensity_minutes_data  sL    %eW55<FFuFF8999s###r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )zAReturn available all day stress data 'cdate' format 'YYYY-MM-DD'.r   r   zRequesting all day stress datar$   ri   r   r   r   rz  s      r#   get_all_day_stresszGarmin.get_all_day_stress
  sL    %eW555????5666s###r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )zReturn available daily events data 'cdate' format 'YYYY-MM-DD'.
        Includes autodetected activities, even if not recorded on the watch.
        r   z?calendarDate=zRequesting all day events data)r$   r   r   r   r   rz  s      r#   get_all_day_eventszGarmin.get_all_day_events  sN     &eW55-DDUDD5666s###r,   c                     | j          d| j         }t                              d           |                     |          S )z)Return personal records for current user.r   z$Requesting personal records for user)r`   r   r   r   r   r   r   s     r#   get_personal_recordzGarmin.get_personal_record  s@    8NN4;LNN;<<<s###r,   c                 n    | j         }t                              d           |                     |          S )z&Return earned badges for current user.z!Requesting earned badges for user)ra   r   r   r   r  s     r#   get_earned_badgeszGarmin.get_earned_badges#  s/    38999s###r,   c                 v    | j         }t                              d           |                     |ddi          S )z)Return available badges for current user.z$Requesting available badges for usershowExclusiveBadgetruer   )rb   r   r   r   r  s     r#   get_available_badgeszGarmin.get_available_badges*  s9    6;<<<s,@&+IJJJr,   c                    t                               d           |                                 }|                                 }dt          dt
          fd}t          t          ||                    }t          t          ||                    }d |D             }|                    d |D                        t          |	                                          S )z+Return in progress badges for current user.z&Requesting in progress badges for userbadger   c                     |                      d          }|sdS |dk    rdS |                      d          }||k    r7|                      d          dS |                      dd          | d         k     S dS )	z(Return True if the badge is in progress.badgeProgressValueFr   badgeTargetValuebadgeLimitCountNbadgeEarnedNumberTr   )r  progresstargets      r#   is_badge_in_progressz;Garmin.get_in_progress_badges.<locals>.is_badge_in_progress9  s    yy!566H u1}}uYY122F6!!99.//7 5yy!4a885AR;SSS4r,   c                      i | ]}|d          |S badgeIdr   r   bs     r#   
<dictcomp>z1Garmin.get_in_progress_badges.<locals>.<dictcomp>L  s    GGGAiL!GGGr,   c                      i | ]}|d          |S r  r   r  s     r#   r  z1Garmin.get_in_progress_badges.<locals>.<dictcomp>M  s    OOOQ9qOOOr,   )
r   r   r  r  r   r)   r  filterupdatevalues)r   earned_badgesavailable_badgesr  earned_in_progress_badgesavailable_in_progress_badgescombineds          r#   get_in_progress_badgeszGarmin.get_in_progress_badges1  s    =>>>..004466	 	 	 	 	 	 %)0Dm)T)T$U$U!'+')9::(
 (
$ HG-FGGGOO2NOOOPPPHOO%%&&&r,   limitc                     t          |d          }t          |d          }| j        }t          |          t          |          d}t                              d           |                     ||          S )z)Return adhoc challenges for current user.r  r  r  r  z$Requesting adhoc challenges for userr   )r1   r3   rc   r   r   r   r   r   r  r  r   r   s        r#   get_adhoc_challengeszGarmin.get_adhoc_challengesP  j    .ug>>*5'::6u::E

;;;<<<s6222r,   c                     t          |d          }t          |d          }| j        }t          |          t          |          d}t                              d           |                     ||          S )z)Return badge challenges for current user.r  r  r  $Requesting badge challenges for userr   )r1   r3   rd   r   r   r   r   r  s        r#   get_badge_challengeszGarmin.get_badge_challengesZ  r  r,   c                     t          |d          }t          |d          }| j        }t          |          t          |          d}t                              d           |                     ||          S )z"Return available badge challenges.r  r  r  z%Requesting available badge challengesr   )r1   r3   re   r   r   r   r   r  s        r#   get_available_badge_challengesz%Garmin.get_available_badge_challengesd  sj    .ug>>*5'::@u::E

;;<===s6222r,   c                     t          |d          }t          |d          }| j        }t          |          t          |          d}t                              d           |                     ||          S )z7Return badge non-completed challenges for current user.r  r  r  r  r   )r1   r3   rf   r   r   r   r   r  s        r#   "get_non_completed_badge_challengesz)Garmin.get_non_completed_badge_challengesn  sl     /ug>>*5'::Du::E

;;;<<<s6222r,   c                     t          |d          }t          |d          }| j        }t          |          t          |          d}t                              d           |                     ||          S )z7Return in-progress virtual challenges for current user.r  r  r  z2Requesting in-progress virtual challenges for userr   )r1   r3   rg   r   r   r   r   r  s        r#   !get_inprogress_virtual_challengesz(Garmin.get_inprogress_virtual_challengesz  sl     /ug>>*5'::Cu::E

;;IJJJs6222r,   c                     t          |d          }| j         d| j         }|dd}t                              d           |                     ||          S )z#Return sleep data for current user.r   r   <   )r   nonSleepBufferMinuteszRequesting sleep datar   )r$   rh   r   r   r   r   rg  s       r#   get_sleep_datazGarmin.get_sleep_data  s`    %eW554JJt7HJJ"==,---s6222r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )z$Return stress data for current user.r   r   zRequesting stress datar  rz  s      r#   get_stress_datazGarmin.get_stress_data  sL    %eW555????-...s###r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )z/Return lifestyle logging data for current user.r   r   z!Requesting lifestyle logging data)r$   r   r   r   r   rz  s      r#   get_lifestyle_logging_dataz!Garmin.get_lifestyle_logging_data  sL    %eW55@JJ5JJ8999s###r,   c                     t          |d          }| j         d| j         }||dd}t                              d           |                     ||          S )z/Return resting heartrate data for current user.r   r   r  )fromDate	untilDatemetricIdz!Requesting resting heartrate datar   )r$   rt   r   r   r   r   rg  s       r#   get_rhr_dayzGarmin.get_rhr_day  sj    %eW55,BBt/@BB
 

 	8999s6222r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )z:Return Heart Rate Variability (hrv) data for current user.r   r   z,Requesting Heart Rate Variability (hrv) data)r$   ru   r   r   r   rz  s      r#   get_hrv_datazGarmin.get_hrv_data  sL    %eW55,66u66CDDDs###r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )z0Return training readiness data for current user.r   r   z"Requesting training readiness data)r$   rv   r   r   r   rz  s      r#   get_training_readinesszGarmin.get_training_readiness  sL    %eW55;EEeEE9:::s###r,   c                     |                      |          }|sdS t          |t                    rBt          d |D             d          }|$|r"t                              d           |d         S |S |S )a  Return morning training readiness data for current user.

        This returns the Training Readiness score calculated immediately after
        waking up, which is shown in Garmin's Morning Report feature. It filters
        for entries with inputContext == 'AFTER_WAKEUP_RESET'.

        Args:
            cdate: Date string in format 'YYYY-MM-DD'

        Returns:
            Dictionary containing morning training readiness data, or None if
            no morning data is available for the specified date.

        Note:
            Not all devices/firmware versions populate the inputContext field.
            If inputContext is null for all entries, this method returns the
            first entry as a fallback (typically the morning reading).

        Nc              3   L   K   | ]}|                     d           dk    |V   dS )inputContextAFTER_WAKEUP_RESETNr  )r   r  s     r#   r   z8Garmin.get_morning_training_readiness.<locals>.<genexpr>  sF        yy004HHH HHHH r,   zBNo AFTER_WAKEUP_RESET context found, using first entry as fallbackr   )r  r   r  nextr   r   )r   r   datamorning_entrys       r#   get_morning_training_readinessz%Garmin.get_morning_training_readiness  s    ( **511 	4 dD!! 	!  !%  
  M $$X   Aw   r,   c                    t          |d          }|I| j        }dt          |          i}t                              d           |                     ||          S | j         d}t          |d          }t          |          t          |          dd	}t                              d
           |                     ||          S )zReturn endurance score by day for 'startdate' format 'YYYY-MM-DD'
        through enddate 'YYYY-MM-DD'.
        Using a single day returns the precise values for that day.
        Using a range returns the aggregated weekly values for that week.
        r)  Nr   z0Requesting endurance score data for a single dayr   /statsr*  r  r-  r.  r  z3Requesting endurance score data for a range of days)r$   ro   r   r   r   r   r/  s        r#   get_endurance_scorezGarmin.get_endurance_score  s     *)[AA	?9C$c)nn5FLLKLLL??3v?6668@@@';;Y7||#
 

 	JKKKs6222r,   _typec                 @   h d}||vrt          d|d          |+|)|'| j        d| j         z   }|                     |          S |||t	          |d          }t	          |d          }t          j        |t                                                    t          j        |t                                                    z
  j	        dk    rt          d	          | j        d
| d
| j         z   }||d}|                     ||          S t          d          )a  Return race predictions for the 5k, 10k, half marathon and marathon.
        Accepts either 0 parameters or all three:
        If all parameters are empty, returns the race predictions for the current date
        Or returns the race predictions for each day or month in the range provided.

        Keyword Arguments:
        'startdate' the date of the earliest race predictions
        Cannot be more than one year before 'enddate'
        'enddate' the date of the last race predictions
        '_type' either 'daily' (the predictions for each day in the range) or
        'monthly' (the aggregated monthly prediction for each month in the range)

        >   Nr  r  zresults: _type must be one of .Nz/latest/r)  r*  in  z5Startdate cannot be more than one year before enddater   )fromCalendarDatetoCalendarDater   z7you must either provide all parameters or no parameters)
r   rw   r   r   r$   r   r    r!   r   r	  )r   r)  r*  r  validr   r   s          r#   get_race_predictionszGarmin.get_race_predictions  s^   & +**HeHHHIII=Y.7?69WDDU9W9WW  ??3'''!67;N-iEEI+GY??G!'?;;@@BB#I??DDFFGS  !K   69XU9X9XTEV9X9XX  +4wOOF??3v?666RSSSr,   c                     t          |d          }| j         d| }t                              d           |                     |          S )z-Return training status data for current user.r   r   zRequesting training status data)r$   rx   r   r   r   rz  s      r#   get_training_statuszGarmin.get_training_status7  sL    %eW558BB5BB6777s###r,   c                     t          |d          }| j         d| }t                              d           |                     |          S )z)Return Fitness Age data for current user.r   r   zRequesting Fitness Age data)r$   r   r   r   r   rz  s      r#   get_fitnessage_datazGarmin.get_fitnessage_data?  sL    %eW55/99%992333s###r,   c                    |Y| j         }t          |d          }dt          |          i}t                              d           |                     ||          S | j          d}t          |d          }t          |d          }t          |          t          |          dd	}t                              d
           |                     ||          S )zgReturn hill score by day from 'startdate' format 'YYYY-MM-DD'
        to enddate 'YYYY-MM-DD'.
        Nr)  r   z+Requesting hill score data for a single dayr   r  r*  r  r  z.Requesting hill score data for a range of days)rj   r$   r   r   r   r   r/  s        r#   get_hill_scorezGarmin.get_hill_scoreG  s     ?4C-iEEI$c)nn5FLLFGGG??3v?6663;;;))[AA	';;Y7||"
 

 	EFFFs6222r,   c                 n    | j         }t                              d           |                     |          S )z6Return available devices for the current user account.zRequesting devices)rQ   r   r   r   r  s     r#   get_deviceszGarmin.get_devicesa  s/    -)***s###r,   	device_idc                 x    | j          d| }t                              d           |                     |          S )z3Return device settings for device with 'device_id'.z/device-info/settings/zRequesting device settingsrR   r   r   r   )r   r*  r   s      r#   get_device_settingszGarmin.get_device_settingsh  s=    /RRyRR1222s###r,   c                 n    | j         }t                              d           |                     |          S )zReturn detailed information around primary training devices, included the specified device and the
        priority of all devices.
        z.Requesting primary training device information)rS   r   r   r   r  s     r#   get_primary_training_devicez"Garmin.get_primary_training_deviceo  s1     4EFFFs###r,   c                     ||}d}nd}t          |d          }t          |d          }d|i}| j         d| d| d| }|                     ||          }|rd	|vrt          d
          |d	         S )z9Return solar data for compatible device with 'device_id'.NTFr)  r*  singleDayViewr   r   deviceSolarInputz#No device solar input data received)r$   rT   r   r   )r   r*  r)  r*  
single_dayr   r   resps           r#   get_device_solar_datazGarmin.get_device_solar_datax  s     ?GJJJ))[AA	';;!:..RRRRYRRRRs622 	V)55./TUUU&''r,   c                     t                               d           g }|                                 }|D ]9}|                     |d                   }|                    d          }|||z  }:|S )z+Get list of active alarms from all devices.zRequesting device alarmsdeviceIdalarms)r   r   r)  r-  r   )r   r8  devicesdevicedevice_settingsdevice_alarmss         r#   get_device_alarmszGarmin.get_device_alarms  s{    /000""$$ 	( 	(F"66vj7IJJO+//99M(-'r,   c                 t    | j          d}t                              d           |                     |          S )zReturn device last used.z/mylastusedzRequesting device last usedr,  r  s     r#   get_device_last_usedzGarmin.get_device_last_used  s8    /<<<2333s###r,   c                     | j          }t                              d           |                     |          }|rd|vrt	          d          |d         S )z?Return total number of activities for the current user account.zRequesting activities count
totalCountz!No activities count data received)r   r   r   r   r   )r   r   activities_counts      r#   count_activitieszGarmin.count_activities  s`    572333??3// 	T<7G#G#G./RSSS--r,   r   rJ   activitytypec                    t          |d          }t          |d          }|t          k    rt          dt                     | j        }t          |          t          |          d}|rt          |          |d<   t                              d||           |                     ||          }|t          	                    d	           g S |S )
a$  Return available activities.
        :param start: Starting activity offset, where 0 means the most recent activity
        :param limit: Number of activities to return
        :param activitytype: (Optional) Filter activities by type
        :return: List of activities from Garmin.
        r  r  zlimit cannot exceed r  activityTypez+Requesting activities from %d with limit %dr   NzNo activities data received)
r1   r3   MAX_ACTIVITY_LIMITr   r   r   r   r   r   r   )r   r  r  rD  r   r   
activitiess          r#   get_activitieszGarmin.get_activities  s     /ug>>*5'::%%%H4FHHIII,u::E

;; 	7%(%6%6F>"BE5QQQ__S_88
NN8999Ir,   fordatec                     t          |d          }| j         d| }t                              d|           |                     |          S )z%Return available activities for date.rJ  r   z!Requesting activities for date %s)r$   r   r   r   r   r   rJ  r   s      r#   get_activities_fordatezGarmin.get_activities_fordate  sN    ';;5AAAA8'BBBs###r,   activity_idtitlec                 `    | j          d| }||d}| j                            d||d          S )zSet name for activity with id.r   )
activityIdactivityNamer   Tr@   rA  )r   r   r  )r   rN  rO  r   r\  s        r#   set_activity_namezGarmin.set_activity_name  sA    -====!,eDDz~~lCg4~HHHr,   type_idtype_keyparent_type_idc                     | j          d| }||||dd}t                              d|           | j                            d||d          S )Nr   )typeIdtypeKeyparentTypeId)rQ  activityTypeDTOzChanging activity type: %sr   TrS  )r   r   r   r   r  )r   rN  rU  rV  rW  r   r\  s          r#   set_activity_typezGarmin.set_activity_type  so     -====%!# .   
 
 	17;;;z~~lCg4~HHHr,   r\  c                     | j          }t                              dt          |                     | j                            d||d          S )NzUploading manual activity: %sr   TrS  )r   r   r   r   r   rJ  )r   r\  r   s      r#    create_manual_activity_from_jsonz'Garmin.create_manual_activity_from_json  sD    -/4c'llCCCz|SwDIIIr,   start_datetime	time_zonedistance_kmduration_minactivity_namec           	      d    d|idddd|i|ddi||dz  |d	z  d
d}|                      |          S )a{  Create a private activity manually with a few basic parameters.
        type_key - Garmin field representing type of activity. See https://connect.garmin.com/modern/main/js/properties/activity_types/activity_types.properties
                    Value to use is the key without 'activity_type_' prefix, e.g. 'resort_skiing'
        start_datetime - timestamp in this pattern "2023-12-02T10:00:00.000"
        time_zone - local timezone of the activity, e.g. 'Europe/Paris'
        distance_km - distance of the activity in kilometers
        duration_min - duration of the activity in minutes
        activity_name - the title.
        rZ     private)rY  rZ  rN  autoCalcCaloriesTr   r  )startTimeLocaldistanceduration)r\  accessControlRuleDTOtimeZoneUnitDTOrR  metadataDTO
summaryDTO)r_  )r   r`  ra  rV  rb  rc  rd  r\  s           r#   create_manual_activityzGarmin.create_manual_activity  sm    & !*84/0Y$G$G )95)"D #1'$.(2- 
 
 44W===r,   c                    |                      dd          }|r0t          |t                    rt          |          dk    r|d         S |r>t          |t                    r)d|v r%|d         }|rt          |          dk    r|d         S dS )zReturn last activity.r   r   activityListN)rI  r   r  r   r   )r   rH  activity_lists      r#   get_last_activityzGarmin.get_last_activity  s    ((A..
 	"*Z66 	"3z??Q;N;Nb>! 	)*Z66 	)>Z;W;W&~6M )]!3!3a!7!7$R((tr,   activity_pathc                    |st          d          t          |t                    st          d          t          |          }|                                st          d|           |                                st          d|           |j        }|st          d          |                    d          }t          |          dk     rt          d|           |d	         }|                                t          j        j        v }|r	 |                    d
          5 }d||fi}| j        }	| j                            d|	|d          cddd           S # 1 swxY w Y   dS # t&          $ r}
t)          d| d|
           |
d}
~
ww xY wd                    t          j        j                                                  }t          d| d|           )z(Upload activity in fit format from file.zactivity_path cannot be emptyzactivity_path must be a stringzFile not found: zpath is not a file: z%invalid file path - no filename foundr  rf  zFile has no extension: rr  rbr>  r   Tr?  NzFailed to read file r   r  zInvalid file format 'z'. Allowed formats: )r   r   r   r
   existsr   is_filer  splitr   #GarminConnectInvalidFileFormatErrorupperrC   ActivityUploadFormat__members__openr   r   rJ  OSErrorr   joinkeys)r   rv  pfile_base_name
file_partsfile_extensionallowed_file_extensionfile_handler@  r   r"   allowed_formatss               r#   upload_activityzGarmin.upload_activity"  s   
  	><===--- 	?=>>> xxzz 	H#$F}$F$FGGG yy{{ 	ECMCCDDD 	FDEEE $))#..
z??Q59-99   $B  ""f&A&MM 	 " 		VVD\\ U[#nk%BCE4C:??<Et?TTU U U U U U U U U U U U U U U U U U    2?=??A?? 
 #ii(C(O(T(T(V(VWWO5]]]O]]  s<   E- (+E E-  E$$E- 'E$(E- -
F7FFc                     | j          d| }t                              d|           | j                            dd|d          S )z"Delete activity with specified id.r   zDeleting activity with id %srk  r   Trl  )r   r   r   r   rm  r   rN  r   s      r#   delete_activityzGarmin.delete_activityX  sX    8HH;HH3[AAAz!!	 " 
 
 	
r,   	sortorderc                 
   g }d}d}| j         }t          |d          }|t          |d          }|t          |          t          |          d}	|r||	d<   |rt          |          |	d<   |rt          |          |	d	<   t                              d
||           	 t          |          |	d<   t                              d|||z              |                     ||	          }
|
r|                    |
           ||z   }nng|S )a  Fetch available activities between specific dates
        :param startdate: String in the format YYYY-MM-DD
        :param enddate: (Optional) String in the format YYYY-MM-DD
        :param activitytype: (Optional) Type of activity you are searching
                             Possible values are [cycling, running, swimming,
                             multi_sport, fitness_equipment, hiking, walking, other]
        :param sortorder: (Optional) sorting direction. By default, Garmin uses descending order by startLocal field.
                          Use "asc" to get activities from oldest to newest.
        :return: list of JSON activities.
        r   rJ   r)  Nr*  )r-  r  r  r.  rF  	sortOrderz+Requesting activities by date from %s to %sTr  zRequesting activities %d to %dr   )r   r$   r   r   r   r   r  )r   r)  r*  rD  r  rH  r  r  r   r   acts              r#   get_activities_by_datezGarmin.get_activities_by_dated  s:   " 
 ,))[AA	+GY??G"ZZZZ
 

  	( 'F9 	7%(%6%6F>" 	1"%i..F;BIwWWW	!%jjF7OLL95%%-PPP//#f/55C !!#&&&	 r,   rj  metricgroupbyactivitiesc                 .   | j         }t          |d          }t          |d          }t          |          t          |          dt          |          t          |          d}t                              d||           |                     ||          S )a  Fetch progress summary data between specific dates
        :param startdate: String in the format YYYY-MM-DD
        :param enddate: String in the format YYYY-MM-DD
        :param metric: metric to be calculated in the summary:
            "elevationGain", "duration", "distance", "movingDuration"
        :param groupbyactivities: group the summary by activity type
        :return: list of JSON activities with their aggregated progress summary.
        r)  r*  lifetime)r-  r.  r  groupByParentActivityTyper  z-Requesting fitnessstats by date from %s to %sr   )r   r$   r   r   r   r   )r   r)  r*  r  r  r   r   s          r#   "get_progress_summary_between_datesz)Garmin.get_progress_summary_between_dates  s     .))[AA	';;Y7||%),->)?)?&kk
 
 	;Y	
 	
 	
 s6222r,   c                 n    | j         }t                              d           |                     |          S )NzRequesting activity types)r   r   r   r   r  s     r#   get_activity_typeszGarmin.get_activity_types  s/    00111s###r,   active   r   c                    g }| j         }h d}||vrt          d|           t          |d          }t          |d          }|t	          |          t	          |          dd}t
                              d|           	 t	          |          |d<   t
                              d	||||z   d
z
             |                     ||          }|r|                    |           ||z   }nnk|S )ab  Fetch all goals based on status
        :param status: Status of goals (valid options are "active", "future", or "past")
        :type status: str
        :param start: Initial goal index
        :type start: int
        :param limit: Pagination limit when retrieving goals
        :type limit: int
        :return: list of goals in JSON format.
        >   pastr  futurezstatus must be one of r  r  asc)r   r  r  r  zRequesting %s goalsTzRequesting %s goals %d to %dr   r   )	rs   r   r1   r3   r   r   r   r   r  )	r   r   r  r  goalsr   valid_statusesr   
goals_jsons	            r#   	get_goalszGarmin.get_goals  s    +555''FnFFGGG.ug>>*5'::ZZZZ	
 
 	*F333
	!%jjF7OLL.uu}q?P   V<<J Z(((
	 r,   userProfileNumberc                 z    | j          d| }t                              d|           |                     |          S )zReturn all user gear.z?userProfilePk=zRequesting gear for user %s)r   r   r   r   r   r  r   s      r#   get_gearzGarmin.get_gear  sA    )MM:KMM24EFFFs###r,   gearUUIDc                 <   | j          d| }t                              d|           	 |                     |          S # t          $ rS}t          t          |j        dd           dd           }|dk    r"t                              d|           i cY d }~S  d }~ww xY w)Nz/stats/z%Requesting gear stats for gearUUID %sr<   r?     z>Gear stats not found for UUID %s (likely retired/removed gear))r   r   r   r   r   r   r   r   )r   r  r   r"   r   s        r#   get_gear_statszGarmin.get_gear_stats  s    1DD(DD<hGGG
	??3''' 	 	 	WQWj$??PTUUF}}T   							s"   > 
BABBBBc                 |    | j          d| d}t                              d|           |                     |          S )Nz/user/z/activityTypesz$Requesting gear defaults for user %s)r   r   r   r   r  s      r#   get_gear_defaultszGarmin.get_gear_defaults  sI    /XX7HXXX 	 	;=NOOOs###r,   rF  defaultGearc                    |rdnd}|rdnd}| j          d| d| | }	 | j                            |d|d	          S # t          $ rE}t	          t	          |j        d
d           dd           }|dk    rt          d| d          | d }~ww xY w)Nz/default/truerM  PUTrk  r   z/activityType/r   Trl  r<   r?   r  z!Cannot set gear default for UUID ): gear not found (likely retired/removed))r   r   rm  r   r   r   r   )	r   rF  r  r  defaultGearStringmethod_overrider   r"   r   s	            r#   set_gear_defaultzGarmin.set_gear_default  s     0;BOO#.<%%H/ > >( > >(>*;> > 	
	:%%o|Sd%SSS 	 	 	WQWj$??PTUUF}}2kkkk  	s   = 
BA BBc                   v    e Zd ZdZ e            Z e            Z e            Z e            Z e            Z	dS )Garmin.ActivityDownloadFormatzActivity variables.N)
__name__
__module____qualname____doc__r	   ORIGINALTCXGPXKMLCSVr   r,   r#   ActivityDownloadFormatr    sK        !!466dffdffdffdffr,   r  c                   J    e Zd Z e            Z e            Z e            ZdS )Garmin.ActivityUploadFormatN)r  r  r  r	   FITr  r  r   r,   r#   r~  r     s/        dffdffdffr,   r~  dl_fmtc                    t          |          }t          j        j        | j         d| t          j        j        | j         d| t          j        j        | j         d| t          j        j	        | j
         d| t          j        j        | j         d| i}||vrt          d| d          ||         }t                              d|           |                     |          S )zDownloads activity in requested format and returns the raw bytes. For
        "Original" will return the zip file content, up to user to extract it.
        "CSV" will return a csv of the splits.
        r   zunexpected value z for dl_fmtzDownloading activity from %s)r   rC   r  r  r   r  r   r  r   r  r   r  r   r   r   r   r   )r   rN  r  urlsr   s        r#   download_activityzGarmin.download_activity%  s     +&&)2t7W4g4gZe4g4g)-$2R/b/bU`/b/b)-$2R/b/bU`/b/b)-$2R/b/bU`/b/b)-$2R/b/bU`/b/b
 DDDDEEE6l3S999}}S!!!r,   c                     t          |          }| j         d| d}t                              d|           |                     |          S )zReturn activity splits.r   z/splitsz$Requesting splits for activity id %sr   r   r   r   r   r  s      r#   get_activity_splitszGarmin.get_activity_splits>  sO    +&&-DDDDD;[IIIs###r,   c                     t          |          }| j         d| d}t                              d|           |                     |          S )zReturn typed activity splits. Contains similar info to `get_activity_splits`, but for certain activity types
        (e.g., Bouldering), this contains more detail.
        r   z/typedsplitsz*Requesting typed splits for activity id %sr  r  s      r#   get_activity_typed_splitsz Garmin.get_activity_typed_splitsF  sQ     +&&-IIIIIA;OOOs###r,   c                     t          |          }| j         d| d}t                              d|           |                     |          S )z Return activity split summaries.r   z/split_summariesz-Requesting split summaries for activity id %sr  r  s      r#   get_activity_split_summariesz#Garmin.get_activity_split_summariesP  sO    +&&-MMMMMDkRRRs###r,   c                     t          |          }| j         d| d}t                              d|           |                     |          S )zReturn activity weather.r   z/weatherz%Requesting weather for activity id %sr  r  s      r#   get_activity_weatherzGarmin.get_activity_weatherX  sO    +&&-EEEEE<kJJJs###r,   c                     t          |          }| j         d| d}t                              d|           |                     |          S )z'Return activity heartrate in timezones.r   z/hrTimeInZonesz.Requesting HR time-in-zones for activity id %sr  r  s      r#   get_activity_hr_in_timezonesz#Garmin.get_activity_hr_in_timezones`  sO    +&&-KKKKKE{SSSs###r,   c                     t          |          }| j         d| d}t                              d|           |                     |          S )z#Return activity power in timezones.r   z/powerTimeInZonesz1Requesting Power time-in-zones for activity id %sr  r  s      r#   get_activity_power_in_timezonesz&Garmin.get_activity_power_in_timezonesh  sO    +&&-NNNNNH+VVVs###r,   c                 t    | j          d}t                              d           |                     |          S )z<Return cycling Functional Threshold Power (FTP) information.z'/latestFunctionalThresholdPower/CYCLINGzRequesting latest cycling FTP)rX   r   r   r   r  s     r#   get_cycling_ftpzGarmin.get_cycling_ftpp  s:     2[[[4555s###r,   c                     t          |          }| j         d| }t                              d|           |                     |          S )z0Return activity summary, including basic splits.r   z3Requesting activity summary data for activity id %sr  r  s      r#   get_activityzGarmin.get_activityx  sL    +&&-====JKXXXs###r,       maxchartmaxpolyc                    t          |          }t          |d          }t          |d          }t          |          t          |          d}| j         d| d}t                              d|           |                     ||          S )zReturn activity details.r  r  )maxChartSizemaxPolylineSizer   z/detailsz%Requesting details for activity id %sr   )r   r3   r1   r   r   r   r   )r   rN  r  r  r   r   s         r#   get_activity_detailszGarmin.get_activity_details  s     +&&-h
CC0)DD"%h--CLLQQ-EEEEE<kJJJs6222r,   c                     t          t          |          d          }| j         d| d}t                              d|           |                     |          S )zReturn activity exercise sets.rN  r   z/exerciseSetsz+Requesting exercise sets for activity id %s)r3   r0   r   r   r   r   r  s      r#   get_activity_exercise_setsz!Garmin.get_activity_exercise_sets  sY    0[1A1A=QQ-JJJJJBKPPPs###r,   c                     t          t          |          d          }dt          |          i}| j        }t                              d|           |                     ||          S )z"Return gears used for activity id.rN  rQ  z"Requesting gear for activity_id %sr   )r3   r0   r   r   r   r   r   )r   rN  r   r   s       r#   get_activity_gearzGarmin.get_activity_gear  sb    0[1A1A=QQ#k**
 &9;GGGs6222r,   r   c                    t          |          }t          |d          }t          |t                    }| j         | d| }t
                              d|           	 |                     |          S # t          $ rS}t          t          |j
        dd          dd          }|dk    r"t
                              d|           g cY d}~S  d}~ww xY w)	a  Return activities where gear uuid was used.
        :param gearUUID: UUID of the gear to get activities for
        :param limit: Maximum number of activities to return (default: 1000)
        :return: List of activities where the specified gear was used.
        r  z/gear?start=0&limit=z%Requesting activities for gearUUID %sr<   Nr?   r  zCGear activities not found for UUID %s (likely retired/removed gear))r   r3   r
  rG  r   r   r   r   r   r   r   r   )r   r  r  r   r"   r   s         r#   get_gear_activitieszGarmin.get_gear_activities  s     x==*5'::E-..7^^^W\^^<hGGG
	??3''' 	 	 	WQWj$??PTUUF}}Y   							s%   A4 4
C>ACCCCc                    t          |          }t          t          |          d          }| j         d| d| }t                              d||           	 | j                            d|                                          S # t          $ rH}t          t          |j        dd          dd          }|d	k    rt          d
| d| d          | d}~ww xY w)aZ  Associates gear with an activity. Requires a gearUUID and an activity_id.

        Args:
            gearUUID: UID for gear to add to activity. Findable though the get_gear function
            activity_id: Integer ID for the activity to add the gear to

        Returns:
            Dictionary containing information for the added gear

        rN  z/link/
/activity/zLinking gear %s to activity %sr   r<   Nr?   r  zCannot add gear z to activity r  r   r3   r0   r   r   r   r   r  r@   r   r   r   r   r   r  rN  r   r"   r   s         r#   add_gear_to_activityzGarmin.add_gear_to_activity  s    x==0[1A1A=QQ /XXxXX;XX 	 	5xMMM	:>>,4499;;; 	 	 	WQWj$??PTUUF}}2txttkttt  	   ,B 
CACCc                    t          |          }t          t          |          d          }| j         d| d| }t                              d||           	 | j                            d|                                          S # t          $ rH}t          t          |j        dd          dd          }|d	k    rt          d
| d| d          | d}~ww xY w)ad  Removes gear from an activity. Requires a gearUUID and an activity_id.

        Args:
            gearUUID: UID for gear to remove from activity. Findable though the get_gear method.
            activity_id: Integer ID for the activity to remove the gear from

        Returns:
            Dictionary containing information about the removed gear

        rN  z/unlink/r  z"Unlinking gear %s from activity %sr   r<   Nr?   r  zCannot remove gear z from activity r  r  r  s         r#   remove_gear_from_activityz Garmin.remove_gear_from_activity  s     x==0[1A1A=QQ1\\8\\{\\98[QQQ	:>>,4499;;; 	 	 	WQWj$??PTUUF}}2y(yy;yyy  	r  c                 n    | j         }t                              d           |                     |          S )zGet all users settings.zRequesting user profile.)rO   r   r   r   r  s     r#   get_user_profilezGarmin.get_user_profile  s/    3/000s###r,   c                 n    | j         }t                              d           |                     |          S )zGet user settings.zGetting userprofile settings)rP   r   r   r   r  s     r#   get_userprofile_settingszGarmin.get_userprofile_settings  s/    :3444s###r,   c                     t          |d          }| j         d| }t                              d|           | j                            d|d                                          S )zrRequest reload of data for a specific date. This is necessary because
        Garmin offloads older data.
        r   r   z!Requesting reload of data for %s.r   Trl  )r$   r   r   r   r   rJ  r@   rz  s      r#   request_reloadzGarmin.request_reload	  sd     &eW55/99%998%@@@z|Sd;;@@BBBr,   d   c                     | j          d}t          |d          }t          |d          }t                              d||           ||d}|                     ||          S )zHReturn workouts starting at offset `start` with at most `limit` results.z	/workoutsr  r  z)Requesting workouts from %d with limit %dr  r   )r   r1   r3   r   r   r   r  s        r#   get_workoutszGarmin.get_workouts	  sk    %000.ug>>*5'::@%OOO 511s6222r,   
workout_idc                 ~    t          t          |          d          }| j         d| }|                     |          S )zReturn workout by id.r  z	/workout/)r3   r0   r   r   r   r  r   s      r#   get_workout_by_idzGarmin.get_workout_by_id	  s?    /JNN
%<<
<<s###r,   c                     t          t          |          d          }| j         d| }t                              d|           |                     |          S )zDownload workout by id.r  z/workout/FIT/zDownloading workout from %s)r3   r0   r   r   r   r   r  s      r#   download_workoutzGarmin.download_workout	  sT    /JNN
%@@J@@2C888}}S!!!r,   workout_jsonc                    | j          d}t                              d|           t          |t                    r@ddl}	 |                    |          }n'# t          $ r}t          d|           |d}~ww xY w|}t          |t          t          z            st          d          | j                            d||d	                                          S )
zUpload workout using json data.z/workoutzUploading workout using %sr   Nzinvalid workout_json string: z+workout_json must be a JSON object or arrayr   TrS  )r   r   r   r   r   r@   r   r   r   r   r  r   rJ  )r   r   r   _jsonr\  r"   s         r#   upload_workoutzGarmin.upload_workout&	  s     %///13777lC(( 	#    M++l33 M M M !D!D!DEE1LM #G'4$;// 	LJKKKz|SwDIINNPPPs    A 
A8 A33A8workoutc                     	 ddl m} t          ||          st          d          |                     |                                          S # t          $ r t          d          dw xY w)a$  Upload a typed running workout.

        Args:
            workout: RunningWorkout instance from garminconnect.workout

        Returns:
            Dictionary containing the uploaded workout data

        Example:
            from garminconnect.workout import RunningWorkout, WorkoutSegment, create_warmup_step

            workout = RunningWorkout(
                workoutName="Easy Run",
                estimatedDurationInSecs=1800,
                workoutSegments=[
                    WorkoutSegment(
                        segmentOrder=1,
                        sportType={"sportTypeId": 1, "sportTypeKey": "running"},
                        workoutSteps=[create_warmup_step(300.0)]
                    )
                ]
            )
            api.upload_running_workout(workout)

        r   )RunningWorkoutz)workout must be a RunningWorkout instancetPydantic is required for typed workouts. Install it with: pip install pydantic or pip install garminconnect[workout]N)r  r  r   	TypeErrorr  to_dictImportError)r   r  r  s      r#   upload_running_workoutzGarmin.upload_running_workout:	      4
	//////g~66 M KLLL&&w'8'8999 	 	 	^  	   AA A)c                     	 ddl m} t          ||          st          d          |                     |                                          S # t          $ r t          d          dw xY w)a)  Upload a typed cycling workout.

        Args:
            workout: CyclingWorkout instance from garminconnect.workout

        Returns:
            Dictionary containing the uploaded workout data

        Example:
            from garminconnect.workout import CyclingWorkout, WorkoutSegment, create_warmup_step

            workout = CyclingWorkout(
                workoutName="Interval Ride",
                estimatedDurationInSecs=3600,
                workoutSegments=[
                    WorkoutSegment(
                        segmentOrder=1,
                        sportType={"sportTypeId": 2, "sportTypeKey": "cycling"},
                        workoutSteps=[create_warmup_step(600.0)]
                    )
                ]
            )
            api.upload_cycling_workout(workout)

        r   )CyclingWorkoutz)workout must be a CyclingWorkout instancer  N)r  r  r   r  r  r	  r
  )r   r  r  s      r#   upload_cycling_workoutzGarmin.upload_cycling_workout`	  r  r  c                     	 ddl m} t          ||          st          d          |                     |                                          S # t          $ r t          d          dw xY w)zUpload a typed swimming workout.

        Args:
            workout: SwimmingWorkout instance from garminconnect.workout

        Returns:
            Dictionary containing the uploaded workout data

        r   )SwimmingWorkoutz*workout must be a SwimmingWorkout instancer  N)r  r  r   r  r  r	  r
  )r   r  r  s      r#   upload_swimming_workoutzGarmin.upload_swimming_workout	  s    
	000000g77 N LMMM&&w'8'8999 	 	 	^  	r  c                     	 ddl m} t          ||          st          d          |                     |                                          S # t          $ r t          d          dw xY w)zUpload a typed walking workout.

        Args:
            workout: WalkingWorkout instance from garminconnect.workout

        Returns:
            Dictionary containing the uploaded workout data

        r   )WalkingWorkoutz)workout must be a WalkingWorkout instancer  N)r  r  r   r  r  r	  r
  )r   r  r  s      r#   upload_walking_workoutzGarmin.upload_walking_workout	  s    
	//////g~66 M KLLL&&w'8'8999 	 	 	^  	r  c                     	 ddl m} t          ||          st          d          |                     |                                          S # t          $ r t          d          dw xY w)zUpload a typed hiking workout.

        Args:
            workout: HikingWorkout instance from garminconnect.workout

        Returns:
            Dictionary containing the uploaded workout data

        r   )HikingWorkoutz(workout must be a HikingWorkout instancer  N)r  r  r   r  r  r	  r
  )r   r  r  s      r#   upload_hiking_workoutzGarmin.upload_hiking_workout	  s    
	......g}55 L JKKK&&w'8'8999 	 	 	^  	r  scheduled_workout_idc                     t          t          |          d          }| j         d| }t                              d|           |                     |          S )zReturn scheduled workout by ID.r  r   z%Requesting scheduled workout by id %d)r3   r0   r   r   r   r   )r   r  r   s      r#   get_scheduled_workout_by_idz"Garmin.get_scheduled_workout_by_id	  sd      :$%%'= 
  
 2KK5IKK<>RSSSs###r,   c                     t          |d          }| j         d| }t                              d|           |                     |          S )zReturn menstrual data for date.rJ  r   z%Requesting menstrual data for date %s)r$   rq   r   r   r   rL  s      r#   get_menstrual_data_for_datez"Garmin.get_menstrual_data_for_date	  sN    ';;:FFWFF<gFFFs###r,   c                     t          |d          }t          |d          }| j         d| d| }t                              d||           |                     |          S )zHReturn summaries of cycles that have days between startdate and enddate.r)  r*  r   z1Requesting menstrual data for dates %s through %s)r$   rp   r   r   r   )r   r)  r*  r   s       r#   get_menstrual_calendar_dataz"Garmin.get_menstrual_calendar_data	  sq     *)[AA	';;;SSiSS'SS?G	
 	
 	
 s###r,   c                 p    | j          }t                              d           |                     |          S )z"Return snapshot of pregnancy data.z"Requesting pregnancy snapshot data)rr   r   r   r   r  s     r#   get_pregnancy_summaryzGarmin.get_pregnancy_summary	  s2    ;=9:::s###r,   queryc                    t          |t                    r|                    d          pdnd}t          |t                    r6t          |                    d          pi                                           ng }t
                              d||           | j                            d| j	        |          
                                S )a  Execute a POST to Garmin's GraphQL endpoint.

        Args:
            query: A GraphQL request body, e.g. {"query": "...", "variables": {...}}
            See example.py for example queries.

        Returns:
            Parsed JSON response as a dict.

        operationNameunnamed	variablesz%Querying Garmin GraphQL op=%s vars=%sr   rW  )r   r   r   sortedr  r   r   r   rJ  r   r@   )r   r#  op	vars_keyss       r#   query_garmin_graphqlzGarmin.query_garmin_graphql	  s     %&&UYY''49 	 %&&FEIIk**0b6688999 	
 	<b)LLLz$6U  
 

$&&	r,   c                 :    t                               d           dS )zLog user out of session.z@Deprecated: Alternative is to delete the login tokens to logout.N)r   r   r   s    r#   logoutzGarmin.logout	
  s%    N	
 	
 	
 	
 	
r,   c                 t    | j          d}t                              d           |                     |          S )z$Return all available training plans.z/planszRequesting training plans.)r   r   r   r   r  s     r#   get_training_planszGarmin.get_training_plans
  s8    6>>>1222s###r,   plan_idc                     t          t          |          d          }| j         d| }t                              d|           |                     |          S )z,Return details for a specific training plan.r0  z/phased/z'Requesting training plan details for %sr3   r0   r   r   r   r   r   r0  r   s      r#   get_training_plan_by_idzGarmin.get_training_plan_by_id
  sT    ,S\\9EE6IIII>HHHs###r,   c                     t          t          |          d          }| j         d| }t                              d|           |                     |          S )z5Return details for a specific adaptive training plan.r0  z/fbt-adaptive/z0Requesting adaptive training plan details for %sr2  r3  s      r#    get_adaptive_training_plan_by_idz'Garmin.get_adaptive_training_plan_by_id
  sT    ,S\\9EE6OOgOOGQQQs###r,   )NNFNFr   )r  )NNNNNNNNNNN)r   rM  )r   rM  rM  )F)rM  rM  )NN)NNN)r   rJ   N)rj  T)r  r   r  )T)r  r  )r   )r   r  )r   N)r  r  r  r  r   r)   r   r   r   r   r   tupler   r   r   r   r   r   r   r  r  r  r  r0   r  r  r  r!  r(  r$  floatrL  r]  r`  re  rh  rn  rv  rx  r{  r  r  r  r  r   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r
  r  r  r  r  r!  r#  r%  r'  r)  r-  r/  r5  r=  r?  rC  rI  rM  rT  r]  r_  rp  ru  r  r  r  r  r  r  r  r  r  r  r   r  r~  r  bytesr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r"  r+  r-  r/  r4  r6  r   r,   r#   rC   rC   d   s       66 !#/3#E  E TzE  *E  	E 
 RW%,E  E  
E  E  E  E N-Ps -Pc -Pc -P -P -P -P^NS NC NC N N N N8_L _L3: _LsTz3QU:?U9V _L _L _L _LB  cN 69 	sCx       *sTz     t        ,s ,tCH~ , , , ,c d38n    &C Dc3h,@    " S#X    8S 8s 8tDcN7K 8 8 8 8t$ $C $ $T$sCx.=Q $ $ $ $0$ $S $ $d4S>>R $ $ $ $*$$"$	d38n	$ $ $ $2S T#s(^    :% %S#X % % % % 593 33'*Tz3	c3h3 3 3 32 %)*.*."&$("&#'(,&*,0 +P +P:+P +P T\	+P
 !4<+P !4<+P 4<+P T\+P 4<+P DL+P +P t|+P #T\+P T\+P 
c3h+P +P +P +P\ JLW WEkW,/WCFW	c3h$	W W W W@ *W *We*W *W 	*W
 *W 
c3h$	*W *W *W *WX3s 3S 3T#s(^ 3 3 3 33 3c3h 3 3 3 3
 
S 
S 
 
 
 
 c t d
    . 593 33'*Tz3	d38n	3 3 3 3"	$S 	$T$sCx.5I 	$ 	$ 	$ 	$  G GG G 	G
 G G 
c3hG G G GD 593 33'*Tz3	c3h3 3 3 3"
S 
 
c3h 
 
 
 
$S $T#s(^ $ $ $ $ (,&*"aJ aJ aJ aJ $J%	aJ
 *t#aJ aJ 
c3haJ aJ aJ aJL !% 	LF LFLF :LF Tz	LF
 
c3hLF LF LF LF\$ $S#X $ $ $ $$# $$sCx. $ $ $ $$3 $4S> $ $ $ $$ $S#X $ $ $ $$ $S#X $ $ $ $$ $S#X $ $ $ $$T#s(^ $ $ $ $$4S#X#7 $ $ $ $Kd4S>&: K K K K'T#s(^(< ' ' ' '>3# 3c 3d38n 3 3 3 33# 3c 3d38n 3 3 3 33C 3 3SRUX 3 3 3 3
3
3!$
3	c3h
3 
3 
3 
3
3
3!$
3	c3h
3 
3 
3 
33C 3DcN 3 3 3 3$S $T#s(^ $ $ $ $$ $S#X $ $ $ $3 3c3h 3 3 3 3$# $$sCx.4*? $ $ $ $$C $DcN $ $ $ $0C 0DcNT<Q 0 0 0 0f 593 33'*Tz3	c3h3 3 3 38 !%" 	-T -T:-T t-T Tz	-T
 
c3h-T -T -T -T^$ $c3h $ $ $ $$ $c3h $ $ $ $ 593 33'*Tz3	c3h3 3 3 34$T$sCx.1 $ $ $ $$S $T#s(^ $ $ $ $$T#s(^ $ $ $ $ EI( ((),(7:Tz(	d38n	( ( ( (*49    $d38n $ $ $ $.# . . . . #'	       Dj	 
 
c3h$s)	#       D$c $d38n $ $ $ $IS I I I I I III I 	I
 I 
I I I I&JS#X J3 J J J J
 > >  > 	 >
  >  >  > 
 >  >  >  >D
4S>D#8 
 
 
 
4S 4S 4 4 4 4l

3 

3 

 

 

 

 ##' $2 22 t2 Dj	2
 :2 
d38n	2 2 2 2p !"&3 33 3 	3
  3 
c3h3 3 3 3>$DcN $ $ $ $ DF' ''-0'=@'	d38n	' ' ' 'R$# $$sCx. $ $ $ $s tCH~     $3 $4S> $ $ $ $ EI +.=A	   (           t    *@)C" "" '" 
	" " " "2$s $tCH~ $ $ $ $$S $T#s(^ $ $ $ $$ $S#X $ $ $ $$ $S#X $ $ $ $$ $S#X $ $ $ $$3 $4S> $ $ $ $$	c3h$tCH~.	.$ $ $ $$ $S#X $ $ $ $ FJ3 33*-3?B3	c3h3 3 3 3$cCi $DcN $ $ $ $	3S3Y 	34S> 	3 	3 	3 	3 +/ $'	d38n	   6*-)	c3h   >*-)	c3h   :$$sCx. $ $ $ $$$sCx. $ $ $ $CC CDcN C C C C3 3# 3# 3S#X 3 3 3 3$C#I $$sCx. $ $ $ $"39 " " " " "Q cNT#Y6<Q	c3hQ Q Q Q($c $d38n $ $ $ $L$c $d38n $ $ $ $Ls tCH~    ,c d38n    ,S T#s(^    ,	$$'#I	$	c3h	$ 	$ 	$ 	$$3 $4S> $ $ $ $$$'*$	c3h$ $ $ $$tCH~ $ $ $ $$sCx. T#s(^    4
 
 
 
$DcN $ $ $ $$sSy $T#s(^ $ $ $ $$c	 $d3PS8n $ $ $ $ $ $r,   rC   c                       e Zd ZdZdS )r   z)Raised when communication ended in error.Nr  r  r  r  r   r,   r#   r   r   &
  s        3333r,   r   c                       e Zd ZdZdS )r   z#Raised when rate limit is exceeded.Nr;  r   r,   r#   r   r   *
  s        ----r,   r   c                       e Zd ZdZdS )r   z%Raised when authentication is failed.Nr;  r   r,   r#   r   r   .
  s        ////r,   r   c                       e Zd ZdZdS )r|  z7Raised when an invalid file format is passed to upload.Nr;  r   r,   r#   r|  r|  2
  s        AAAAr,   r|  )r   )r%   )3r  loggingr'   r   r   collections.abcr   r   r   r   r   enumr   r	   pathlibr
   typingr   r   r   	garth.excr   r   r   fitr   	getLoggerr  r   rG  r  r   r!   rX  r   r$   r0   r8  r+   r1   r3   r;   Responser   rA   rC   r   r   r   r   r|  r   r,   r#   <module>rH     s4   . .   				 				 $ $ $ $ $ $ 8 8 8 8 8 8 8 8 8 8 8 8                       4 4 4 4 4 4 4 4       ! ! ! ! ! !		8	$	$   * E]  C S c    . +2 ;$'5[     # 3 S     c s     I IS I I I I
H$5 $sCx.4:O    &$ &$ &$ &$ &$ &$ &$ &$DN4 4 4 4 49 4 4 4. . . . .	 . . .0 0 0 0 0y 0 0 0B B B B B) B B B B Br,   