Skip to content

Commit 918c8fd

Browse files
authored
Merge pull request #327 from Skydler/feature/workout-scheduling
Add workout scheduling
2 parents 411ba35 + b653ebf commit 918c8fd

2 files changed

Lines changed: 80 additions & 4 deletions

File tree

demo.py

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,10 @@ def __init__(self):
324324
"desc": "Count activities for current user",
325325
"key": "count_activities",
326326
},
327+
"s": {
328+
"desc": "Schedule a workout on a date (interactive)",
329+
"key": "scheduled_workout",
330+
},
327331
"v": {
328332
"desc": "Upload typed running workout (sample)",
329333
"key": "upload_running_workout",
@@ -2063,7 +2067,9 @@ def clean_step_ids(workout_segments):
20632067

20642068
except FileNotFoundError:
20652069
print(f"❌ File not found: {config.workoutfile}")
2066-
print("ℹ️ Please ensure the workout JSON file exists in the test_data directory")
2070+
print(
2071+
"ℹ️ Please ensure the workout JSON file exists in the test_data directory"
2072+
)
20672073
except json.JSONDecodeError as e:
20682074
print(f"❌ Invalid JSON format in {config.workoutfile}: {e}")
20692075
print("ℹ️ Please check the JSON file format")
@@ -2268,6 +2274,60 @@ def upload_hiking_workout_data(api: Garmin) -> None:
22682274
print(f"❌ Error uploading hiking workout: {e}")
22692275

22702276

2277+
def schedule_workout_data(api: Garmin) -> None:
2278+
"""Schedule a workout on a specific date."""
2279+
try:
2280+
workouts = api.get_workouts()
2281+
if not workouts:
2282+
print("ℹ️ No workouts found")
2283+
return
2284+
2285+
print("\nAvailable workouts (most recent):")
2286+
for i, workout in enumerate(workouts[:10]):
2287+
workout_id = workout.get("workoutId")
2288+
workout_name = workout.get("workoutName", "Unknown")
2289+
print(f" [{i}] {workout_name} (ID: {workout_id})")
2290+
2291+
try:
2292+
index_input = input(
2293+
f"\nEnter workout index (0-{min(9, len(workouts) - 1)}, or 'q' to cancel): "
2294+
).strip()
2295+
2296+
if index_input.lower() == "q":
2297+
print("❌ Cancelled")
2298+
return
2299+
2300+
workout_index = int(index_input)
2301+
if not (0 <= workout_index < min(10, len(workouts))):
2302+
print("❌ Invalid index")
2303+
return
2304+
2305+
selected_workout = workouts[workout_index]
2306+
workout_id = selected_workout["workoutId"]
2307+
workout_name = selected_workout.get("workoutName", "Unknown")
2308+
2309+
date_input = input(
2310+
f"Enter date to schedule '{workout_name}' (YYYY-MM-DD, default: today): "
2311+
).strip()
2312+
schedule_date = date_input if date_input else config.today.isoformat()
2313+
2314+
call_and_display(
2315+
api.schedule_workout,
2316+
workout_id,
2317+
schedule_date,
2318+
method_name="scheduled_workout",
2319+
api_call_desc=f"api.scheduled_workout({workout_id}, '{schedule_date}') - {workout_name}",
2320+
)
2321+
2322+
print("✅ Workout scheduled successfully!")
2323+
2324+
except ValueError:
2325+
print("❌ Invalid input")
2326+
2327+
except Exception as e:
2328+
print(f"❌ Error scheduling workout: {e}")
2329+
2330+
22712331
def get_scheduled_workout_by_id_data(api: Garmin) -> None:
22722332
"""Get scheduled workout by ID."""
22732333
try:
@@ -3605,8 +3665,8 @@ def execute_api_call(api: Garmin, key: str) -> None:
36053665
),
36063666
"get_activity_weather": lambda: get_activity_weather_data(api),
36073667
"get_activity_hr_in_timezones": lambda: get_activity_hr_timezones_data(api),
3608-
"get_activity_power_in_timezones": lambda: get_activity_power_timezones_data(
3609-
api
3668+
"get_activity_power_in_timezones": lambda: (
3669+
get_activity_power_timezones_data(api)
36103670
),
36113671
"get_cycling_ftp": lambda: get_cycling_ftp_data(api),
36123672
"get_activity_details": lambda: get_activity_details_data(api),
@@ -3624,6 +3684,7 @@ def execute_api_call(api: Garmin, key: str) -> None:
36243684
"get_scheduled_workout_by_id": lambda: get_scheduled_workout_by_id_data(
36253685
api
36263686
),
3687+
"scheduled_workout": lambda: schedule_workout_data(api),
36273688
"count_activities": lambda: call_and_display(
36283689
api.count_activities,
36293690
method_name="count_activities",

garminconnect/__init__.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2316,7 +2316,7 @@ def request_reload(self, cdate: str) -> dict[str, Any]:
23162316

23172317
return self.garth.post("connectapi", url, api=True).json()
23182318

2319-
def get_workouts(self, start: int = 0, limit: int = 100) -> dict[str, Any]:
2319+
def get_workouts(self, start: int = 0, limit: int = 100) -> list[dict[str, Any]]:
23202320
"""Return workouts starting at offset `start` with at most `limit` results."""
23212321
url = f"{self.garmin_workouts}/workouts"
23222322
start = _validate_non_negative_integer(start, "start")
@@ -2512,6 +2512,21 @@ def get_scheduled_workout_by_id(
25122512
logger.debug("Requesting scheduled workout by id %d", scheduled_workout_id)
25132513
return self.connectapi(url)
25142514

2515+
def schedule_workout(self, workout_id: int | str, date_str: str) -> dict[str, Any]:
2516+
"""Schedule a workout on a specific date in the Garmin calendar.
2517+
2518+
Args:
2519+
workout_id: The workout ID returned after uploading.
2520+
date_str: Target date in YYYY-MM-DD format.
2521+
2522+
"""
2523+
workout_id = _validate_positive_integer(int(workout_id), "workout_id")
2524+
date_str = _validate_date_format(date_str, "date_str")
2525+
url = f"{self.garmin_workouts_schedule_url}/{workout_id}"
2526+
logger.debug("Scheduling workout %s for %s", workout_id, date_str)
2527+
payload = {"date": date_str}
2528+
return self.garth.post("connectapi", url, json=payload, api=True).json()
2529+
25152530
def get_menstrual_data_for_date(self, fordate: str) -> dict[str, Any]:
25162531
"""Return menstrual data for date."""
25172532
fordate = _validate_date_format(fordate, "fordate")

0 commit comments

Comments
 (0)