State¶
Session persistence — protocol, models, and built-in storage backends.
See the Backends guide for usage instructions, configuration, and collection schemas for each backend.
Base protocol and models¶
q2google.state.base
¶
Abstract persistence protocol and JSON-serializable session models.
Defines :class:SyncStateBackend, document types for staged sync, and helpers such as
:func:new_session.
BatchState(batch_index: int, file_names: list[str], status: StageStatus = 'pending', responses_json: list[str] | None = None, error: ErrorRecord | None = None)
dataclass
¶
Tracks one mediaItems:batchCreate HTTP batch within the create stage.
Attributes:
| Name | Type | Description |
|---|---|---|
batch_index |
int
|
Zero-based index among create batches for this session. |
file_names |
list[str]
|
Filenames included in this batch (aligned with API order). |
status |
StageStatus
|
Batch-level lifecycle state. |
responses_json |
list[str] | None
|
Optional serialized per-response JSON strings after success. |
error |
ErrorRecord | None
|
Populated when the batch fails as a whole. |
from_dict(data: dict[str, Any]) -> BatchState
classmethod
¶
Deserialize batch metadata from persisted JSON.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
Mapping with |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Reconstructed |
BatchState
|
class: |
Source code in q2google/state/base.py
to_dict() -> dict[str, Any]
¶
Serialize this batch to a plain dict.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
JSON-compatible mapping. |
Source code in q2google/state/base.py
ErrorRecord
¶
Bases: TypedDict
Structured error payload stored on an item or batch.
Attributes:
| Name | Type | Description |
|---|---|---|
error_type |
str
|
Exception type name. |
message |
str
|
Human-readable error message. |
attempt |
int
|
Monotonic attempt counter for retries. |
updated_at |
str
|
ISO 8601 timestamp when the record was written. |
ItemState(file_name: str, media_id: str | None = None, download_url: str | None = None, discovery_status: StageStatus = 'pending', transfer_status: StageStatus = 'pending', create_status: ItemCreateStatus = 'pending', upload_token: str | None = None, errors: dict[str, ErrorRecord] = dict())
dataclass
¶
Per-file progress within a sync session.
Attributes:
| Name | Type | Description |
|---|---|---|
file_name |
str
|
GoPro logical filename used as map key in :class: |
media_id |
str | None
|
Optional remote identifier when known. |
download_url |
str | None
|
Resolved CDN URL after discovery. |
discovery_status |
StageStatus
|
Lifecycle state for URL resolution. |
transfer_status |
StageStatus
|
Lifecycle state for download + upload to Google. |
create_status |
ItemCreateStatus
|
Lifecycle state for |
upload_token |
str | None
|
Finalized resumable upload token before library registration. |
errors |
dict[str, ErrorRecord]
|
Map of stage key (e.g. |
from_dict(data: dict[str, Any]) -> ItemState
classmethod
¶
Deserialize from a dict produced by :meth:to_dict or legacy JSON.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
Mapping with required |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Reconstructed |
ItemState
|
class: |
Source code in q2google/state/base.py
to_dict() -> dict[str, Any]
¶
Serialize this item to a plain dict suitable for JSON.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
JSON-compatible mapping for persistence layers. |
Source code in q2google/state/base.py
SessionState(schema_version: int = 1, session_id: str = '', created_at: str = '', updated_at: str = '', start_date_iso: str = '', end_date_iso: str = '', batch_size: int = 50, stages: dict[StageKey, StageStatus] = (lambda: {'discovery': 'pending', 'transfer': 'pending', 'create': 'pending'})(), items: dict[str, ItemState] = dict(), batches: dict[str, BatchState] = dict())
dataclass
¶
Full persisted session for discovery → transfer → create.
Attributes:
| Name | Type | Description |
|---|---|---|
schema_version |
int
|
Document format version for migrations. |
session_id |
str
|
Stable identifier used as storage key. |
created_at |
str
|
ISO timestamp when the session was first created. |
updated_at |
str
|
ISO timestamp last updated via :meth: |
start_date_iso |
str
|
Capture window start (ISO string). |
end_date_iso |
str
|
Capture window end (ISO string). |
batch_size |
int
|
Transfer-stage batch size chosen at session creation. |
stages |
dict[StageKey, StageStatus]
|
High-level stage keys mapped to coarse status. |
items |
dict[str, ItemState]
|
Filename-keyed :class: |
batches |
dict[str, BatchState]
|
String-keyed batch index → :class: |
from_dict(data: dict[str, Any]) -> SessionState
classmethod
¶
Deserialize session JSON produced by :meth:to_dict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
Top-level session mapping. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Reconstructed |
SessionState
|
class: |
Source code in q2google/state/base.py
to_dict() -> dict[str, Any]
¶
Serialize the full session for JSON or document databases.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Nested plain dict suitable for :func: |
Source code in q2google/state/base.py
SyncStateBackend
¶
Bases: Protocol
Storage backend for :class:SessionState.
load(session_id: str) -> SessionState | None
¶
Load a session by id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
Same key passed to :func: |
required |
Returns:
| Type | Description |
|---|---|
SessionState | None
|
Parsed state, or |
save(state: SessionState) -> None
¶
Atomically persist state (semantics defined by the implementation).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
SessionState
|
Complete session document to store. |
required |
Raises:
| Type | Description |
|---|---|
OSError
|
Implementations may propagate IO failures from the storage layer. |
Source code in q2google/state/base.py
new_session(session_id: str, *, start_date_iso: str, end_date_iso: str, batch_size: int) -> SessionState
¶
Create an empty session with timestamps and stage defaults initialized.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
External key used for persistence lookups. |
required |
start_date_iso
|
str
|
Capture window start as ISO string. |
required |
end_date_iso
|
str
|
Capture window end as ISO string. |
required |
batch_size
|
int
|
Transfer batch size recorded for later stages. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
New |
SessionState
|
class: |
Source code in q2google/state/base.py
JSON file backend¶
q2google.state.local
¶
Filesystem-backed :class:~q2google.state.base.SyncStateBackend.
Stores each session as a directory tree with one JSON file per item and one per batch,
making concurrent writes to different items safe by construction. Each individual file
is published atomically via a temp file and :func:os.replace.
Layout::
{root}/
{session_id}/
meta.json # session metadata + stages (no items, no batches)
items/
{safe_file_name}.json # one file per ItemState
batches/
{batch_key}.json # one file per BatchState
Legacy flat-file sessions ({session_id}.json) written by older versions of this
module are still readable; load detects and falls back to that format transparently.
JsonFileBackend(root: str | Path)
¶
Store each session under {root}/{session_id}/ as a directory of JSON files.
Each :class:~q2google.state.base.ItemState and
:class:~q2google.state.base.BatchState is written to its own file so that
concurrent writers updating different items never conflict. Session-level metadata
(stages, timestamps) lives in meta.json and is still subject to last-write-wins
semantics, but stage transitions are sequential in the current orchestrator so this
is not a practical concern.
Create the backend and ensure root exists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str | Path
|
Directory that will contain per-session subdirectories. |
required |
Source code in q2google/state/local.py
load(session_id: str) -> SessionState | None
¶
Load SessionState for session_id from disk.
Falls back to the legacy flat-file format ({session_id}.json) when the
session directory does not exist, so sessions written by older versions of this
module remain readable without any migration step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
Session key used when saving. |
required |
Returns:
| Type | Description |
|---|---|
SessionState | None
|
Parsed state, or |
Raises:
| Type | Description |
|---|---|
JSONDecodeError
|
If any JSON file on disk is malformed. |
Source code in q2google/state/local.py
save(state: SessionState) -> None
¶
Persist state by writing metadata, items, and batches to separate files.
Each file is written atomically. Writers updating different items never conflict because they target distinct paths.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
SessionState
|
Complete session document to store. |
required |
Raises:
| Type | Description |
|---|---|
OSError
|
On failure to write any individual file. |
Source code in q2google/state/local.py
MongoDB backend¶
q2google.state.mongo
¶
MongoDB-backed :class:~q2google.state.base.SyncStateBackend.
Stores session state across three collections that mirror the filesystem layout used by
:class:~q2google.state.local.JsonFileBackend:
- sessions — one document per session containing metadata and stage statuses.
- items — one document per
(session_id, file_name)pair. - batches — one document per
(session_id, batch_index)pair.
The database name is parsed from the URI path component
(mongodb://host:27017/q2google → database q2google).
When the path is absent or /, the name defaults to q2google.
Requires the pymongo package (pip install q2google[mongo]).
Layout::
<database>/
sessions { session_id, schema_version, created_at, updated_at,
start_date_iso, end_date_iso, batch_size, stages }
items { session_id, file_name, media_id, download_url,
discovery_status, transfer_status, create_status,
upload_token, errors }
batches { session_id, batch_index, file_names, status,
responses_json, error }
MongoBackend(uri: str)
¶
Store each session across three MongoDB collections.
Each :class:~q2google.state.base.ItemState and
:class:~q2google.state.base.BatchState is upserted to its own document,
matching the per-file isolation of :class:~q2google.state.local.JsonFileBackend.
Session-level metadata lives in the sessions collection.
Indexes are created lazily the first time :meth:save is called on a new instance,
ensuring that the collections are usable without a separate setup step.
Create the backend and connect to MongoDB.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
uri
|
str
|
MongoDB connection string. The database name is parsed from the URI
path component; defaults to |
required |
Raises:
| Type | Description |
|---|---|
ImportError
|
When |
Source code in q2google/state/mongo.py
load(session_id: str) -> SessionState | None
¶
Load SessionState for session_id from MongoDB.
Fetches the session metadata document from sessions, then all item
documents from items and all batch documents from batches for the
same session_id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
Session key used when saving. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Reconstructed |
SessionState | None
|
class: |
SessionState | None
|
no session document exists for |
Source code in q2google/state/mongo.py
save(state: SessionState) -> None
¶
Persist state by upserting documents into all three collections.
Each item and batch is upserted independently, so concurrent writers updating different items never conflict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
SessionState
|
Complete session document to store. |
required |