azure-cosmos-py
Azure Cosmos DB SDK for Python (NoSQL API). Use for document CRUD, queries, containers, and globally distributed data. Triggers: "cosmos db", "CosmosClient", "container", "document", "NoSQL", "partition key".
Install
npx skills add https://github.com/microsoft/skills/tree/main/.github/plugins/azure-sdk-python/skills/azure-cosmos-py
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install microsoft-skills@llmmart
git clone https://github.com/microsoft/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole microsoft/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Azure Cosmos DB SDK for Python
Client library for Azure Cosmos DB NoSQL API — globally distributed, multi-model database.
Installation
pip install azure-cosmos azure-identity
Environment Variables
COSMOS_ENDPOINT=https://<account>.documents.azure.com:443/ # Required for all auth methods
COSMOS_DATABASE=mydb # Required for all auth methods
COSMOS_CONTAINER=mycontainer # Required for all auth methods
AZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production
Authentication & Lifecycle
🔑 Two rules apply to every code sample below:
- Prefer
DefaultAzureCredential. It works locally (Azure CLI / VS Code / Developer CLI) and in Azure (managed identity, workload identity) with no code change. Avoid connection strings, account/API keys — they bypass Entra audit and rotation.
- Local dev:
DefaultAzureCredentialworks as-is.- Production: set
AZURE_TOKEN_CREDENTIALS=prod(orAZURE_TOKEN_CREDENTIALS=<specific_credential>) to constrain the credential chain to production-safe credentials.- Wrap every client in a context manager so HTTP transports, sockets, and token caches are released deterministically:
- Sync:
with <Client>(...) as client:- Async:
async with <Client>(...) as client:andasync with DefaultAzureCredential() as credential:(fromazure.identity.aio)Snippets may abbreviate this setup, but production code should always follow both rules.
import os
from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
from azure.cosmos import CosmosClient
# Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential>
credential = DefaultAzureCredential(require_envvar=True)
# Or use a specific credential directly in production:
# See https://learn.microsoft.com/python/api/overview/azure/identity-readme?view=azure-python#credential-classes
# credential = ManagedIdentityCredential()
endpoint = "https://<account>.documents.azure.com:443/"
with CosmosClient(url=endpoint, credential=credential) as client:
# Use client here (see following sections for operations)
...
Client Hierarchy
| Client | Purpose | Get From |
|---|---|---|
CosmosClient |
Account-level operations | Direct instantiation |
DatabaseProxy |
Database operations | client.get_database_client() |
ContainerProxy |
Container/item operations | database.get_container_client() |
Core Workflow
Setup Database and Container
# Get or create database
database = client.create_database_if_not_exists(id="mydb")
# Get or create container with partition key
container = database.create_container_if_not_exists(
id="mycontainer",
partition_key=PartitionKey(path="/category")
)
# Get existing
database = client.get_database_client("mydb")
container = database.get_container_client("mycontainer")
Create Item
item = {
"id": "item-001", # Required: unique within partition
"category": "electronics", # Partition key value
"name": "Laptop",
"price": 999.99,
"tags": ["computer", "portable"]
}
created = container.create_item(body=item)
print(f"Created: {created['id']}")
Read Item
# Read requires id AND partition key
item = container.read_item(
item="item-001",
partition_key="electronics"
)
print(f"Name: {item['name']}")
Update Item (Replace)
item = container.read_item(item="item-001", partition_key="electronics")
item["price"] = 899.99
item["on_sale"] = True
updated = container.replace_item(item=item["id"], body=item)
Upsert Item
# Create if not exists, replace if exists
item = {
"id": "item-002",
"category": "electronics",
"name": "Tablet",
"price": 499.99
}
result = container.upsert_item(body=item)
Delete Item
container.delete_item(
item="item-001",
partition_key="electronics"
)
Queries
Basic Query
# Query within a partition (efficient)
query = "SELECT * FROM c WHERE c.price < @max_price"
items = container.query_items(
query=query,
parameters=[{"name": "@max_price", "value": 500}],
partition_key="electronics"
)
for item in items:
print(f"{item['name']}: ${item['price']}")
Cross-Partition Query
# Cross-partition (more expensive, use sparingly)
query = "SELECT * FROM c WHERE c.price < @max_price"
items = container.query_items(
query=query,
parameters=[{"name": "@max_price", "value": 500}],
enable_cross_partition_query=True
)
for item in items:
print(item)
Query with Projection
query = "SELECT c.id, c.name, c.price FROM c WHERE c.category = @category"
items = container.query_items(
query=query,
parameters=[{"name": "@category", "value": "electronics"}],
partition_key="electronics"
)
Read All Items
# Read all in a partition
items = container.read_all_items() # Cross-partition
# Or with partition key
items = container.query_items(
query="SELECT * FROM c",
partition_key="electronics"
)
Partition Keys
Critical: Always include partition key for efficient operations.
from azure.cosmos import PartitionKey
# Single partition key
container = database.create_container_if_not_exists(
id="orders",
partition_key=PartitionKey(path="/customer_id")
)
# Hierarchical partition key (preview)
container = database.create_container_if_not_exists(
id="events",
partition_key=PartitionKey(path=["/tenant_id", "/user_id"])
)
Throughput
# Create container with provisioned throughput
container = database.create_container_if_not_exists(
id="mycontainer",
partition_key=PartitionKey(path="/pk"),
offer_throughput=400 # RU/s
)
# Read current throughput
offer = container.read_offer()
print(f"Throughput: {offer.offer_throughput} RU/s")
# Update throughput
container.replace_throughput(throughput=1000)
Async Client
from azure.cosmos.aio import CosmosClient
from azure.identity.aio import DefaultAzureCredential
async def cosmos_operations():
async with DefaultAzureCredential() as credential:
async with CosmosClient(endpoint, credential=credential) as client:
database = client.get_database_client("mydb")
container = database.get_container_client("mycontainer")
# Create
await container.create_item(body={"id": "1", "pk": "test"})
# Read
item = await container.read_item(item="1", partition_key="test")
# Query
async for item in container.query_items(
query="SELECT * FROM c",
partition_key="test"
):
print(item)
import asyncio
asyncio.run(cosmos_operations())
Error Handling
from azure.cosmos.exceptions import CosmosHttpResponseError
try:
item = container.read_item(item="nonexistent", partition_key="pk")
except CosmosHttpResponseError as e:
if e.status_code == 404:
print("Item not found")
elif e.status_code == 429:
print(f"Rate limited. Retry after: {e.headers.get('x-ms-retry-after-ms')}ms")
else:
raise
Best Practices
- Pick sync OR async and stay consistent. Do not mix
azure.cosmossync clients withazure.cosmos.aioasync clients in the same call path. Choose one mode per module. - Always use context managers for clients and async credentials. Wrap every client in
with CosmosClient(...) as client:(sync) orasync with CosmosClient(...) as client:(async). For asyncDefaultAzureCredentialfromazure.identity.aio, also useasync with credential:so tokens and transports are cleaned up. - Use
DefaultAzureCredentialfor portable auth across local dev and Azure (avoid connection strings / API keys when possible). - Always specify partition key for point reads and queries
- Use parameterized queries to prevent injection and improve caching
- Avoid cross-partition queries when possible
- Use
upsert_itemfor idempotent writes - Use async client for high-throughput scenarios
- Design partition key for even data distribution
- Use
read_iteminstead of query for single document retrieval
Reference Files
| File | Contents |
|---|---|
| references/partitioning.md | Partition key strategies, hierarchical keys, hot partition detection and mitigation |
| references/query-patterns.md | Query optimization, aggregations, pagination, transactions, change feed |
| scripts/setup_cosmos_container.py | CLI tool for creating containers with partitioning, throughput, and indexing |
Files (skills)
-
references
-
partitioning.md 10.3 KB
# Partition Key Strategies Comprehensive guide to partition key design and optimization for Azure Cosmos DB. ## Partition Key Fundamentals ### What is a Partition Key? A partition key determines how data is distributed across physical partitions: - **Logical partition**: All items with the same partition key value - **Physical partition**: Storage unit that holds one or more logical partitions - **Maximum logical partition size**: 20 GB ```python from azure.cosmos import PartitionKey # Define partition key at container creation container = database.create_container_if_not_exists( id="orders", partition_key=PartitionKey(path="/customer_id") ) ``` ## Choosing a Partition Key ### Good Partition Key Characteristics | Characteristic | Why It Matters | |---------------|----------------| | High cardinality | Many unique values = even distribution | | Even distribution | Prevents hot partitions | | Frequently used in queries | Enables efficient single-partition queries | | Immutable | Partition key cannot be changed after item creation | ### Common Partition Key Patterns ```python # E-commerce orders - partition by customer container = database.create_container_if_not_exists( id="orders", partition_key=PartitionKey(path="/customer_id") ) # Query: "Get all orders for customer X" - efficient single partition # IoT telemetry - partition by device container = database.create_container_if_not_exists( id="telemetry", partition_key=PartitionKey(path="/device_id") ) # Query: "Get recent readings for device X" - efficient # Multi-tenant SaaS - partition by tenant container = database.create_container_if_not_exists( id="data", partition_key=PartitionKey(path="/tenant_id") ) # Query: "Get all data for tenant X" - efficient, isolated ``` ### Anti-Patterns to Avoid ```python # BAD: Low cardinality partition key # Only 2 values = all data in 2 partitions partition_key=PartitionKey(path="/status") # "active" or "inactive" # BAD: Timestamp as partition key (unless with synthetic key) # Creates write-only partitions, recent partition is always hot partition_key=PartitionKey(path="/created_date") # BAD: Monotonically increasing ID # All recent writes go to the same partition partition_key=PartitionKey(path="/auto_increment_id") ``` ## Hierarchical Partition Keys For workloads needing multi-level distribution: ```python from azure.cosmos import PartitionKey # Two-level hierarchy: tenant → user container = database.create_container_if_not_exists( id="user_events", partition_key=PartitionKey(path=["/tenant_id", "/user_id"]) ) # Three-level hierarchy: tenant → department → user container = database.create_container_if_not_exists( id="enterprise_data", partition_key=PartitionKey(path=["/tenant_id", "/department", "/user_id"]) ) ``` ### Querying with Hierarchical Keys ```python # Full key path - most efficient items = container.query_items( query="SELECT * FROM c WHERE c.status = 'active'", partition_key=["tenant-123", "user-456"] # List for hierarchical ) # Partial key path - queries within tenant items = container.query_items( query="SELECT * FROM c WHERE c.department = 'engineering'", partition_key=["tenant-123"] # Queries all users in tenant ) ``` ### Hierarchical Key Benefits | Scenario | Single Key | Hierarchical Key | |----------|------------|------------------| | Multi-tenant with user isolation | Cross-partition for tenant-wide queries | Single partition for tenant, sub-partitions for users | | Geographic + temporal data | Choose one dimension | Partition by region, then by date | | Hot partition mitigation | Limited options | Add synthetic suffix as sub-key | ## Synthetic Partition Keys When natural keys have poor distribution: ```python import hashlib def create_item_with_synthetic_key(container, item: dict, num_buckets: int = 10): """Add synthetic partition key for better distribution.""" # Generate bucket from item ID bucket = int(hashlib.md5(item["id"].encode()).hexdigest(), 16) % num_buckets # Combine natural key with bucket item["pk"] = f"{item['category']}_{bucket}" return container.create_item(body=item) # Example: Distribute "electronics" category across 10 partitions item = { "id": "item-001", "category": "electronics", "name": "Laptop" } create_item_with_synthetic_key(container, item) # Results in pk: "electronics_7" (depending on hash) ``` ### Time-Based Synthetic Keys ```python from datetime import datetime def create_event_with_time_bucket(container, event: dict): """Partition by device with hourly buckets.""" timestamp = event.get("timestamp", datetime.utcnow()) hour_bucket = timestamp.strftime("%Y%m%d%H") event["pk"] = f"{event['device_id']}_{hour_bucket}" return container.create_item(body=event) # Distributes writes across time-based partitions event = { "id": "event-001", "device_id": "device-123", "timestamp": datetime.utcnow(), "value": 42.5 } create_event_with_time_bucket(container, event) # Results in pk: "device-123_2025012812" ``` ## Hot Partition Detection ### Monitoring Partition Metrics ```python from azure.cosmos import CosmosClient def check_partition_metrics(container): """Check container throughput and partition distribution.""" # Get container properties properties = container.read() print(f"Container ID: {properties['id']}") # Get current throughput try: offer = container.read_offer() print(f"Provisioned throughput: {offer.offer_throughput} RU/s") except Exception: print("Using serverless or database-level throughput") # Query to check partition key distribution query = """ SELECT COUNT(1) as count, c.pk FROM c GROUP BY c.pk """ partition_counts = list(container.query_items( query=query, enable_cross_partition_query=True )) if partition_counts: total = sum(p['count'] for p in partition_counts) print(f"\nPartition distribution ({len(partition_counts)} partitions):") for p in sorted(partition_counts, key=lambda x: x['count'], reverse=True)[:10]: pct = (p['count'] / total) * 100 print(f" {p['pk']}: {p['count']} items ({pct:.1f}%)") if pct > 20: print(f" ⚠️ Hot partition detected!") ``` ### Identifying Hot Partitions via Azure Portal Key metrics to monitor: - **Normalized RU Consumption**: >70% indicates potential hot partition - **Partition Key Range Statistics**: Uneven distribution warnings - **Throttled Requests (429s)**: May indicate hot partition ### Mitigation Strategies ```python # Strategy 1: Add randomized suffix def add_random_suffix(pk: str, num_suffixes: int = 5) -> str: """Distribute writes across multiple logical partitions.""" import random suffix = random.randint(0, num_suffixes - 1) return f"{pk}_{suffix}" # Strategy 2: Time-based bucketing def add_time_bucket(pk: str, bucket_minutes: int = 60) -> str: """Create time-based sub-partitions.""" from datetime import datetime bucket = datetime.utcnow().hour return f"{pk}_{bucket}" # Strategy 3: Hash-based distribution def add_hash_bucket(pk: str, item_id: str, num_buckets: int = 10) -> str: """Deterministic distribution based on item ID.""" import hashlib bucket = int(hashlib.sha256(item_id.encode()).hexdigest(), 16) % num_buckets return f"{pk}_{bucket}" ``` ## Partition Key Design Patterns by Use Case ### E-commerce ```python # Orders container: partition by customer for order history queries orders_container = database.create_container_if_not_exists( id="orders", partition_key=PartitionKey(path="/customer_id") ) # Products container: partition by category for browsing products_container = database.create_container_if_not_exists( id="products", partition_key=PartitionKey(path="/category") ) # Cart container: partition by session for cart operations cart_container = database.create_container_if_not_exists( id="carts", partition_key=PartitionKey(path="/session_id") ) ``` ### Multi-tenant SaaS ```python # Hierarchical: tenant → resource type for isolation + efficient queries container = database.create_container_if_not_exists( id="tenant_data", partition_key=PartitionKey(path=["/tenant_id", "/resource_type"]) ) # Document structure document = { "id": "doc-001", "tenant_id": "tenant-abc", "resource_type": "project", "name": "My Project", "data": {...} } ``` ### IoT / Time Series ```python # Hierarchical: device → time bucket for efficient range queries container = database.create_container_if_not_exists( id="telemetry", partition_key=PartitionKey(path=["/device_id", "/day"]) ) # Document structure telemetry = { "id": "reading-001", "device_id": "sensor-123", "day": "2025-01-28", # Daily bucket "timestamp": "2025-01-28T12:30:00Z", "temperature": 22.5, "humidity": 45.2 } ``` ### Social / Activity Feeds ```python # Partition by user for personal feed queries container = database.create_container_if_not_exists( id="activity_feed", partition_key=PartitionKey(path="/user_id") ) # With write amplification for follower feeds # Store activity in both author and follower partitions async def post_activity(container, author_id: str, follower_ids: list[str], activity: dict): """Fan-out activity to author and all followers.""" tasks = [] # Author's partition author_activity = {**activity, "user_id": author_id, "id": f"{activity['id']}_author"} tasks.append(container.create_item(body=author_activity)) # Each follower's partition for follower_id in follower_ids: follower_activity = {**activity, "user_id": follower_id, "id": f"{activity['id']}_{follower_id}"} tasks.append(container.create_item(body=follower_activity)) await asyncio.gather(*tasks) ``` ## Best Practices Summary | Practice | Recommendation | |----------|----------------| | Cardinality | Aim for thousands+ unique values | | Distribution | Monitor and rebalance if >20% in single partition | | Query alignment | Partition key should match most frequent query filter | | Size monitoring | Keep logical partitions under 15GB (20GB limit) | | Throughput | Consider hierarchical keys for mixed workloads | | Immutability | Plan partition key carefully - cannot change later | -
query-patterns.md 10.6 KB
# Query Patterns Reference Advanced query patterns for Azure Cosmos DB NoSQL API. ## Parameterized Queries Always use parameters to prevent injection and enable query plan caching: ```python # GOOD: Parameterized query query = "SELECT * FROM c WHERE c.category = @category AND c.price < @max_price" items = container.query_items( query=query, parameters=[ {"name": "@category", "value": "electronics"}, {"name": "@max_price", "value": 500} ], partition_key="electronics" ) # BAD: String interpolation (injection risk, no caching) category = "electronics" query = f"SELECT * FROM c WHERE c.category = '{category}'" # Never do this! ``` ## Query Optimization ### Single Partition vs Cross-Partition ```python # EFFICIENT: Single partition query (always preferred) items = container.query_items( query="SELECT * FROM c WHERE c.status = 'active'", partition_key="tenant-123" ) # EXPENSIVE: Cross-partition query (use sparingly) items = container.query_items( query="SELECT * FROM c WHERE c.status = 'active'", enable_cross_partition_query=True # Fans out to all partitions ) ``` ### Point Reads vs Queries ```python # MOST EFFICIENT: Point read (1 RU for 1KB document) item = container.read_item( item="doc-123", partition_key="tenant-abc" ) # LESS EFFICIENT: Query for single document (higher RU) items = list(container.query_items( query="SELECT * FROM c WHERE c.id = @id", parameters=[{"name": "@id", "value": "doc-123"}], partition_key="tenant-abc" )) item = items[0] if items else None ``` ### Projection for Efficiency ```python # GOOD: Select only needed fields (less RU, less bandwidth) query = "SELECT c.id, c.name, c.price FROM c WHERE c.category = @category" items = container.query_items( query=query, parameters=[{"name": "@category", "value": "electronics"}], partition_key="electronics" ) # EXPENSIVE: Select all fields when you only need a few query = "SELECT * FROM c WHERE c.category = @category" ``` ## Pagination ### Continuation Token Pagination ```python def paginated_query( container, query: str, partition_key: str, page_size: int = 100, parameters: list | None = None ): """Paginate through query results using continuation tokens.""" continuation_token = None page_number = 0 while True: # Create query iterable with pagination query_iterable = container.query_items( query=query, parameters=parameters or [], partition_key=partition_key, max_item_count=page_size, continuation_token=continuation_token ) # Get page of results page = list(query_iterable.by_page().__next__()) page_number += 1 print(f"Page {page_number}: {len(page)} items") yield page # Get continuation token for next page continuation_token = query_iterable.continuation_token if not continuation_token: break # Usage for page in paginated_query( container, query="SELECT * FROM c WHERE c.status = 'active'", partition_key="tenant-123", page_size=50 ): process_page(page) ``` ### Offset-Limit Pagination (Not Recommended for Large Datasets) ```python # Works but inefficient for large offsets (still reads skipped items) query = "SELECT * FROM c ORDER BY c.created_at DESC OFFSET @offset LIMIT @limit" items = container.query_items( query=query, parameters=[ {"name": "@offset", "value": 100}, {"name": "@limit", "value": 10} ], partition_key="tenant-123" ) ``` ## Aggregations ### COUNT, SUM, AVG, MIN, MAX ```python # Count items in partition query = "SELECT VALUE COUNT(1) FROM c WHERE c.status = 'active'" result = list(container.query_items( query=query, partition_key="tenant-123" )) count = result[0] # Returns integer directly with VALUE # Sum with grouping query = """ SELECT c.category, SUM(c.quantity) as total_quantity, AVG(c.price) as avg_price FROM c WHERE c.order_date >= @start_date GROUP BY c.category """ results = container.query_items( query=query, parameters=[{"name": "@start_date", "value": "2025-01-01"}], partition_key="tenant-123" ) for result in results: print(f"{result['category']}: {result['total_quantity']} items, ${result['avg_price']:.2f} avg") ``` ### Aggregate Functions Reference | Function | Description | Example | |----------|-------------|---------| | `COUNT(expr)` | Count non-null values | `COUNT(c.id)` | | `COUNT(1)` | Count all documents | `COUNT(1)` | | `SUM(expr)` | Sum numeric values | `SUM(c.quantity)` | | `AVG(expr)` | Average of numeric values | `AVG(c.price)` | | `MIN(expr)` | Minimum value | `MIN(c.created_at)` | | `MAX(expr)` | Maximum value | `MAX(c.price)` | ## Advanced Query Patterns ### Array Operations ```python # Check if array contains value query = "SELECT * FROM c WHERE ARRAY_CONTAINS(c.tags, 'urgent')" # Check if array contains object with property query = """ SELECT * FROM c WHERE ARRAY_CONTAINS(c.items, {'product_id': @product_id}, true) """ # Flatten array with JOIN query = """ SELECT c.id, c.order_id, item.product_name, item.quantity FROM c JOIN item IN c.items WHERE item.quantity > @min_quantity """ # Array length query = "SELECT * FROM c WHERE ARRAY_LENGTH(c.tags) > 3" ``` ### String Functions ```python # Case-insensitive search (expensive - no index) query = "SELECT * FROM c WHERE LOWER(c.name) = LOWER(@search_name)" # Contains (prefix search uses index, contains doesn't) query = "SELECT * FROM c WHERE STARTSWITH(c.name, @prefix)" # Uses index query = "SELECT * FROM c WHERE CONTAINS(c.name, @substring)" # Full scan # String manipulation query = """ SELECT c.id, CONCAT(c.first_name, ' ', c.last_name) as full_name, LENGTH(c.description) as desc_length FROM c """ ``` ### Subqueries ```python # EXISTS subquery query = """ SELECT * FROM c WHERE EXISTS( SELECT VALUE item FROM item IN c.items WHERE item.status = 'pending' ) """ # Scalar subquery query = """ SELECT c.id, c.name, (SELECT VALUE COUNT(1) FROM item IN c.items) as item_count FROM c """ ``` ### Ordering and Distinct ```python # Order by (requires composite index for multiple fields) query = "SELECT * FROM c ORDER BY c.created_at DESC, c.priority ASC" # Distinct values query = "SELECT DISTINCT VALUE c.category FROM c" # Top N query = "SELECT TOP 10 * FROM c ORDER BY c.score DESC" ``` ## Transactions (Transactional Batch) Execute multiple operations atomically within a single partition: ```python from azure.cosmos import TransactionalBatch def transfer_funds(container, from_account_id: str, to_account_id: str, amount: float, partition_key: str): """Transfer funds between accounts atomically.""" # Read current balances from_account = container.read_item(item=from_account_id, partition_key=partition_key) to_account = container.read_item(item=to_account_id, partition_key=partition_key) if from_account["balance"] < amount: raise ValueError("Insufficient funds") # Update balances from_account["balance"] -= amount to_account["balance"] += amount # Execute as transaction batch = TransactionalBatch(partition_key=partition_key) batch.replace_item(item=from_account_id, body=from_account) batch.replace_item(item=to_account_id, body=to_account) results = container.execute_transactional_batch(batch=batch) # Check results for result in results: if result["statusCode"] >= 400: raise Exception(f"Transaction failed: {result}") return results ``` ### Batch Operations ```python from azure.cosmos import TransactionalBatch def batch_create_items(container, items: list[dict], partition_key: str): """Create multiple items in a single batch (max 100 operations).""" batch = TransactionalBatch(partition_key=partition_key) for item in items[:100]: # Max 100 operations per batch batch.create_item(body=item) results = container.execute_transactional_batch(batch=batch) successful = sum(1 for r in results if r["statusCode"] < 400) print(f"Created {successful}/{len(items)} items") return results # Usage items = [ {"id": f"item-{i}", "pk": "batch-test", "value": i} for i in range(50) ] batch_create_items(container, items, partition_key="batch-test") ``` ## Change Feed Process changes to documents in order: ```python def process_change_feed(container, partition_key: str | None = None): """Process change feed for a container.""" # Start from beginning change_feed = container.query_items_change_feed( partition_key=partition_key, # None for all partitions start_time="Beginning" ) for change in change_feed: print(f"Changed document: {change['id']}") # Process the change process_document(change) # Get continuation token for next run continuation = change_feed.continuation_token return continuation def process_change_feed_incremental(container, continuation_token: str): """Resume change feed from continuation token.""" change_feed = container.query_items_change_feed( continuation_token=continuation_token ) for change in change_feed: process_document(change) return change_feed.continuation_token ``` ## Query Best Practices | Practice | Recommendation | |----------|----------------| | Always use partition key | Avoid cross-partition queries when possible | | Use parameters | Enable query plan caching, prevent injection | | Project only needed fields | Reduces RU cost and bandwidth | | Use point reads | For single document by ID, use `read_item` not query | | Index strategy | Create composite indexes for ORDER BY on multiple fields | | Pagination | Use continuation tokens for large result sets | | Aggregations | Prefer single-partition aggregations | | Avoid OFFSET | Use continuation tokens instead for large datasets | ## RU Estimation ```python def estimate_query_cost(container, query: str, partition_key: str, parameters: list | None = None): """Execute query and return RU cost.""" query_iterable = container.query_items( query=query, parameters=parameters or [], partition_key=partition_key, populate_query_metrics=True ) items = list(query_iterable) # Get request charge from response headers request_charge = query_iterable.get_response_headers().get("x-ms-request-charge", 0) print(f"Query returned {len(items)} items") print(f"Total RU cost: {request_charge}") print(f"RU per item: {float(request_charge) / len(items) if items else 0:.2f}") return items, float(request_charge) ```
-
-
SKILL.md 9.1 KB
--- name: azure-cosmos-py description: | Azure Cosmos DB SDK for Python (NoSQL API). Use for document CRUD, queries, containers, and globally distributed data. Triggers: "cosmos db", "CosmosClient", "container", "document", "NoSQL", "partition key". license: MIT metadata: author: Microsoft version: "1.0.0" package: azure-cosmos --- # Azure Cosmos DB SDK for Python Client library for Azure Cosmos DB NoSQL API — globally distributed, multi-model database. ## Installation ```bash pip install azure-cosmos azure-identity ``` ## Environment Variables ```bash COSMOS_ENDPOINT=https://<account>.documents.azure.com:443/ # Required for all auth methods COSMOS_DATABASE=mydb # Required for all auth methods COSMOS_CONTAINER=mycontainer # Required for all auth methods AZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production ``` ## Authentication & Lifecycle > **🔑 Two rules apply to every code sample below:** > > 1. **Prefer `DefaultAzureCredential`.** It works locally (Azure CLI / VS Code / Developer CLI) and in Azure (managed identity, workload identity) with no code change. Avoid connection strings, account/API keys — they bypass Entra audit and rotation. > - Local dev: `DefaultAzureCredential` works as-is. > - Production: set `AZURE_TOKEN_CREDENTIALS=prod` (or `AZURE_TOKEN_CREDENTIALS=<specific_credential>`) to constrain the credential chain to production-safe credentials. > 2. **Wrap every client in a context manager** so HTTP transports, sockets, and token caches are released deterministically: > - Sync: `with <Client>(...) as client:` > - Async: `async with <Client>(...) as client:` **and** `async with DefaultAzureCredential() as credential:` (from `azure.identity.aio`) > > Snippets may abbreviate this setup, but production code should always follow both rules. ```python import os from azure.identity import DefaultAzureCredential, ManagedIdentityCredential from azure.cosmos import CosmosClient # Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential> credential = DefaultAzureCredential(require_envvar=True) # Or use a specific credential directly in production: # See https://learn.microsoft.com/python/api/overview/azure/identity-readme?view=azure-python#credential-classes # credential = ManagedIdentityCredential() endpoint = "https://<account>.documents.azure.com:443/" with CosmosClient(url=endpoint, credential=credential) as client: # Use client here (see following sections for operations) ... ``` ## Client Hierarchy | Client | Purpose | Get From | |--------|---------|----------| | `CosmosClient` | Account-level operations | Direct instantiation | | `DatabaseProxy` | Database operations | `client.get_database_client()` | | `ContainerProxy` | Container/item operations | `database.get_container_client()` | ## Core Workflow ### Setup Database and Container ```python # Get or create database database = client.create_database_if_not_exists(id="mydb") # Get or create container with partition key container = database.create_container_if_not_exists( id="mycontainer", partition_key=PartitionKey(path="/category") ) # Get existing database = client.get_database_client("mydb") container = database.get_container_client("mycontainer") ``` ### Create Item ```python item = { "id": "item-001", # Required: unique within partition "category": "electronics", # Partition key value "name": "Laptop", "price": 999.99, "tags": ["computer", "portable"] } created = container.create_item(body=item) print(f"Created: {created['id']}") ``` ### Read Item ```python # Read requires id AND partition key item = container.read_item( item="item-001", partition_key="electronics" ) print(f"Name: {item['name']}") ``` ### Update Item (Replace) ```python item = container.read_item(item="item-001", partition_key="electronics") item["price"] = 899.99 item["on_sale"] = True updated = container.replace_item(item=item["id"], body=item) ``` ### Upsert Item ```python # Create if not exists, replace if exists item = { "id": "item-002", "category": "electronics", "name": "Tablet", "price": 499.99 } result = container.upsert_item(body=item) ``` ### Delete Item ```python container.delete_item( item="item-001", partition_key="electronics" ) ``` ## Queries ### Basic Query ```python # Query within a partition (efficient) query = "SELECT * FROM c WHERE c.price < @max_price" items = container.query_items( query=query, parameters=[{"name": "@max_price", "value": 500}], partition_key="electronics" ) for item in items: print(f"{item['name']}: ${item['price']}") ``` ### Cross-Partition Query ```python # Cross-partition (more expensive, use sparingly) query = "SELECT * FROM c WHERE c.price < @max_price" items = container.query_items( query=query, parameters=[{"name": "@max_price", "value": 500}], enable_cross_partition_query=True ) for item in items: print(item) ``` ### Query with Projection ```python query = "SELECT c.id, c.name, c.price FROM c WHERE c.category = @category" items = container.query_items( query=query, parameters=[{"name": "@category", "value": "electronics"}], partition_key="electronics" ) ``` ### Read All Items ```python # Read all in a partition items = container.read_all_items() # Cross-partition # Or with partition key items = container.query_items( query="SELECT * FROM c", partition_key="electronics" ) ``` ## Partition Keys **Critical**: Always include partition key for efficient operations. ```python from azure.cosmos import PartitionKey # Single partition key container = database.create_container_if_not_exists( id="orders", partition_key=PartitionKey(path="/customer_id") ) # Hierarchical partition key (preview) container = database.create_container_if_not_exists( id="events", partition_key=PartitionKey(path=["/tenant_id", "/user_id"]) ) ``` ## Throughput ```python # Create container with provisioned throughput container = database.create_container_if_not_exists( id="mycontainer", partition_key=PartitionKey(path="/pk"), offer_throughput=400 # RU/s ) # Read current throughput offer = container.read_offer() print(f"Throughput: {offer.offer_throughput} RU/s") # Update throughput container.replace_throughput(throughput=1000) ``` ## Async Client ```python from azure.cosmos.aio import CosmosClient from azure.identity.aio import DefaultAzureCredential async def cosmos_operations(): async with DefaultAzureCredential() as credential: async with CosmosClient(endpoint, credential=credential) as client: database = client.get_database_client("mydb") container = database.get_container_client("mycontainer") # Create await container.create_item(body={"id": "1", "pk": "test"}) # Read item = await container.read_item(item="1", partition_key="test") # Query async for item in container.query_items( query="SELECT * FROM c", partition_key="test" ): print(item) import asyncio asyncio.run(cosmos_operations()) ``` ## Error Handling ```python from azure.cosmos.exceptions import CosmosHttpResponseError try: item = container.read_item(item="nonexistent", partition_key="pk") except CosmosHttpResponseError as e: if e.status_code == 404: print("Item not found") elif e.status_code == 429: print(f"Rate limited. Retry after: {e.headers.get('x-ms-retry-after-ms')}ms") else: raise ``` ## Best Practices 1. **Pick sync OR async and stay consistent.** Do not mix `azure.cosmos` sync clients with `azure.cosmos.aio` async clients in the same call path. Choose one mode per module. 2. **Always use context managers for clients and async credentials.** Wrap every client in `with CosmosClient(...) as client:` (sync) or `async with CosmosClient(...) as client:` (async). For async `DefaultAzureCredential` from `azure.identity.aio`, also use `async with credential:` so tokens and transports are cleaned up. 3. **Use `DefaultAzureCredential`** for portable auth across local dev and Azure (avoid connection strings / API keys when possible). 4. **Always specify partition key** for point reads and queries 5. **Use parameterized queries** to prevent injection and improve caching 6. **Avoid cross-partition queries** when possible 7. **Use `upsert_item`** for idempotent writes 8. **Use async client** for high-throughput scenarios 9. **Design partition key** for even data distribution 10. **Use `read_item`** instead of query for single document retrieval ## Reference Files | File | Contents | |------|----------| | [references/partitioning.md](references/partitioning.md) | Partition key strategies, hierarchical keys, hot partition detection and mitigation | | [references/query-patterns.md](references/query-patterns.md) | Query optimization, aggregations, pagination, transactions, change feed | | [scripts/setup_cosmos_container.py](scripts/setup_cosmos_container.py) | CLI tool for creating containers with partitioning, throughput, and indexing |
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.