Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
ignored_types: '["chore"]'
type_labels: '{"feat":"feature","fix":"fix","perf":"performance","refactor":"refactor","docs":"documentation","test":"test","build":"build","ci":"ci","breaking_change":"breaking change"}'
type_labels: '{"feat":"feature","fix":"fix","perf":"performance","refactor":"refactor","docs":"documentation","test":"test","build":"build","ci":"ci","breaking":"breaking change"}'

lint-and-build:
name: Lint and build
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ python -m gopro_api.cli pull MEDIA_ID ./out --height 720

## Configuration

`gopro_api.config` reads settings from the environment and from a `.env` file in the current working directory via **pydantic-settings**. The only required setting is **`GP_ACCESS_TOKEN`**.
`gopro_api.config` reads settings from the environment and from a `.env` file in the current working directory via **pydantic-settings**. Use `get_settings()` to access the memoized singleton; the only required setting is **`GP_ACCESS_TOKEN`**.

Example `.env`:

Expand Down Expand Up @@ -201,7 +201,7 @@ List fields in search params are serialized to comma-separated strings when you
| `gopro_api/api/async_gopro.py` | `AsyncGoProAPI` — async `search`, `download` |
| `gopro_api/api/models.py` | Pydantic request/response models |
| `gopro_api/api/__init__.py` | Re-exports `GoProAPI`, `AsyncGoProAPI` |
| `gopro_api/config.py` | pydantic-settings `Settings`, `GP_ACCESS_TOKEN` |
| `gopro_api/config.py` | pydantic-settings `Settings`, lazy `get_settings()` |
| `gopro_api/cli/` | `gopro-api` CLI |
| `setup.py` | Package metadata, dependencies, console entry point |

Expand Down
8 changes: 4 additions & 4 deletions gopro_api/api/async_gopro.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import aiohttp

from gopro_api.config import GP_ACCESS_TOKEN
from gopro_api.config import get_settings
from gopro_api.api.models import (
GoProMediaSearchParams,
GoProMediaDownloadResponse,
Expand All @@ -15,18 +15,18 @@ class AsyncGoProAPI:

Use as an async context manager so an ``aiohttp.ClientSession`` is opened
and closed around ``search`` and ``download``. Pass ``access_token`` to
override ``gopro_api.config.GP_ACCESS_TOKEN``.
override :func:`~gopro_api.config.get_settings`.
"""

def __init__(self, access_token: str | None = None, timeout: float = 10.0) -> None:
"""Create an async client.

Args:
access_token: ``gp_access_token`` cookie value; defaults to
``gopro_api.config.GP_ACCESS_TOKEN``.
:attr:`~gopro_api.config.Settings.gp_access_token` from settings.
timeout: Total client timeout in seconds for ``aiohttp``.
"""
self.access_token = access_token or GP_ACCESS_TOKEN
self.access_token = access_token or get_settings().gp_access_token
self._timeout = aiohttp.ClientTimeout(total=timeout)
self._session: aiohttp.ClientSession | None = None

Expand Down
8 changes: 4 additions & 4 deletions gopro_api/api/gopro.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import requests

from gopro_api.config import GP_ACCESS_TOKEN
from gopro_api.config import get_settings
from gopro_api.api.models import (
GoProMediaDownloadResponse,
GoProMediaSearchParams,
Expand All @@ -15,18 +15,18 @@ class GoProAPI:

Use as a context manager so a ``requests.Session`` is created and closed
around ``search`` and ``download``. Pass ``access_token`` to override
``gopro_api.config.GP_ACCESS_TOKEN``.
:func:`~gopro_api.config.get_settings`.
"""

def __init__(self, access_token: str | None = None, timeout: float = 10.0) -> None:
"""Create a sync client.

Args:
access_token: ``gp_access_token`` cookie value; defaults to
``gopro_api.config.GP_ACCESS_TOKEN``.
:attr:`~gopro_api.config.Settings.gp_access_token` from settings.
timeout: Per-request timeout in seconds passed to ``requests``.
"""
self.access_token = access_token or GP_ACCESS_TOKEN
self.access_token = access_token or get_settings().gp_access_token
self._timeout = timeout
self._session: requests.Session | None = None

Expand Down
4 changes: 2 additions & 2 deletions gopro_api/cli/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import typer
from rich.table import Table

from gopro_api.config import GP_ACCESS_TOKEN
from gopro_api.config import get_settings

_FIELD_LABELS: dict[str, str] = {
"type": "media",
Expand Down Expand Up @@ -36,7 +36,7 @@ def _require_token() -> None:
Raises:
typer.Exit: With code ``2`` if the token is missing.
"""
if not GP_ACCESS_TOKEN:
if not get_settings().gp_access_token:
typer.secho(
"error: GP_ACCESS_TOKEN is not set. "
"Add it to your environment or a .env file.",
Expand Down
4 changes: 2 additions & 2 deletions gopro_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def __init__(

Args:
access_token: ``gp_access_token`` cookie value; defaults to
``gopro_api.config.GP_ACCESS_TOKEN``.
:func:`~gopro_api.config.get_settings`.
timeout: Per-request HTTP timeout in seconds (API and CDN fetches).
page_size: Default page size for ``iter_nonempty_search_pages``.
max_items: Maximum rows returned by ``list_media_items``.
Expand Down Expand Up @@ -256,7 +256,7 @@ def __init__(

Args:
access_token: ``gp_access_token`` cookie value; defaults to
``gopro_api.config.GP_ACCESS_TOKEN``.
:func:`~gopro_api.config.get_settings`.
timeout: Total ``aiohttp`` client timeout in seconds.
page_size: Default page size for ``iter_nonempty_search_pages``.
max_items: Maximum rows returned by ``list_media_items``.
Expand Down
23 changes: 18 additions & 5 deletions gopro_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

from __future__ import annotations

from functools import lru_cache

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
"""Application settings from the process environment and optional ``.env`` file.

Values are read at instantiation; use ``gopro_api.config.settings`` or the
``GP_ACCESS_TOKEN`` alias for the token used by API clients and the CLI.
Values are read at instantiation; use :func:`get_settings` for the token used
by API clients and the CLI.

Attributes:
gp_access_token: GoPro cloud cookie value. Environment variable:
Expand All @@ -19,12 +21,23 @@ class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)

gp_access_token: str | None = None


settings = Settings()
@lru_cache
def get_settings() -> Settings:
"""Return the process-wide settings singleton.

The result is memoized. Call ``get_settings.cache_clear()`` before constructing
a new ``Settings`` instance when tests mutate the environment.

Returns:
Parsed :class:`Settings` for the current process.
"""
return Settings()


# Backward-compatible module-level alias used throughout the package.
GP_ACCESS_TOKEN = settings.gp_access_token
__all__ = ["Settings", "get_settings"]
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "gopro-api"
version = "0.0.8"
version = "0.0.9"
description = "Unofficial Python client for the GoPro cloud API (api.gopro.com): sync and async clients, Pydantic models, and a CLI."
readme = "README.md"
license = { file = "LICENSE" }
Expand Down
Loading