Skip to main content

Unsplash Wrapper

Typed async Python client for the Unsplash API focused on the photo search endpoint.

Features:

  • Async HTTP via httpx
  • Pydantic models (requests & responses)
  • Fluent builder (UnsplashSearchParamsBuilder) OR direct kwargs
  • Returns a simple list[UnsplashPhoto]
  • Automatic retry of 429 (rate limit) responses

Installation

Requires Python 3.13+.

pip install unsplash-wrapper

Set your Unsplash access key (Create one in your Unsplash developer dashboard):

export UNSPLASH_API_KEY=your_access_key_here  # macOS/Linux
$env:UNSPLASH_API_KEY='your_access_key_here'  # PowerShell

Quick Start (async context manager)

from unsplash_wrapper import UnsplashClient, UnsplashSearchParamsBuilder

params = UnsplashSearchParamsBuilder().query("mountains").build()

async with UnsplashClient() as client:
    photos = await client.search_photos(params)

for photo in photos:
    print(photo.id, photo.url)

Without Context Manager

UnsplashClient can also be used without async with — it creates and closes an httpx.AsyncClient per call automatically:

from unsplash_wrapper import UnsplashClient, UnsplashSearchParamsBuilder

params = UnsplashSearchParamsBuilder().query("ocean").build()

client = UnsplashClient()
photos = await client.search_photos(params)

for photo in photos:
    print(photo.id, photo.url)

Sync Usage Helper

If you're not already inside an async context:

import asyncio
from unsplash_wrapper import UnsplashClient, UnsplashSearchParamsBuilder

def main() -> None:
    params = UnsplashSearchParamsBuilder().query("ocean").build()
    photos = asyncio.run(UnsplashClient().search_photos(params))
    for p in photos:
        print(p.id, p.url)

if __name__ == "__main__":
    main()

Search Parameter Builder

All builder methods are chainable. Pass the result of .build() to search_photos.

from unsplash_wrapper import (
    UnsplashClient,
    UnsplashSearchParamsBuilder,
)

params = (
    UnsplashSearchParamsBuilder()
    .query("sunset beach")
    .limit(20)
    .landscape_orientation()
    .high_quality()
    .order_by_latest()
    .page(2)
    .build()
)

async with UnsplashClient() as client:
    photos = await client.search_photos(params)

for photo in photos:
    print(photo.id, photo.description, photo.url)

Available builder methods:

Method Description
.query(str) Search query
.limit(int) Results per page (default: 10)
.page(int) Page number (default: 1)
.orientation(Orientation) Set orientation enum directly
.landscape_orientation() Shortcut for landscape
.portrait_orientation() Shortcut for portrait
.squarish_orientation() Shortcut for squarish
.content_filter(ContentFilter) Set filter enum directly
.high_quality() Shortcut for high content filter
.low_quality() Shortcut for low content filter
.order_by(OrderBy) Set order enum directly
.order_by_relevant() Shortcut for relevance ordering
.order_by_latest() Shortcut for latest ordering

Manual Params

from unsplash_wrapper import UnsplashSearchParams, UnsplashClient, Orientation

params = UnsplashSearchParams(query="minimal", per_page=5, orientation=Orientation.SQUARISH)

async with UnsplashClient() as client:
    photos = await client.search_photos(params)

Models Overview

UnsplashPhoto attributes:

id: str
description: str | None
alt_description: str | None
urls: UnsplashUrls (raw/full/regular/small/thumb)
user: UnsplashUser (username, name, portfolio_url, ...)
width, height, color, likes, created_at
url (property -> regular URL string)

Error Handling

Exceptions from unsplash_wrapper.exceptions:

  • UnsplashAuthenticationException (401)
  • UnsplashNotFoundException (404)
  • UnsplashRateLimitException (429) — includes retry_after if provided
  • UnsplashServerException (5xx)
  • UnsplashClientException (other 4xx)
  • UnsplashTimeoutException (request timeout)
from unsplash_wrapper import (
    UnsplashClient,
    UnsplashRateLimitException,
    UnsplashAuthenticationException,
)

client = UnsplashClient()
try:
    photos = await client.search_photos(query="forest")
except UnsplashRateLimitException as e:
    msg = f"retry after {e.retry_after}s" if e.retry_after else "no retry window"
    print("Rate limited:", msg)
except UnsplashAuthenticationException:
    print("Invalid API key configured")

Retry Behavior

429 responses are retried up to 3 times (delays: 1s → 2s → 4s). Other failures propagate immediately.

Logging

Logger name: unsplash_wrapper.UnsplashClient

import logging
logging.basicConfig(level=logging.INFO)

Development

Run tests:

uv run pytest -q

Type check (if mypy configured):

mypy unsplash_wrapper

Metadata

Release files for unsplash-wrapper 0.3.0

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

Source distribution (sdist)

Source distribution for unsplash-wrapper 0.3.0
File Size Uploaded Provenance Actions
unsplash_wrapper-0.3.0.tar.gz 36.1 kB Without provenance Details for unsplash_wrapper-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for unsplash-wrapper 0.3.0
File Interpreter ABI Platform Actions
unsplash_wrapper-0.3.0-py3-none-any.whl Python 3 none any Details for unsplash_wrapper-0.3.0-py3-none-any.whl

Total release size: 45.0 kB

Release files / unsplash_wrapper-0.3.0.tar.gz

Download URL unsplash_wrapper-0.3.0.tar.gz
Size 36.1 kB
Tags Source
SHA-256 checksum
How to use checksums
373fe2d0b88806c7b3f41507c3604c64d407169a62d3fe27bd18bb5ca02da233
BLAKE2b-256 checksum
How to use checksums
5b6936be0319305b8538e5d4ac5da14740b6ef286666632d5f698f1b45663da0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.2

Release files / unsplash_wrapper-0.3.0-py3-none-any.whl

Download URL unsplash_wrapper-0.3.0-py3-none-any.whl
Size 8.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
500bacc06260410edf3a79af2d7084f3a4bdb3557d004159e761e12fc34c2bd6
BLAKE2b-256 checksum
How to use checksums
13d7d3a40031a02f1fd2582a14259fdd3b087c237fd5669cf15bf341107ac000
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.2

Security for unsplash-wrapper 0.3.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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.

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