Metadata-Version: 2.4
Name: azure-functions-validation
Version: 0.7.1
Summary: Validation and serialization layer for Azure Functions Python v2 programming model
Project-URL: Homepage, https://yeongseon.github.io/azure-functions-validation/
Project-URL: Documentation, https://yeongseon.github.io/azure-functions-validation/
Project-URL: Repository, https://github.com/yeongseon/azure-functions-validation
Project-URL: Issues, https://github.com/yeongseon/azure-functions-validation/issues
Author-email: Yeongseon Choe <yeongseon.choe@gmail.com>
License: MIT
License-File: LICENSE
Keywords: azure,azure-functions,pydantic,python,serialization,serverless,v2,validation
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: <3.15,>=3.10
Requires-Dist: azure-functions
Requires-Dist: pydantic<3.0,>=2.0
Provides-Extra: dev
Requires-Dist: bandit==1.9.4; extra == 'dev'
Requires-Dist: build; extra == 'dev'
Requires-Dist: coverage; extra == 'dev'
Requires-Dist: git-cliff; extra == 'dev'
Requires-Dist: hatch; extra == 'dev'
Requires-Dist: mypy==1.20.0; extra == 'dev'
Requires-Dist: pre-commit; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-anyio; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: requests; extra == 'dev'
Requires-Dist: ruff==0.15.10; extra == 'dev'
Requires-Dist: types-requests; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material<10.0; extra == 'docs'
Requires-Dist: mkdocs<2.0; extra == 'docs'
Requires-Dist: mkdocstrings[python]<2.0; extra == 'docs'
Description-Content-Type: text/markdown

# Azure Functions Validation

