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:
- create a session
- send the user to
pickerUri - poll at the server-recommended interval until
mediaItemsSet, respectingtimeoutIn - page through the picked items
- download each one with an OAuth header and the right
baseUrlsuffix (=d,=dv,=wN-hN...) before the URLs expire after about 60 minutes - skip videos that are still processing
- 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()followspollIntervalandtimeoutInexactly and raisesPickerTimeout.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 (
.partand then an atomic rename), the extension is corrected fromContent-Type, thumbnails use resize and crop options, and unready videos are detected. - Concurrent async downloads via
download_all(..., concurrency=4). - Typed models (
PickingSession,PickedMediaItemwith 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 asSCOPE). pickerUristops working once the user presses Done. Create a new session to let them pick more.PickedMediaItem.idis stable across sessions, so use it to de-duplicate repeated imports.- For TVs and other limited-input devices, pass
request_id=str(uuid.uuid4())tocreate_sessionto 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.