q2google — architecture¶
Overview¶
q2google moves assets from GoPro cloud (gopro-api / AsyncGoProClient) into Google Photos Library (resumable upload + mediaItems:batchCreate). Orchestration lives in q2google.sync; Google HTTP details live under q2google.gphotos; a thin facade is q2google.photos.
Module map¶
| Path | Role |
|---|---|
q2google/config.py |
Q2GoogleSettings + get_settings() — env / .env defaults for CLI and library. |
q2google/cli.py |
Typer entrypoint; merges flags with get_settings(), wires clients and sync. |
q2google/sync.py |
GoProToPhotosSync — loads session, runs stages, logs results. |
q2google/stages/ |
DiscoveryStage, TransferStage, CreateStage — one class per pipeline step. |
q2google/photos.py |
GooglePhotosClient + GooglePhotoLibraryPort — chunk upload and batched batchCreate. |
q2google/gphotos/ |
Low-level Library v1 HTTP (GooglePhotosAPI), OAuth (GooglePhotosOAuth), Pydantic models. |
q2google/state/base.py |
SessionState, ItemState, SyncStateBackend protocol — persistence contract. |
q2google/state/local.py |
JsonFileBackend — directory-tree backend; each session is a subdirectory containing meta.json, items/*.json, and batches/*.json. Reads legacy flat-file sessions transparently. |
q2google/state/mongo.py |
MongoBackend — MongoDB backend; distributes each session across three collections (sessions, items, batches). Requires pymongo (pip install q2google[mongo]). |
q2google/state/__init__.py |
build_backend(cfg) — factory that parses cfg.state_uri scheme and returns the matching SyncStateBackend; defaults to JsonFileBackend when state_uri is unset. |
Sync pipeline (sync_date_range)¶
sync_date_range requires a configured state_backend and a session_id. The run is split into discovery, transfer, and create stages. State is loaded/saved through SyncStateBackend.load / save on SessionState (JSON-serializable via to_dict / from_dict).
sequenceDiagram
participant Caller
participant Sync as GoProToPhotosSync
participant GoPro as AsyncGoProClient
participant Photos as GooglePhotosClient
participant Store as SyncStateBackend
Caller->>Sync: sync_date_range(start, end, session_id)
Sync->>Store: load(session_id)
Store-->>Sync: SessionState or new
Note over Sync: discovery
Sync->>GoPro: list_media_items, get_download_url
Sync->>Store: save(state)
Note over Sync: transfer
Sync->>Photos: upload_file_path per item
Sync->>Store: save(state)
Note over Sync: create
Sync->>Photos: create_media_items_from_upload_sessions
Sync->>Store: save(state)
Sync-->>Caller: list of batch create responses
Extension: custom SyncStateBackend¶
Implement the protocol:
from q2google import SessionState, SyncStateBackend
class CustomBackend:
def load(self, session_id: str) -> SessionState | None:
...
def save(self, state: SessionState) -> None:
...
SessionState.to_dict() / from_dict() produce a plain dict suitable for any document store. No changes to GoProToPhotosSync are required.
Pass the instance directly or route through build_backend() by registering the new scheme there. See the Backends guide for a full walkthrough.
Documentation conventions¶
Public modules, classes, and functions use a one-line summary, optional narrative, and structured
sections (Args, Returns, Raises, Yields, Attributes) where they add clarity.
Tooling¶
- UV —
uv sync,uv run q2google …,uv run pytest - Ruff — lint + format (
task lint,task format) - pytest —
tests/(task test)