[![PyPI](https://img.shields.io/pypi/v/azure-functions-validation.svg)](https://pypi.org/project/azure-functions-validation/)
[![Python Version](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)](https://pypi.org/project/azure-functions-validation/)
[![CI](https://github.com/yeongseon/azure-functions-validation/actions/workflows/ci-test.yml/badge.svg)](https://github.com/yeongseon/azure-functions-validation/actions/workflows/ci-test.yml)
[![Release](https://github.com/yeongseon/azure-functions-validation/actions/workflows/publish-pypi.yml/badge.svg)](https://github.com/yeongseon/azure-functions-validation/actions/workflows/publish-pypi.yml)
[![Security Scans](https://github.com/yeongseon/azure-functions-validation/actions/workflows/security.yml/badge.svg)](https://github.com/yeongseon/azure-functions-validation/actions/workflows/security.yml)
[![codecov](https://codecov.io/gh/yeongseon/azure-functions-validation/branch/main/graph/badge.svg)](https://codecov.io/gh/yeongseon/azure-functions-validation)
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://pre-commit.com/)
[![Docs](https://img.shields.io/badge/docs-gh--pages-blue)](https://yeongseon.github.io/azure-functions-validation/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

Read this in: [한국어](README.ko.md) | [日本語](README.ja.md) | [简体中文](README.zh-CN.md)

Validation and serialization for the **Azure Functions Python v2 programming model**.

---

Part of the **Azure Functions Python DX Toolkit**
→ Bring FastAPI-like developer experience to Azure Functions

## Why this exists

Azure Functions Python v2 handlers often drift into the same repeated problems:

- **Repeated manual parsing** — every handler calls `req.get_json()`, `req.params.get()`, handles `ValueError` individually
- **Inconsistent error responses** — some handlers return 400, others 422, formats vary across the project
- **Missing response contracts** — response payloads silently diverge from the intended schema
- **No type safety** — request data flows through as untyped dicts, bugs surface only at runtime

## What it does

- **Typed validation** — body, query, path, and header parameters validated via Pydantic v2
- **Automatic error responses** — invalid requests get consistent `400`/`422` JSON error bodies
- **Response model enforcement** — mismatches raise `ResponseValidationError` (HTTP 500)
- **Decorator-first API** — `@validate_http` wraps your handler, no boilerplate needed

## Before / After

**Without** this package — manual parsing, manual errors, no contracts:

```python
import json
import azure.functions as func

app = func.FunctionApp()


@app.route(route="users", methods=["POST"])
def create_user(req: func.HttpRequest) -> func.HttpResponse:
    try:
        body = req.get_json()
    except ValueError:
        return func.HttpResponse(
            json.dumps({"error": "Invalid JSON"}),
            status_code=400,
            mimetype="application/json",
        )

    name = body.get("name")
    email = body.get("email")
    if not name or not isinstance(name, str):
        return func.HttpResponse(
            json.dumps({"error": "name is required"}),
            status_code=400,
            mimetype="application/json",
        )
    if not email or not isinstance(email, str):
        return func.HttpResponse(
            json.dumps({"error": "email is required"}),
            status_code=400,
            mimetype="application/json",
        )

    return func.HttpResponse(
        json.dumps({"message": f"Hello {name}", "status": "success"}),
        mimetype="application/json",
    )
```

**With** `@validate_http` — typed, consistent, contract-enforced:

```python
import azure.functions as func
from pydantic import BaseModel

from azure_functions_validation import validate_http

app = func.FunctionApp()


class CreateUserRequest(BaseModel):
    name: str
    email: str


class CreateUserResponse(BaseModel):
    message: str
    status: str = "success"


@app.route(route="users", methods=["POST"])
@validate_http(body=CreateUserRequest, response_model=CreateUserResponse)
def create_user(req: func.HttpRequest, body: CreateUserRequest) -> CreateUserResponse:
    return CreateUserResponse(message=f"Hello {body.name}")
```

> Manual parsing and validation disappear from the handler. Error formatting and response contracts — handled.

### What you get

**Valid request** → typed response:

```bash
$ curl -s -X POST http://localhost:7071/api/users \
    -H "Content-Type: application/json" \
    -d '{"name": "Alice", "email": "alice@example.com"}'
```

```json
{"message": "Hello Alice", "status": "success"}
```

> HTTP 200

**Missing required field** → automatic error response:

```bash
$ curl -s -X POST http://localhost:7071/api/users \
    -H "Content-Type: application/json" \
    -d '{"name": "Alice"}'
```

```json
{
  "detail": [
    {
      "loc": ["email"],
      "msg": "Field required",
      "type": "missing"
    }
  ]
}
```

> HTTP 422 — standardized error response, automatic

**Invalid JSON** → clear error:

```bash
$ curl -s -X POST http://localhost:7071/api/users \
    -H "Content-Type: application/json" \
    -d 'not json'
```

```json
{"detail": [{"loc": [], "msg": "Invalid JSON", "type": "value_error"}]}
```

> HTTP 400

## FastAPI comparison

| Feature | FastAPI | azure-functions-validation |
|---------|---------|---------------------------|
| Request body parsing | Built-in via type hints | `@validate_http(body=Model)` |
| Query/path/header validation | `Query()`, `Path()`, `Header()` | `@validate_http(query=Model, path=Model, headers=Model)` |
| Response model | `response_model=` | `@validate_http(response_model=Model)` |
| Validation errors | Automatic 422 | Automatic 422 with `{"detail": [...]}` |
| Error customization | Exception handlers | `ErrorFormatter` callback |

## Scope

- Azure Functions Python **v2 programming model**
- HTTP-triggered functions registered on `func.FunctionApp()`
- Pydantic v2-based request and response validation

This package does **not** target the legacy `function.json`-based v1 programming model.

## What this package does not do

This package does not own:
- API documentation and spec generation — use [`azure-functions-openapi`](https://github.com/yeongseon/azure-functions-openapi)
- Runtime exposure or graph deployment — use [`azure-functions-langgraph`](https://github.com/yeongseon/azure-functions-langgraph)
- Project scaffolding — use [`azure-functions-scaffold`](https://github.com/yeongseon/azure-functions-scaffold)

## Features

- Typed body, query, path, and header validation via `@validate_http`
- Automatic 400 / 422 responses with `{"detail": [...]}` envelope
- Response model validation — mismatches raise `ResponseValidationError` (HTTP 500)
- Custom per-handler error formatting via `ErrorFormatter`

## Installation

```bash
pip install azure-functions-validation
```

Your Azure Functions app should also include:

```text
azure-functions
azure-functions-validation
```

For local development:

```bash
git clone https://github.com/yeongseon/azure-functions-validation.git
cd azure-functions-validation
pip install -e .[dev]
```

## Quick Start

```python
import azure.functions as func
from pydantic import BaseModel

from azure_functions_validation import validate_http


class CreateUserRequest(BaseModel):
    name: str
    email: str


class CreateUserResponse(BaseModel):
    message: str
    status: str = "success"


app = func.FunctionApp()


@app.function_name(name="create_user")
@app.route(route="users", methods=["POST"], auth_level=func.AuthLevel.ANONYMOUS)
@validate_http(body=CreateUserRequest, response_model=CreateUserResponse)
def create_user(req: func.HttpRequest, body: CreateUserRequest) -> CreateUserResponse:
    return CreateUserResponse(message=f"Hello {body.name}")
```

Start the Functions host locally:

```bash
func start
```

### Verify locally and on Azure

After deploying (see [docs/deployment.md](docs/deployment.md)), the same request produces the same response in both environments.

#### Local

```bash
curl -s http://localhost:7071/api/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "alice@example.com"}'
```

```json
{"message": "Hello Alice", "status": "success"}
```

#### Azure

```bash
curl -s "https://<your-app>.azurewebsites.net/api/users" \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "alice@example.com"}'
```

```json
{"message": "Hello Alice", "status": "success"}
```
Invalid requests return the same `400` error in both environments:

#### Local

```bash
curl -s http://localhost:7071/api/users \
  -H "Content-Type: application/json" \
  -d 'not json'
```

```json
{"detail": [{"loc": [], "msg": "Invalid JSON", "type": "value_error"}]}
```

> HTTP 400

#### Azure

```bash
curl -s "https://<your-app>.azurewebsites.net/api/users" \
  -H "Content-Type: application/json" \
  -d 'not json'
```

```json
{"detail": [{"loc": [], "msg": "Invalid JSON", "type": "value_error"}]}
```

> HTTP 400

> Verified against a temporary Azure Functions deployment in koreacentral (Python 3.12, Consumption plan). Response captured and URL anonymized.

## When to use

- You have HTTP-triggered Azure Functions that accept JSON request bodies
- You want Pydantic-based validation without writing manual parsing code
- You need consistent error response formats across handlers
- You want response schema enforcement to catch contract drift

## Documentation

- Project docs live under `docs/`
- Smoke-tested examples live under `examples/`
- Product requirements: `PRD.md`
- Design principles: `DESIGN.md`

## Ecosystem

This package is part of the **Azure Functions Python DX Toolkit**.

**Design principle:** `azure-functions-validation` owns request/response validation and serialization. `azure-functions-openapi` owns API documentation and spec generation. `azure-functions-langgraph` owns LangGraph runtime exposure.

| Package | Role |
|---------|------|
| [azure-functions-openapi](https://github.com/yeongseon/azure-functions-openapi) | OpenAPI spec generation and Swagger UI |
| **azure-functions-validation** | Request/response validation and serialization |
| [azure-functions-db](https://github.com/yeongseon/azure-functions-db) | Database bindings for SQL, PostgreSQL, MySQL, SQLite, and Cosmos DB |
| [azure-functions-langgraph](https://github.com/yeongseon/azure-functions-langgraph) | LangGraph deployment adapter for Azure Functions |
| [azure-functions-scaffold](https://github.com/yeongseon/azure-functions-scaffold) | Project scaffolding CLI |
| [azure-functions-logging](https://github.com/yeongseon/azure-functions-logging) | Structured logging and observability |
| [azure-functions-doctor](https://github.com/yeongseon/azure-functions-doctor) | Pre-deploy diagnostic CLI |
| [azure-functions-durable-graph](https://github.com/yeongseon/azure-functions-durable-graph) | Manifest-first graph runtime with Durable Functions *(experimental)* |
| [azure-functions-python-cookbook](https://github.com/yeongseon/azure-functions-python-cookbook) | Recipes and examples |


## For AI Coding Assistants

When integrating with LLM-powered coding assistants, provide these files for context:

- **`llms.txt`** — Concise index with quick start and API overview
- **`llms-full.txt`** — Expanded reference with full signatures and patterns

Reference the files at repository root:
- https://github.com/yeongseon/azure-functions-validation/blob/main/llms.txt
- https://github.com/yeongseon/azure-functions-validation/blob/main/llms-full.txt

## Disclaimer

This project is an independent community project and is not affiliated with,
endorsed by, or maintained by Microsoft.

Azure and Azure Functions are trademarks of Microsoft Corporation.

## License

MIT
