Skip to content
Back to skills

Azure

ASecurity

Use when writing Python code that integrates with Azure Blob Storage, AI Search, Document Intelligence, or Key Vault — or when configuring Managed Identity auth, designing a hybrid search index, or troubleshooting Azure SDK retry behavior.

  • 189 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 25, 2026
devopspythongonodeazuretestingci/cd

Works with

  • cli

Security analysis

A100/100

Scanned September 25, 2026

npx -y skills add kid-sid/claude-spellbook --skill azure --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Azure?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Azure
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kid-sid-azure-claude-spellbook/badge)](https://www.skillsdirectory.com/skills/kid-sid-azure-claude-spellbook)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: azure
description: Use when writing Python code that integrates with Azure Blob Storage, AI Search, Document Intelligence, or Key Vault — or when configuring Managed Identity auth, designing a hybrid search index, or troubleshooting Azure SDK retry behavior.
---

# Azure SDK

Production patterns for Azure services in Python using the official Azure SDKs.

## When to Activate

- Writing code that imports `azure-storage-blob`, `azure-search-documents`, `azure-ai-formrecognizer`, or `azure-identity`
- Configuring authentication for Azure services (Managed Identity, service principals, connection strings)
- Designing or querying an Azure AI Search index (vector, text, hybrid)
- Extracting content from documents using Azure Document Intelligence
- Managing secrets with Azure Key Vault
- Deploying a pipeline as an Azure Function
- Troubleshooting Azure SDK errors or retry behavior

## Authentication

### DefaultAzureCredential (always prefer this)

```python
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient

credential = DefaultAzureCredential()
client = BlobServiceClient(account_url="https://<account>.blob.core.windows.net", credential=credential)
```

`DefaultAzureCredential` tries, in order: environment variables → Managed Identity → Azure CLI → VS Code → Interactive browser. The same code works locally (via CLI auth) and in production (via Managed Identity) without changes.

```python
# BAD: connection string hardcoded
client = BlobServiceClient.from_connection_string("DefaultEndpointsProtocol=https;AccountName=...")

# BAD: key hardcoded
client = BlobServiceClient(account_url=url, credential="storage-account-key-here")

# GOOD: keyless auth
credential = DefaultAzureCredential()
client = BlobServiceClient(account_url=url, credential=credential)
```

### Auth decision matrix

| Environment | Credential type | How to enable |
|---|---|---|
| Local dev | Azure CLI | `az login` |
| CI/CD | Service principal (env vars) | Set `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID` |
| Azure VM / AKS | System-assigned Managed Identity | Enable on the resource in portal/Bicep |
| Azure Functions | User-assigned Managed Identity | Set `AZURE_CLIENT_ID` env var |
| Testing | `ClientSecretCredential` | Explicit — never use in production code |

## Configuration (pydantic-settings)

```python
from pydantic_settings import BaseSettings

class AzureSettings(BaseSettings):
    azure_storage_account_url: str
    azure_search_endpoint: str
    azure_search_index_name: str
    azure_document_intelligence_endpoint: str
    azure_key_vault_url: str | None = None

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"

settings = AzureSettings()
```

Never store credentials in settings — let `DefaultAzureCredential` handle them.

## Azure Blob Storage

### Upload

```python
from azure.storage.blob import BlobServiceClient, ContentSettings

def upload_file(account_url: str, container: str, blob_name: str, data: bytes, content_type: str) -> str:
    credential = DefaultAzureCredential()
    client = BlobServiceClient(account_url=account_url, credential=credential)
    blob = client.get_blob_client(container=container, blob=blob_name)
    blob.upload_blob(
        data,
        overwrite=True,
        content_settings=ContentSettings(content_type=content_type),
    )
    return blob.url
```

### Download and list

```python
def download_blob(account_url: str, container: str, blob_name: str) -> bytes:
    client = BlobServiceClient(account_url=account_url, credential=DefaultAzureCredential())
    blob = client.get_blob_client(container=container, blob=blob_name)
    return blob.download_blob().readall()

def list_blobs(account_url: str, container: str, prefix: str = "") -> list[str]:
    client = BlobServiceClient(account_url=account_url, credential=DefaultAzureCredential())
    container_client = client.get_container_client(container)
    return [b.name for b in container_client.list_blobs(name_starts_with=prefix)]
```

### SAS token (time-limited read access)

```python
from datetime import datetime, timedelta, timezone
from azure.storage.blob import generate_blob_sas, BlobSasPermissions

def get_sas_url(account_name: str, account_key: str, container: str, blob: str, expiry_hours: int = 1) -> str:
    sas = generate_blob_sas(
        account_name=account_name,
        container_name=container,
        blob_name=blob,
        account_key=account_key,
        permission=BlobSasPermissions(read=True),
        expiry=datetime.now(timezone.utc) + timedelta(hours=expiry_hours),
    )
    return f"https://{account_name}.blob.core.windows.net/{container}/{blob}?{sas}"
```

## Azure AI Search

### Index schema (with vector field)

```python
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents.indexes.models import (
    SearchIndex, SimpleField, SearchableField, SearchFieldDataType,
    VectorSearch, HnswAlgorithmConfiguration, VectorSearchProfile,
    SearchField, SemanticConfiguration, SemanticSearch, SemanticPrioritizedFields,
    SemanticField,
)

def create_index(endpoint: str, index_name: str) -> None:
    client = SearchIndexClient(endpoint=endpoint, credential=DefaultAzureCredential())
    fields = [
        SimpleField(name="id", type=SearchFieldDataType.String, key=True),
        SearchableField(name="content", type=SearchFieldDataType.String),
        SearchableField(name="title", type=SearchFieldDataType.String),
        SimpleField(name="source", type=SearchFieldDataType.String, filterable=True),
        SimpleField(name="chunk_index", type=SearchFieldDataType.Int32, filterable=True),
        SearchField(
            name="content_vector",
            type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
            searchable=True,
            vector_search_dimensions=1536,
            vector_search_profile_name="hnsw-profile",
        ),
    ]
    vector_search = VectorSearch(
        algorithms=[HnswAlgorithmConfiguration(name="hnsw")],
        profiles=[VectorSearchProfile(name="hnsw-profile", algorithm_configuration_name="hnsw")],
    )
    semantic_search = SemanticSearch(
        configurations=[
            SemanticConfiguration(
                name="default",
                prioritized_fields=SemanticPrioritizedFields(
                    content_fields=[SemanticField(field_name="content")],
                    title_field=SemanticField(field_name="title"),
                ),
            )
        ]
    )
    index = SearchIndex(
        name=index_name,
        fields=fields,
        vector_search=vector_search,
        semantic_search=semantic_search,
    )
    client.create_or_update_index(index)
```

### Upload documents

```python
from azure.search.documents import SearchClient

def upload_documents(endpoint: str, index_name: str, docs: list[dict]) -> None:
    client = SearchClient(
        endpoint=endpoint,
        index_name=index_name,
        credential=DefaultAzureCredential(),
    )
    # Batch in chunks of 1000 (SDK limit)
    for i in range(0, len(docs), 1000):
        result = client.upload_documents(documents=docs[i:i + 1000])
        failed = [r for r in result if not r.succeeded]
        if failed:
            raise RuntimeError(f"{len(failed)} documents failed to index: {failed[0].key}")
```

### Search: text / vector / hybrid

```python
from azure.search.documents.models import VectorizedQuery

def search(
    endpoint: str,
    index_name: str,
    query: str,
    query_vector: list[float],
    top: int = 5,
    mode: str = "hybrid",  # "text" | "vector" | "hybrid"
    filter_expr: str | None = None,
) -> list[dict]:
    client = SearchClient(endpoint=endpoint, index_name=index_name, credential=DefaultAzureCredential())

    vector_query = VectorizedQuery(
        vector=query_vector,
        k_nearest_neighbors=top,
        fields="content_vector",
    ) if mode in ("vector", "hybrid") else None

    results = client.search(
        search_text=query if mode in ("text", "hybrid") else None,
        vector_queries=[vector_query] if vector_query else None,
        filter=filter_expr,
        top=top,
        query_type="semantic" if mode == "hybrid" else "simple",
        semantic_configuration_name="default" if mode == "hybrid" else None,
    )
    return [dict(r) for r in results]
```

### Search mode comparison

| Mode | When to use | Relevance | Cost |
|---|---|---|---|
| Text | Keyword lookup, exact matches | Low | Lowest |
| Vector | Semantic similarity, paraphrase | High | Medium |
| Hybrid | Production RAG (default choice) | Highest | Medium |
| Semantic reranking | High-precision Q&A on top of hybrid | Highest | Higher |

## Azure Document Intelligence

```python
from azure.ai-formrecognizer import DocumentAnalysisClient

def analyze_document(endpoint: str, file_bytes: bytes, model_id: str = "prebuilt-read") -> dict:
    client = DocumentAnalysisClient(endpoint=endpoint, credential=DefaultAzureCredential())
    poller = client.begin_analyze_document(model_id, document=file_bytes)
    result = poller.result()
    return {
        "content": result.content,
        "pages": len(result.pages),
        "tables": [
            {
                "row_count": t.row_count,
                "column_count": t.column_count,
                "cells": [{"row": c.row_index, "col": c.column_index, "text": c.content} for c in t.cells],
            }
            for t in (result.tables or [])
        ],
    }
```

### Model selection

| Model ID | Best for |
|---|---|
| `prebuilt-read` | Text extraction from any document |
| `prebuilt-layout` | Tables, checkboxes, structure-aware extraction |
| `prebuilt-document` | Key-value pairs + tables |
| `prebuilt-invoice` | Invoices |
| `prebuilt-receipt` | Receipts |
| Custom model | Domain-specific forms with consistent layout |

## Key Vault

```python
from azure.keyvault.secrets import SecretClient

def get_secret(vault_url: str, secret_name: str) -> str:
    client = SecretClient(vault_url=vault_url, credential=DefaultAzureCredential())
    return client.get_secret(secret_name).value

# Cache the client — don't recreate per call
_kv_client: SecretClient | None = None

def kv_client(vault_url: str) -> SecretClient:
    global _kv_client
    if _kv_client is None:
        _kv_client = SecretClient(vault_url=vault_url, credential=DefaultAzureCredential())
    return _kv_client
```

## Retry with tenacity

```python
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
from azure.core.exceptions import HttpResponseError, ServiceRequestError

def is_retryable(exc: Exception) -> bool:
    if isinstance(exc, HttpResponseError):
        return exc.status_code in (429, 500, 502, 503, 504)
    return isinstance(exc, ServiceRequestError)

@retry(
    retry=retry_if_exception_type((HttpResponseError, ServiceRequestError)),
    wait=wait_exponential(multiplier=1, min=2, max=60),
    stop=stop_after_attempt(5),
    reraise=True,
)
def upload_with_retry(client: SearchClient, docs: list[dict]) -> None:
    client.upload_documents(documents=docs)
```

## Error handling

```python
from azure.core.exceptions import (
    HttpResponseError,
    ResourceNotFoundError,
    ResourceExistsError,
    ClientAuthenticationError,
    ServiceRequestError,
)

try:
    result = client.get_document(key="doc-123")
except ResourceNotFoundError:
    # Document does not exist — handle gracefully
    return None
except ClientAuthenticationError:
    # Credential expired or RBAC role missing — fail fast
    raise
except HttpResponseError as e:
    if e.status_code == 429:
        # Throttled — tenacity will handle retry
        raise
    logger.error("azure_error", status=e.status_code, message=e.message)
    raise
```

## Cost controls

| Lever | Impact | How |
|---|---|---|
| AI Search tier | High | `Basic` for dev, `Standard S1` for prod; avoid `S3 HD` unless >1B docs |
| Semantic reranking | Medium | Enable only on queries that need it; billed per 1000 queries |
| Document Intelligence | Medium | Use `prebuilt-read` (cheapest) unless you need tables or KV pairs |
| Blob storage tier | Low-medium | `Hot` for active docs, `Cool` for archive; lifecycle policies auto-tier |
| Vector dimensions | Medium | 1536 (ada-002) vs 3072 (text-embedding-3-large) — smaller = cheaper storage |

## Red Flags

- **Hardcoded connection strings or storage account keys** — keys can be leaked or rotated; always use `DefaultAzureCredential` with RBAC roles, never access keys or SAS tokens in code
- **`DefaultAzureCredential` in production without pinning to `ManagedIdentityCredential`** — the credential chain tries 6+ sources sequentially; a misconfigured chain causes 30s+ startup failures; pin to `ManagedIdentityCredential` in prod
- **SDK clients recreated per request** — SDK clients are designed to be long-lived and manage connection pools; recreating them per request exhausts connections and slows every call
- **Uploading documents to AI Search one at a time** — single-document uploads are ~100× slower than batching; always use `upload_documents` in batches of up to 1000
- **Text-only search for RAG queries** — semantic/vector-only search misses exact-match terms; use hybrid search (text + vector) with semantic re-ranking for best recall across diverse queries
- **No retry policy on 429 or 503 responses** — Azure services throttle under load; wrap all SDK calls with `tenacity` or the Azure SDK's built-in retry configuration
- **`ClientAuthenticationError` silently retried** — auth errors must fail fast and loudly; retrying authentication failures burns through retry budget and delays surfacing the real problem

## Checklist

- [ ] All SDK clients use `DefaultAzureCredential` — no hardcoded keys or connection strings
- [ ] Managed Identity enabled on compute (Function App, VM, AKS node pool)
- [ ] RBAC roles assigned (`Storage Blob Data Contributor`, `Search Index Data Contributor`, etc.) — not access keys
- [ ] Secrets stored in Key Vault, not env vars or config files
- [ ] All long-running SDK calls wrapped with tenacity retry on 429/5xx
- [ ] Document upload batched in chunks of ≤1000 for Azure AI Search
- [ ] Index schema reviewed: filterable/sortable fields declared explicitly
- [ ] Hybrid search enabled for RAG queries (not text-only)
- [ ] Blob lifecycle policy configured to auto-tier cold data to `Cool`/`Archive`
- [ ] `ClientAuthenticationError` caught and surfaced immediately (not retried)
- [ ] SDK client instances reused per process — not recreated per request
- [ ] Azure resource names follow naming convention (`<service>-<env>-<region>-<suffix>`)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.