Azure Functions Validation
Part of the Azure Functions Python DX Toolkit — dogfood-tested by azure-functions-cookbook-python.
Read this in: 한국어 | 日本語 | 简体中文
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
Python 3.10 is deprecated. Support ends in the next minor release — Python 3.10 reaches end of life in October 2026. Importing the package on Python 3.10 emits a
FutureWarning; upgrade to Python 3.11 or newer.
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(), handlesValueErrorindividually - 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/422JSON error bodies - Response model enforcement — mismatches raise
ResponseValidationError(HTTP 500) - Decorator-first API —
@validate_httpwraps your handler, no boilerplate needed - Custom error formatting — per-handler error shaping via
ErrorFormatter
How it works
@validate_http builds a validation pipeline once at import time, then runs it for every request:
flowchart LR
Client([HTTP Client]) --> AZ[Azure Functions wrapper]
AZ --> DEC["@validate_http"]
DEC --> PL[pipeline]
PL -->|parse / validate| AD[adapter]
AD --> H[your handler]
H --> PL2[pipeline]
PL2 -->|validate / serialize| AD2[adapter]
AD2 --> RESP([HttpResponse])
Decorator order and parameter passthrough:
@validate_httpmust sit directly beneath@app.route(...)so the Azure Functions worker still sees a bindable handler signature. WorkerCompat deliberately hides the validation-injected models from the worker-visible signature, so the worker binds onlyreq(plus any declared input/output binding params likecontext) by name;@validate_httpthen injects the validated models at runtime. The worker's name-binding contract — and this repo's WorkerCompat tests that verify it — is explained in How the worker binds handlers.
Before / After
Without this package — manual parsing, manual errors, no contracts
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:
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:
$ curl -s -X POST http://localhost:7071/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
{"message": "Hello Alice", "status": "success"}
HTTP 200
Missing required field → automatic error response:
$ curl -s -X POST http://localhost:7071/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice"}'
{
"detail": [
{
"loc": ["body", "email"],
"msg": "Field required",
"type": "missing"
}
]
}
HTTP 422 — standardized error response, automatic
Invalid JSON → clear error:
$ curl -s -X POST http://localhost:7071/api/users \
-H "Content-Type: application/json" \
-d 'not json'
{"detail": [{"loc": [], "msg": "Invalid JSON", "type": "value_error"}]}
HTTP 400
Why not just use Pydantic directly?
You can. If a handler is simple, calling Model.model_validate(req.get_json()) inside a try/except is perfectly fine — this package does not replace Pydantic, it wraps your Pydantic models. Reach for @validate_http when the manual approach starts repeating across endpoints:
# Raw Pydantic in the handler — you own every step
def create_user(req: func.HttpRequest) -> func.HttpResponse:
try:
body = CreateUserRequest.model_validate(req.get_json())
except ValueError: # invalid JSON
return func.HttpResponse(..., status_code=400)
except ValidationError as exc: # shape/type errors
return func.HttpResponse(exc.json(), status_code=422) # your own envelope
result = CreateUserResponse(message=f"Hello {body.name}")
return func.HttpResponse(result.model_dump_json(), mimetype="application/json")
The model is one line; the parsing, the two error branches, the error envelope, and the response serialization are the other twelve — and every handler re-implements them, often inconsistently. @validate_http collapses that to the decorator:
- No repeated glue — parsing,
try/except, and serialization move out of the handler body. - Consistent errors — every endpoint returns the same
400/422{"detail": [...]}envelope instead of a per-handler shape. - Response enforcement —
response_modelcatches contract drift on the way out, not just on the way in. - Discoverable metadata — the decorator records request/response models so
azure-functions-openapi's bridge can generate OpenAPI docs from the same models. See docs/usage.md for the metadata bridge.
In short: raw Pydantic validates one handler; @validate_http standardizes every handler. Compare the full Before / After above.
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
API documentation (azure-functions-openapi), runtime/graph deployment (azure-functions-langgraph), and project scaffolding (azure-functions-scaffold) are handled by sibling packages.
Package names
Three names cover three different contexts:
| Context | Name |
|---|---|
| GitHub repo | azure-functions-validation-python |
| PyPI package | azure-functions-validation |
| Python import | azure_functions_validation |
The repository carries the -python suffix to mark it as the Python implementation. The PyPI package follows Python ecosystem conventions and is published without the suffix, so installation stays idiomatic: pip install azure-functions-validation. See the FAQ entry for the long version.
Installation
pip install azure-functions-validation
Your Azure Functions app should also include:
azure-functions
azure-functions-validation
For local development:
git clone https://github.com/yeongseon/azure-functions-validation-python.git
cd azure-functions-validation-python
pip install -e .[dev]
Quick Start
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.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:
func start
Verify locally and on Azure
After deploying (see docs/deployment.md), the same request produces the same response in both environments.
Local
curl -s http://localhost:7071/api/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
{"message": "Hello Alice", "status": "success"}
Azure
curl -s "https://<your-app>.azurewebsites.net/api/users" \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
{"message": "Hello Alice", "status": "success"}
Invalid requests return the same 400 error in both environments:
Local
curl -s http://localhost:7071/api/users \
-H "Content-Type: application/json" \
-d 'not json'
{"detail": [{"loc": [], "msg": "Invalid JSON", "type": "value_error"}]}
HTTP 400
Azure
curl -s "https://<your-app>.azurewebsites.net/api/users" \
-H "Content-Type: application/json" \
-d 'not json'
{"detail": [{"loc": [], "msg": "Invalid JSON", "type": "value_error"}]}
HTTP 400
Manually verified by maintainers against a temporary Azure Functions deployment (koreacentral, Python 3.12, Consumption plan); response captured and URL anonymized. See docs/deployment.md for the verification status and context.
Status codes and controlled errors
Return 201 on creation and raise controlled HTTP errors (e.g. 404) without
bypassing validation. Set the success status with status_code= and raise
HttpError to render an error through the standard {"detail": [...]} envelope:
import azure.functions as func
from pydantic import BaseModel
from azure_functions_validation import HttpError, validate_http
app = func.FunctionApp()
_USERS: dict[int, "UserResponse"] = {}
class CreateUserRequest(BaseModel):
name: str
email: str
class UserResponse(BaseModel):
id: int
name: str
@app.route(route="users", methods=["POST"], auth_level=func.AuthLevel.ANONYMOUS)
@validate_http(body=CreateUserRequest, response_model=UserResponse, status_code=201)
def create_user(req: func.HttpRequest, body: CreateUserRequest) -> UserResponse:
user = UserResponse(id=len(_USERS) + 1, name=body.name)
_USERS[user.id] = user
return user # HTTP 201
@app.route(route="users/{user_id}", methods=["GET"], auth_level=func.AuthLevel.ANONYMOUS)
@validate_http(response_model=UserResponse)
def get_user(req: func.HttpRequest) -> UserResponse:
user = _USERS.get(int(req.route_params["user_id"]))
if user is None:
raise HttpError(404, "User not found") # standardized error envelope
return user
A missing user returns a consistent error body:
{"detail": [{"loc": [], "msg": "User not found", "type": "http_error"}]}
HTTP 404
HttpError also accepts a pre-built detail list for richer errors, and
server-side (>=500) errors are always sanitized so internal details never
leak to clients.
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/ - Endpoint metadata contract:
docs/METADATA_SPEC.md— theendpointnamespace payload (parameters,inmapping,requiredrules) thatazure-functions-openapiconsumes - Product requirements:
PRD.md - Design principles:
DESIGN.md - Design notes:
docs/design/optional-body-347.md— why a configured body is always reported as required (#347)
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-python | OpenAPI spec generation and Swagger UI |
| azure-functions-validation-python | Request/response validation and serialization |
| azure-functions-db-python | SQLAlchemy-powered DB integration helpers (poll-based pseudo trigger, input/output/client injection) |
| azure-functions-langgraph-python | LangGraph deployment adapter for Azure Functions |
| azure-functions-scaffold-python | Project scaffolding CLI |
| azure-functions-logging-python | Structured logging and observability |
| azure-functions-doctor-python | Pre-deploy diagnostic CLI |
| azure-functions-durable-graph-python | Manifest-first graph runtime with Durable Functions (experimental) |
| azure-functions-knowledge-python | Knowledge retrieval (RAG) decorators |
| azure-functions-cookbook-python | Dogfood examples — runnable recipes that exercise the full toolkit |
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 overviewllms-full.txt— Expanded reference with full signatures and patterns
Reference the files at repository root:
- https://github.com/yeongseon/azure-functions-validation-python/blob/main/llms.txt
- https://github.com/yeongseon/azure-functions-validation-python/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
Metadata
Release files for azure-functions-validation 0.13.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| azure_functions_validation-0.13.0.tar.gz | 182.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| azure_functions_validation-0.13.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 218.0 kB
Release files / azure_functions_validation-0.13.0.tar.gz
| Download URL | azure_functions_validation-0.13.0.tar.gz |
|---|---|
| Size | 182.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cc7693f333e57f8658e92e6910797f11b21286661d922744ad937c55b52af274
|
|
BLAKE2b-256 checksum How to use checksums |
e9cda298c7ff59662976ef56c8ab025a0e56895d33a9689ac647abe809c4877a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / azure_functions_validation-0.13.0-py3-none-any.whl
| Download URL | azure_functions_validation-0.13.0-py3-none-any.whl |
|---|---|
| Size | 35.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
14ee85db3f39c244630bb025fab4ef390d39c1b35d6d5f90934777e721c43436
|
|
BLAKE2b-256 checksum How to use checksums |
153e1edd372be98191c57b332146362607c536e530512814ac5edabddc5b71c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency log