Skip to main content

google-photos-picker

A Python client for the Google Photos Picker API, the supported way for apps to let users choose photos and videos from their Google Photos library.

On 31 March 2025 Google removed the Library API scopes that could read a user's whole library (photoslibrary.readonly, photoslibrary, photoslibrary.sharing). Apps that imported photos with those scopes must move to the Picker API. Google publishes no Python client for it.

The flow looks simple, but you have to get these steps right:

  1. create a session
  2. send the user to pickerUri
  3. poll at the server-recommended interval until mediaItemsSet, respecting timeoutIn
  4. page through the picked items
  5. download each one with an OAuth header and the right baseUrl suffix (=d, =dv, =wN-hN...) before the URLs expire after about 60 minutes
  6. skip videos that are still processing
  7. delete the session so you don't hit per-user limits

This library does all of that:

  • Sync and async clients that share one API surface (PickerClient, AsyncPickerClient) and run on Python 3.9+.
  • wait() follows pollInterval and timeoutIn exactly and raises PickerTimeout.
  • session() context manager: sessions are always deleted, even if your code raises.
  • Downloads: streamed to a file, a directory or a file object. Filenames are safe and unique, partial files never linger (.part and then an atomic rename), the extension is corrected from Content-Type, thumbnails use resize and crop options, and unready videos are detected.
  • Concurrent async downloads via download_all(..., concurrency=4).
  • Typed models (PickingSession, PickedMediaItem with photo and video metadata), plus OAuth 2.0 with PKCE and refresh, and retries with backoff.
  • A CLI: google-photos-picker pick --out ./photos --qr. Scan the QR code on your phone, pick, and the files appear on disk.

Install

pip install google-photos-picker
pip install "google-photos-picker[qr]"   # terminal QR codes for phone hand-off

CLI

export GOOGLE_PHOTOS_PICKER_CLIENT_ID=...apps.googleusercontent.com
export GOOGLE_PHOTOS_PICKER_CLIENT_SECRET=...
google-photos-picker login --client-secrets client_secret_xxx.json   # JSON from the Cloud console
google-photos-picker pick --out ./holiday --max-items 50 --qr
google-photos-picker pick --out ./thumbs --max-size 1024     # resized photos, full videos

Library

from google_photos_picker import PickerClient, OAuthCredentials

client = PickerClient(OAuthCredentials.load("token.json"))

with client.session(max_items=20) as session:           # deleted automatically
    print("Pick here:", session.picker_uri)              # or session.picker_uri_autoclose
    session = client.wait(session, timeout=600)
    for item in client.list_items(session):
        print(item.type, item.filename, item.width, item.height, item.create_time)
    paths = client.download_all(client.list_items(session), "imports/")

Or as one call:

paths = client.pick("imports/", max_items=20, show=lambda s: send_link_to_user(s.picker_uri))

Media options

client.read(item)                                  # original bytes (=d / =dv)
client.read(item, width=512, height=512, crop=True) # square thumbnail (=w512-h512-c)
client.download(item, "out/", width=2048, height=2048)
with client.open(item) as r:                        # stream it yourself
    for chunk in r.iter_bytes(): ...
media_url(item, width=320, height=180, video_overlay=False)  # needs an auth header to fetch!

Videos whose processingStatus isn't READY raise VideoNotReady when you ask for the original. Thumbnails still work. download_all skips them by default and reports them through on_item(item, None).

Web apps (async)

from google_photos_picker import AsyncPickerClient

@app.post("/import/start")
async def start(user=Depends(current_user)):
    async with AsyncPickerClient(user.google_creds) as c:
        s = await c.create_session(max_items=100)
    await db.save_picker_session(user.id, s.id)
    return {"pickerUri": s.picker_uri_autoclose}      # open in a new tab (cannot be iframed)

@app.get("/import/status")
async def status(user=Depends(current_user)):
    async with AsyncPickerClient(user.google_creds) as c:
        s = await c.get_session(await db.picker_session(user.id))
        if not s.media_items_set:
            return {"done": False, "pollAfter": s.polling.poll_interval if s.polling else 5}
        paths = await c.download_all(c.list_items(s), f"uploads/{user.id}", concurrency=6)
        await c.delete_session(s)
        return {"done": True, "count": len(paths)}

examples/fastapi_app.py is a complete runnable version with the browser side included.

Notes

  • Scope: https://www.googleapis.com/auth/photospicker.mediaitems.readonly (exported as SCOPE).
  • pickerUri stops working once the user presses Done. Create a new session to let them pick more.
  • PickedMediaItem.id is stable across sessions, so use it to de-duplicate repeated imports.
  • For TVs and other limited-input devices, pass request_id=str(uuid.uuid4()) to create_session to get the streamlined device flow.

Status

Version 0.1, alpha. Built from the official REST reference and covered by 27 unit tests on Python 3.9–3.13 against mocked HTTP; the full flow (OAuth, session, polling, picking, download) has also been run end to end against the live API with a real account (October 2026). This is a community project, not affiliated with or endorsed by Google.

License

Apache-2.0

Metadata

Release files for google-photos-picker 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for google-photos-picker 0.1.1
File Size Uploaded Provenance Actions
google_photos_picker-0.1.1.tar.gz 31.0 kB With provenance Details for google_photos_picker-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for google-photos-picker 0.1.1
File Interpreter ABI Platform Actions
google_photos_picker-0.1.1-py3-none-any.whl Python 3 none any Details for google_photos_picker-0.1.1-py3-none-any.whl

Total release size: 60.8 kB

Release files / google_photos_picker-0.1.1.tar.gz

Download URL google_photos_picker-0.1.1.tar.gz
Size 31.0 kB
Tags Source
SHA-256 checksum
How to use checksums
9fcd467d3e5b21d47fa357d8aea8e892c909b2dcddadf31dfa4ff6f0b8c50bbe
BLAKE2b-256 checksum
How to use checksums
2f64e7dfdceffd0659412a8001f758e601ae44cf735c5f8046dc2ce33d3a8384
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Release files / google_photos_picker-0.1.1-py3-none-any.whl

Download URL google_photos_picker-0.1.1-py3-none-any.whl
Size 29.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fa6f76496eac5892da7d0f0eda8fa485c7d2805f10596742b8df40360941322c
BLAKE2b-256 checksum
How to use checksums
ea3a3305e5f037aa0ac6808201ece50d0b5745592a04f7855f9c6d442c1d1677
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Security for google-photos-picker 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for google_photos_picker-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page