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) — includesretry_afterif providedUnsplashServerException(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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.