250 lines
9.0 KiB
Markdown
250 lines
9.0 KiB
Markdown
---
|
|
name: azure-cosmos-db-py
|
|
description: "Build production-grade Azure Cosmos DB NoSQL services following clean code, security best practices, and TDD principles."
|
|
risk: unknown
|
|
source: community
|
|
date_added: "2026-02-27"
|
|
---
|
|
|
|
# Cosmos DB Service Implementation
|
|
|
|
Build production-grade Azure Cosmos DB NoSQL services following clean code, security best practices, and TDD principles.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pip install azure-cosmos azure-identity
|
|
```
|
|
|
|
## Environment Variables
|
|
|
|
```bash
|
|
COSMOS_ENDPOINT=https://<account>.documents.azure.com:443/
|
|
COSMOS_DATABASE_NAME=<database-name>
|
|
COSMOS_CONTAINER_ID=<container-id>
|
|
# For emulator only (not production)
|
|
COSMOS_KEY=<emulator-key>
|
|
```
|
|
|
|
## Authentication
|
|
|
|
**DefaultAzureCredential (preferred)**:
|
|
```python
|
|
from azure.cosmos import CosmosClient
|
|
from azure.identity import DefaultAzureCredential
|
|
|
|
client = CosmosClient(
|
|
url=os.environ["COSMOS_ENDPOINT"],
|
|
credential=DefaultAzureCredential()
|
|
)
|
|
```
|
|
|
|
**Emulator (local development)**:
|
|
```python
|
|
from azure.cosmos import CosmosClient
|
|
|
|
client = CosmosClient(
|
|
url="https://localhost:8081",
|
|
credential=os.environ["COSMOS_KEY"],
|
|
connection_verify=False
|
|
)
|
|
```
|
|
|
|
## Architecture Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ FastAPI Router │
|
|
│ - Auth dependencies (get_current_user, get_current_user_required)
|
|
│ - HTTP error responses (HTTPException) │
|
|
└──────────────────────────────┬──────────────────────────────────┘
|
|
│
|
|
┌──────────────────────────────▼──────────────────────────────────┐
|
|
│ Service Layer │
|
|
│ - Business logic and validation │
|
|
│ - Document ↔ Model conversion │
|
|
│ - Graceful degradation when Cosmos unavailable │
|
|
└──────────────────────────────┬──────────────────────────────────┘
|
|
│
|
|
┌──────────────────────────────▼──────────────────────────────────┐
|
|
│ Cosmos DB Client Module │
|
|
│ - Singleton container initialization │
|
|
│ - Dual auth: DefaultAzureCredential (Azure) / Key (emulator) │
|
|
│ - Async wrapper via run_in_threadpool │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
### 1. Client Module Setup
|
|
|
|
Create a singleton Cosmos client with dual authentication:
|
|
|
|
```python
|
|
# db/cosmos.py
|
|
from azure.cosmos import CosmosClient
|
|
from azure.identity import DefaultAzureCredential
|
|
from starlette.concurrency import run_in_threadpool
|
|
|
|
_cosmos_container = None
|
|
|
|
def _is_emulator_endpoint(endpoint: str) -> bool:
|
|
return "localhost" in endpoint or "127.0.0.1" in endpoint
|
|
|
|
async def get_container():
|
|
global _cosmos_container
|
|
if _cosmos_container is None:
|
|
if _is_emulator_endpoint(settings.cosmos_endpoint):
|
|
client = CosmosClient(
|
|
url=settings.cosmos_endpoint,
|
|
credential=settings.cosmos_key,
|
|
connection_verify=False
|
|
)
|
|
else:
|
|
client = CosmosClient(
|
|
url=settings.cosmos_endpoint,
|
|
credential=DefaultAzureCredential()
|
|
)
|
|
db = client.get_database_client(settings.cosmos_database_name)
|
|
_cosmos_container = db.get_container_client(settings.cosmos_container_id)
|
|
return _cosmos_container
|
|
```
|
|
|
|
**Full implementation**: See references/client-setup.md
|
|
|
|
### 2. Pydantic Model Hierarchy
|
|
|
|
Use five-tier model pattern for clean separation:
|
|
|
|
```python
|
|
class ProjectBase(BaseModel): # Shared fields
|
|
name: str = Field(..., min_length=1, max_length=200)
|
|
|
|
class ProjectCreate(ProjectBase): # Creation request
|
|
workspace_id: str = Field(..., alias="workspaceId")
|
|
|
|
class ProjectUpdate(BaseModel): # Partial updates (all optional)
|
|
name: Optional[str] = Field(None, min_length=1)
|
|
|
|
class Project(ProjectBase): # API response
|
|
id: str
|
|
created_at: datetime = Field(..., alias="createdAt")
|
|
|
|
class ProjectInDB(Project): # Internal with docType
|
|
doc_type: str = "project"
|
|
```
|
|
|
|
### 3. Service Layer Pattern
|
|
|
|
```python
|
|
class ProjectService:
|
|
def _use_cosmos(self) -> bool:
|
|
return get_container() is not None
|
|
|
|
async def get_by_id(self, project_id: str, workspace_id: str) -> Project | None:
|
|
if not self._use_cosmos():
|
|
return None
|
|
doc = await get_document(project_id, partition_key=workspace_id)
|
|
if doc is None:
|
|
return None
|
|
return self._doc_to_model(doc)
|
|
```
|
|
|
|
**Full patterns**: See references/service-layer.md
|
|
|
|
## Core Principles
|
|
|
|
### Security Requirements
|
|
|
|
1. **RBAC Authentication**: Use `DefaultAzureCredential` in Azure — never store keys in code
|
|
2. **Emulator-Only Keys**: Hardcode the well-known emulator key only for local development
|
|
3. **Parameterized Queries**: Always use `@parameter` syntax — never string concatenation
|
|
4. **Partition Key Validation**: Validate partition key access matches user authorization
|
|
|
|
### Clean Code Conventions
|
|
|
|
1. **Single Responsibility**: Client module handles connection; services handle business logic
|
|
2. **Graceful Degradation**: Services return `None`/`[]` when Cosmos unavailable
|
|
3. **Consistent Naming**: `_doc_to_model()`, `_model_to_doc()`, `_use_cosmos()`
|
|
4. **Type Hints**: Full typing on all public methods
|
|
5. **CamelCase Aliases**: Use `Field(alias="camelCase")` for JSON serialization
|
|
|
|
### TDD Requirements
|
|
|
|
Write tests BEFORE implementation using these patterns:
|
|
|
|
```python
|
|
@pytest.fixture
|
|
def mock_cosmos_container(mocker):
|
|
container = mocker.MagicMock()
|
|
mocker.patch("app.db.cosmos.get_container", return_value=container)
|
|
return container
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_project_by_id_returns_project(mock_cosmos_container):
|
|
# Arrange
|
|
mock_cosmos_container.read_item.return_value = {"id": "123", "name": "Test"}
|
|
|
|
# Act
|
|
result = await project_service.get_by_id("123", "workspace-1")
|
|
|
|
# Assert
|
|
assert result.id == "123"
|
|
assert result.name == "Test"
|
|
```
|
|
|
|
**Full testing guide**: See references/testing.md
|
|
|
|
## Reference Files
|
|
|
|
| File | When to Read |
|
|
|------|--------------|
|
|
| references/client-setup.md | Setting up Cosmos client with dual auth, SSL config, singleton pattern |
|
|
| references/service-layer.md | Implementing full service class with CRUD, conversions, graceful degradation |
|
|
| references/testing.md | Writing pytest tests, mocking Cosmos, integration test setup |
|
|
| references/partitioning.md | Choosing partition keys, cross-partition queries, move operations |
|
|
| references/error-handling.md | Handling CosmosResourceNotFoundError, logging, HTTP error mapping |
|
|
|
|
## Template Files
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| assets/cosmos_client_template.py | Ready-to-use client module |
|
|
| assets/service_template.py | Service class skeleton |
|
|
| assets/conftest_template.py | pytest fixtures for Cosmos mocking |
|
|
|
|
## Quality Attributes (NFRs)
|
|
|
|
### Reliability
|
|
- Graceful degradation when Cosmos unavailable
|
|
- Retry logic with exponential backoff for transient failures
|
|
- Connection pooling via singleton pattern
|
|
|
|
### Security
|
|
- Zero secrets in code (RBAC via DefaultAzureCredential)
|
|
- Parameterized queries prevent injection
|
|
- Partition key isolation enforces data boundaries
|
|
|
|
### Maintainability
|
|
- Five-tier model pattern enables schema evolution
|
|
- Service layer decouples business logic from storage
|
|
- Consistent patterns across all entity services
|
|
|
|
### Testability
|
|
- Dependency injection via `get_container()`
|
|
- Easy mocking with module-level globals
|
|
- Clear separation enables unit testing without Cosmos
|
|
|
|
### Performance
|
|
- Partition key queries avoid cross-partition scans
|
|
- Async wrapping prevents blocking FastAPI event loop
|
|
- Minimal document conversion overhead
|
|
|
|
## When to Use
|
|
This skill is applicable to execute the workflow or actions described in the overview.
|
|
|
|
## Limitations
|
|
- Use this skill only when the task clearly matches the scope described above.
|
|
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
|
|
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
|