python-sanity
Python client for Sanity.io CMS HTTP API. Sanity is a hosted CMS solution for content management. This project is not affiliated with Sanity.io and is a third-party package.
ℹ️ Note: This package is an active fork of the original project at OmniPro-Group/sanity-python.
Install
Available on pypi as package python-sanity
Install with uv:
uv add python-sanity
Install with pip:
pip install python-sanity
Environment Variables
You can pass parameters to the client constructor directly, but it is recommended to use environment variables.
| Variable | Description | Required | Default Value |
|---|---|---|---|
| SANITY_PROJECT_ID | The project ID | Yes | |
| SANITY_DATASET | The dataset to use | No | production |
| SANITY_API_TOKEN | The API token | No (required for mutations) | |
| SANITY_LOG_LEVEL | Level of logging | No | INFO |
What's New in v0.2.0
- AsyncClient: Full async/await support for all operations
- Optional Logger: Logger parameter is now optional, uses built-in logger with
SANITY_LOG_LEVELsupport - httpx: Migrated from
requeststohttpxfor better async support and HTTP/2 - Automatic Retries: Configurable retry logic with exponential backoff
- Better Error Handling: Specific exception types (
SanityAuthError,SanityRateLimitError, etc.) - New API Parameters:
- Query:
perspective,result_source_map,tag,return_query - Mutation:
auto_generate_array_keys,skip_cross_dataset_references_validation,transaction_id
- Query:
- Context Managers: Both
ClientandAsyncClientsupport context managers - Updated API Version: Default API version updated to
2025-02-19
Quick Start
Synchronous Client
from sanity import Client
# Simple initialization (logger is now optional!)
client = Client() # Uses environment variables
# Or with explicit parameters
client = Client(
project_id="your-project-id",
dataset="production",
token="your-api-token", # Optional for read-only queries
use_cdn=True
)
# Query with GROQ
result = client.query(
groq="*[_type == 'post'] | order(publishedAt desc)[0...10]",
variables={"limit": 10}
)
# Mutations
transactions = [{
"createOrReplace": {
"_id": "post.123",
"_type": "post",
"title": "Hello World",
"publishedAt": "2025-01-15T00:00:00Z"
}
}]
result = client.mutate(
transactions=transactions,
return_documents=True
)
# Upload assets
result = client.assets(
file_path="https://example.com/image.png"
)
Async Client
from sanity import AsyncClient
import asyncio
async def main():
# Use async context manager
async with AsyncClient() as client:
# Async query
result = await client.query(
groq="*[_type == 'post']",
perspective="published"
)
# Async mutation
result = await client.mutate(
transactions=[{
"create": {
"_type": "post",
"title": "Async Post"
}
}]
)
# Async asset upload
result = await client.assets(
file_path="/path/to/image.png"
)
asyncio.run(main())
Advanced Configuration
from sanity import Client, TimeoutConfig, RetryConfig
# Custom timeouts and retries
client = Client(
timeout=TimeoutConfig(
connect=5.0,
read=30.0,
write=30.0,
pool=5.0
),
retry_config=RetryConfig(
max_retries=5,
backoff_factor=1.0
),
http2=True
)
Error Handling
from sanity import (
Client,
SanityAuthError,
SanityRateLimitError,
SanityValidationError
)
client = Client()
try:
result = client.query(groq="*[_type == 'post']")
except SanityAuthError as e:
print(f"Authentication failed: {e.message}")
except SanityRateLimitError as e:
print(f"Rate limited, retry after {e.retry_after}s")
except SanityValidationError as e:
print(f"Validation error: {e.response_body}")
Migration Guide from v0.1.x
Breaking Changes
None! v0.2.0 is fully backward compatible.
Optional Improvements
-
Logger is now optional:
# Old way (still works) import logging client = Client(logger=logging.getLogger(__name__)) # New way (simpler) client = Client() # Uses built-in logger with SANITY_LOG_LEVEL
-
Use context managers for cleanup:
# Recommended with Client() as client: result = client.query(groq="*[_type == 'post']")
-
Try async for better performance:
from sanity import AsyncClient async with AsyncClient() as client: result = await client.query(groq="*[_type == 'post']")
-
Use new parameters:
# Query with perspective result = client.query( groq="*[_type == 'post']", perspective="published", # drafts, published, raw tag="my-app" ) # Mutations with new options result = client.mutate( transactions=[...], auto_generate_array_keys=True, transaction_id="my-custom-id" )
Metadata
Release files for python-sanity 0.2.0.post1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| python_sanity-0.2.0.post1.tar.gz | 20.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| python_sanity-0.2.0.post1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.0 kB
Release files / python_sanity-0.2.0.post1.tar.gz
| Download URL | python_sanity-0.2.0.post1.tar.gz |
|---|---|
| Size | 20.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3a24614b74a6eb24ea42a42bfafdb9e7ac9d37d0685d5a3b13cdaa8ac312e7d4
|
|
BLAKE2b-256 checksum How to use checksums |
8eeed6c0c2ee9c7a0013ca423a9e89d43400a3c70eb42dd49046e47f2801c164
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.2
|
Release files / python_sanity-0.2.0.post1-py3-none-any.whl
| Download URL | python_sanity-0.2.0.post1-py3-none-any.whl |
|---|---|
| Size | 25.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c8a09b49fac1d12c2de49db7845b5f0ae7679cecf159153f429de89716b41e24
|
|
BLAKE2b-256 checksum How to use checksums |
905a94d9b85b5f0ac247ad2881659f789887bd9ab42e23940be75cf872adc11e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.2
|