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.
Available on pypi as package python-sanity
Install with uv:
uv add python-sanityInstall with pip:
pip install python-sanityYou 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 |
- 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
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"
)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())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
)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}")None! v0.2.0 is fully backward compatible.
-
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" )