Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,22 @@ club_stats = client.get_golf_club_stats()
user_stats = client.get_golf_user_stats()
```

### Activity Photos

Photos attached to an activity are available inside the `get_activity()`
response at `metadataDTO.activityImages`; the `get_activity_images()`
helper returns that list directly:

```python
# [{imageId, url, photoDate}, ...] or [] when the activity has no photos
images = client.get_activity_images(activity_id)
```

The `url` values are presigned S3 links that expire after 24 hours, so
download the images promptly instead of storing the URLs. The `hasImages`
flag in `get_activities()` results is not a reliable indicator that an
activity has photos attached.

### Additional Resources
- **Simple Example**: [example.py](https://raw.githubusercontent.com/cyberjunky/python-garminconnect/master/example.py) - Getting started guide
- **Comprehensive Demo**: [demo.py](https://raw.githubusercontent.com/cyberjunky/python-garminconnect/master/demo.py) - All 154+ API methods
Expand Down
16 changes: 16 additions & 0 deletions garminconnect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3388,6 +3388,22 @@ def get_activity_details(

return self.connectapi(url, params=params)

def get_activity_images(self, activity_id: int | str) -> list[dict[str, Any]]:
"""Return the photos attached to an activity.

Photos are surfaced in ``metadataDTO.activityImages`` of the
`get_activity` response; there is no separate images endpoint.
Each entry contains ``imageId``, ``url``, and ``photoDate``.

Note: the ``url`` values are presigned S3 links
(``X-Amz-Expires=86400``) that expire after 24 hours, so download
the images promptly rather than storing the URLs. The
``hasImages`` flag in `get_activities` results is not a reliable
indicator that an activity has photos attached.
"""
activity = self.get_activity(str(activity_id)) or {}
return (activity.get("metadataDTO") or {}).get("activityImages") or []

def get_activity_exercise_sets(self, activity_id: int | str) -> dict[str, Any]:
"""Return activity exercise sets."""
activity_id = str(_validate_positive_integer(int(activity_id), "activity_id"))
Expand Down
37 changes: 37 additions & 0 deletions tests/test_garmin_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1739,6 +1739,43 @@ def test_get_available_badges_sets_exclusive_badge_flag(
params = mock.call_args.kwargs["params"]
assert params == {"showExclusiveBadge": "true"}

def test_get_activity_images_returns_images_from_metadata(
self, garmin: garminconnect.Garmin
):
images = [
{"imageId": "img-1", "url": "https://s3.example/1.jpg", "photoDate": None},
{"imageId": "img-2", "url": "https://s3.example/2.jpg", "photoDate": None},
]
payload = {"activityId": 12345, "metadataDTO": {"activityImages": images}}
with patch.object(garmin, "get_activity", return_value=payload) as mock:
result = garmin.get_activity_images(12345)

mock.assert_called_once_with("12345")
assert result == images

def test_get_activity_images_returns_empty_without_metadata(
self, garmin: garminconnect.Garmin
):
with patch.object(garmin, "get_activity", return_value={"activityId": 1}):
assert garmin.get_activity_images(1) == []

with patch.object(garmin, "get_activity", return_value={"metadataDTO": None}):
assert garmin.get_activity_images(1) == []

with patch.object(
garmin,
"get_activity",
return_value={"metadataDTO": {"activityImages": None}},
):
assert garmin.get_activity_images(1) == []

def test_get_activity_images_returns_empty_on_none_response(
self, garmin: garminconnect.Garmin
):
# A successful response with a JSON null body parses to None.
with patch.object(garmin, "get_activity", return_value=None):
assert garmin.get_activity_images(1) == []

def test_get_morning_training_readiness_picks_after_wakeup_entry(
self, garmin: garminconnect.Garmin
):
Expand Down