From 195699cd3c59188c73b1cb2d15cfb175b6707e7c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 12 Aug 2026 01:16:52 +0000 Subject: [PATCH] fix: make CapturedRange end date inclusive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Serialize calendar dates only and advance the exclusive API end bound by one day so same-day filters (e.g. 07–07) cover the full day instead of collapsing to an empty midnight-to-midnight window. Co-authored-by: Welbert Castro --- README.md | 6 +++--- docs/cli.md | 2 +- docs/getting-started.md | 8 ++++---- gopro_api/api/models.py | 22 ++++++++++++++++------ gopro_api/client.py | 16 ++++++++-------- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 8fc9169..7de390d 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Global **`--timeout`** (seconds, default **`60`**) applies to API calls and to * Run without an installed script: ```bash -python -m gopro_api.cli search --start 2026-03-01 --end 2026-03-02 +python -m gopro_api.cli search --start 2026-03-01 --end 2026-03-03 python -m gopro_api.cli info MEDIA_ID python -m gopro_api.cli pull MEDIA_ID ./out python -m gopro_api.cli pull MEDIA_ID ./out --height 720 @@ -143,7 +143,7 @@ async def main() -> None: params = GoProMediaSearchParams( captured_range=CapturedRange( start=datetime.fromisoformat("2026-03-01"), - end=datetime.fromisoformat("2026-03-02"), + end=datetime.fromisoformat("2026-03-03"), ), per_page=50, page=1, @@ -173,7 +173,7 @@ def main() -> None: params = GoProMediaSearchParams( captured_range=CapturedRange( start=datetime.fromisoformat("2026-03-01"), - end=datetime.fromisoformat("2026-03-02"), + end=datetime.fromisoformat("2026-03-03"), ), per_page=50, page=1, diff --git a/docs/cli.md b/docs/cli.md index fd6a363..2a39eac 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -156,7 +156,7 @@ gopro-api auth --tsv ## Running without an installed entry point ```bash -python -m gopro_api.cli search --start 2026-03-01 --end 2026-03-02 +python -m gopro_api.cli search --start 2026-03-01 --end 2026-03-03 python -m gopro_api.cli info MEDIA_ID python -m gopro_api.cli pull MEDIA_ID ./out python -m gopro_api.cli pull MEDIA_ID ./out --height 720 diff --git a/docs/getting-started.md b/docs/getting-started.md index 766cce0..4fdb65d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -54,8 +54,8 @@ gopro-api auth After installing, `gopro-api` is available on your `PATH`: ```bash -# List media captured on a specific day -gopro-api search --start 2026-03-01 --end 2026-03-02 +# List media captured on a specific day (start and end are inclusive) +gopro-api search --start 2026-03-01 --end 2026-03-01 # Show download metadata for a single media item gopro-api info MEDIA_ID @@ -79,7 +79,7 @@ from gopro_api.api.models import CapturedRange, GoProMediaSearchParams params = GoProMediaSearchParams( captured_range=CapturedRange( start=datetime.fromisoformat("2026-03-01"), - end=datetime.fromisoformat("2026-03-02"), + end=datetime.fromisoformat("2026-03-03"), ), per_page=50, page=1, @@ -104,7 +104,7 @@ from gopro_api.api.models import CapturedRange, GoProMediaSearchParams params = GoProMediaSearchParams( captured_range=CapturedRange( start=datetime.fromisoformat("2026-03-01"), - end=datetime.fromisoformat("2026-03-02"), + end=datetime.fromisoformat("2026-03-03"), ), per_page=50, page=1, diff --git a/gopro_api/api/models.py b/gopro_api/api/models.py index ca3e688..4cd606d 100644 --- a/gopro_api/api/models.py +++ b/gopro_api/api/models.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import datetime +from datetime import date, datetime, timedelta from typing import Any, List, Optional from pydantic import BaseModel, ConfigDict, Field, field_serializer, model_serializer @@ -44,11 +44,13 @@ class CapturedRange(BaseModel): """Inclusive capture date window used in search queries. Serialized to a single ``captured_range`` query string with fixed - ``T00:00:00.000Z`` suffixes, as required by the cloud API. + ``T00:00:00.000Z`` suffixes, as required by the cloud API. The API treats + that range as half-open ``[start, end)``, so the serialized end date is the + calendar day after ``end`` (unless ``end`` is already ``date.max``). Attributes: - start: Range start (date portion used in the wire format). - end: Range end (date portion used in the wire format). + start: Inclusive range start (date portion used in the wire format). + end: Inclusive range end (date portion used in the wire format). """ start: datetime @@ -58,12 +60,20 @@ class CapturedRange(BaseModel): def _serialize_captured_range(self) -> str: """Serialize this range for the ``captured_range`` query parameter. + Uses calendar dates only and advances the end by one day so a same-day + filter such as ``2024-01-07``…``2024-01-07`` becomes + ``2024-01-07T00:00:00.000Z,2024-01-08T00:00:00.000Z``. + Returns: Comma-separated ISO date pair with ``Z`` UTC suffixes. """ + start_day = self.start.date() + end_day = self.end.date() + if end_day < date.max: + end_day = end_day + timedelta(days=1) return ( - f"{self.start.isoformat()}T00:00:00.000Z," - f"{self.end.isoformat()}T00:00:00.000Z" + f"{start_day.isoformat()}T00:00:00.000Z," + f"{end_day.isoformat()}T00:00:00.000Z" ) diff --git a/gopro_api/client.py b/gopro_api/client.py index b069b01..903e225 100644 --- a/gopro_api/client.py +++ b/gopro_api/client.py @@ -142,8 +142,8 @@ def iter_nonempty_search_pages( """Yield search result pages until one returns an empty ``_embedded.media``. Args: - start_date: Capture range start (inclusive semantics per API). - end_date: Capture range end. + start_date: Capture range start (inclusive calendar day). + end_date: Capture range end (inclusive calendar day). per_page: Items per page; defaults to ``self.page_size``. start_page: First page number to request (1-indexed). @@ -170,8 +170,8 @@ def list_media_items( """Collect media rows across pages up to ``max_items``. Args: - start_date: Capture range start. - end_date: Capture range end. + start_date: Capture range start (inclusive calendar day). + end_date: Capture range end (inclusive calendar day). Returns: Up to ``self.max_items`` ``GoProMediaSearchItem`` instances. @@ -356,8 +356,8 @@ async def iter_nonempty_search_pages( """Yield search pages until one returns an empty ``_embedded.media``. Args: - start_date: Capture range start (inclusive semantics per API). - end_date: Capture range end. + start_date: Capture range start (inclusive calendar day). + end_date: Capture range end (inclusive calendar day). per_page: Items per page; defaults to ``self.page_size``. start_page: First page number to request (1-indexed). @@ -384,8 +384,8 @@ async def list_media_items( """Collect media rows across pages up to ``max_items``. Args: - start_date: Capture range start. - end_date: Capture range end. + start_date: Capture range start (inclusive calendar day). + end_date: Capture range end (inclusive calendar day). Returns: Up to ``self.max_items`` ``GoProMediaSearchItem`` instances.