Lightweight OpenAPI decorator for Azure Functions with Pydantic support
Project description
Azure Functions OpenAPI with Pydantic
A lightweight, zero-opinion OpenAPI decorator for Azure Functions with full Pydantic support. Generate accurate API documentation with minimal code.
Features
- 🎯 Simple decorator syntax - Just add
@openapi()to your functions - 📝 Pydantic integration - Full validation and schema generation
- 🔓 Zero opinions - Return models directly or use your own envelope pattern
- 🏗️ Nested models - Automatically expands nested Pydantic models
- ⚡ Blueprint support - Works seamlessly with Azure Functions blueprints
- 🏷️ Smart tag inference - Auto-organizes endpoints by analyzing routes
- 📊 Docstring introspection - Automatically extracts summaries from docstrings
Installation
pip install azure-functions-openapi-pydantic
Quick Start
import azure.functions as func
from pydantic import BaseModel, EmailStr
from azfunc_openapi_pydantic import openapi, OpenAPIBlueprint
# Define your models
class User(BaseModel):
id: str
name: str
email: EmailStr
# Create blueprint
api_bp = OpenAPIBlueprint()
# Direct response (no envelope)
@api_bp.route(route="users/{id}", methods=["GET"])
@openapi(responses={200: User, 404: dict})
def get_user(req: func.HttpRequest) -> func.HttpResponse:
"""Get a user by ID"""
user = User(id="123", name="Alice", email="alice@example.com")
return func.HttpResponse(
json.dumps(user.model_dump()),
mimetype="application/json"
)
# Register blueprint
app = func.FunctionApp()
app.register_functions(api_bp)
OpenAPI spec shows:
{
"paths": {
"users/{id}": {
"get": {
"responses": {
"200": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/User"}
}
}
}
}
}
}
}
}
Optional: Define Your Own Envelope Pattern
The library doesn't force any response structure. If you want a consistent envelope, define it yourself:
from typing import Generic, TypeVar, Optional
from pydantic import BaseModel
T = TypeVar('T')
class ApiResponse(BaseModel, Generic[T]):
"""Your custom envelope"""
success: bool
data: Optional[T] = None
error: Optional[dict] = None
@classmethod
def ok(cls, data, status_code=200):
return func.HttpResponse(
json.dumps({"success": True, "data": data.model_dump()}),
mimetype="application/json",
status_code=status_code
)
# Use it
@openapi(responses={200: ApiResponse[User]})
def get_user(req: func.HttpRequest):
"""Get user with envelope"""
user = User(id="123", name="Alice", email="alice@example.com")
return ApiResponse.ok(user) # → {success: true, data: {...}}
OpenAPI spec shows your envelope:
{
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"success": {"type": "boolean"},
"data": {"$ref": "#/components/schemas/User"}
}
}
}
}
}
}
Generate OpenAPI Spec & Swagger UI
from azfunc_openapi_pydantic import generate_openapi_spec, generate_swagger_ui
@app.route(route="openapi.json", methods=["GET"])
def openapi_spec(req: func.HttpRequest) -> func.HttpResponse:
"""OpenAPI 3.0 spec"""
spec = generate_openapi_spec(
title="My API",
version="1.0.0",
description="API documentation"
)
return func.HttpResponse(
json.dumps(spec, indent=2),
mimetype="application/json"
)
@app.route(route="docs", methods=["GET"])
def swagger_ui(req: func.HttpRequest) -> func.HttpResponse:
"""Interactive Swagger UI"""
return generate_swagger_ui(
title="My API Documentation",
openapi_url="/api/openapi.json"
)
Visit: http://localhost:7071/api/docs
Decorator Options
from azfunc_openapi_pydantic import openapi, ResponsesMap
@openapi(
request=CreateUserRequest, # Request body model
responses={ # Status code → Response model
200: User,
201: User,
400: ErrorDetail,
404: NotFoundError
},
# Or use ResponsesMap for validation:
responses=ResponsesMap({
200: User,
404: NotFoundError
}),
params={ # Query/path parameters
"limit": int,
"offset": int,
"active": bool
},
tags=["Users"] # Optional (auto-inferred if omitted)
)
def my_endpoint(req: func.HttpRequest):
"""
This docstring becomes the OpenAPI summary.
Additional lines become the description.
"""
pass
Request Validation
The decorator automatically validates request bodies:
@openapi(
request=CreateUserRequest,
responses={201: User, 400: dict}
)
def create_user(req: func.HttpRequest) -> func.HttpResponse:
"""Create user with automatic validation"""
# Validated body is attached to the request
body = req.validated_body
user = User(
id=generate_id(),
name=body.name,
email=body.email,
age=body.age
)
return func.HttpResponse(
json.dumps(user.model_dump()),
mimetype="application/json",
status_code=201
)
Invalid requests return automatic 400 responses with validation errors.
Parameter Injection
Instead of accessing req, inject validated parameters directly:
@openapi(
request=CreateUserRequest,
responses={201: User}
)
def create_user(body: CreateUserRequest) -> func.HttpResponse:
"""Body is injected and validated"""
user = User(id=generate_id(), **body.model_dump())
return func.HttpResponse(json.dumps(user.model_dump()))
Query parameters work too:
@openapi(
responses={200: list[User]},
params={"limit": int, "offset": int}
)
def list_users(limit: int = 10, offset: int = 0) -> func.HttpResponse:
"""Parameters are injected and type-converted"""
users = get_users(limit=limit, offset=offset)
return func.HttpResponse(json.dumps([u.model_dump() for u in users]))
Nested Models
Nested Pydantic models are automatically expanded:
class Address(BaseModel):
street: str
city: str
country: str = "US"
class UserProfile(BaseModel):
user: User
address: Address
preferences: dict
@openapi(responses={200: UserProfile})
def get_profile(req: func.HttpRequest):
"""Returns fully expanded nested schema"""
pass
Blueprints
Organize your API with blueprints:
from azfunc_openapi_pydantic import OpenAPIBlueprint
# Create blueprint with default tags (optional)
users_bp = OpenAPIBlueprint(tags=["Users"])
@users_bp.route(route="users", methods=["GET"])
@openapi(responses={200: list[User]})
def list_users(req: func.HttpRequest):
"""Inherits 'Users' tag from blueprint"""
pass
@users_bp.route(route="users/{id}", methods=["GET"])
@openapi(
responses={200: User},
tags=["Admin"] # Override blueprint tag
)
def get_user(req: func.HttpRequest):
"""Uses 'Admin' tag instead"""
pass
# Register
app.register_functions(users_bp)
Smart Tag Inference
If you don't specify tags, they're automatically inferred from your routes:
# Routes: api/v1/users, api/v1/users/{id}, api/v1/products
# → Tags: "Users", "Products" (inferred from diverging path segments)
# Routes: users, users/{id}, users/{id}/profile
# → Tag: "Users" (sub-resources grouped under parent)
Explicit tags always take precedence over inference.
Examples
The examples/ directory contains complete Azure Functions applications:
Basic User API
Simple user management API demonstrating core features:
- Direct responses vs. envelope pattern
- Request validation and parameter injection
- OpenAPI spec and Swagger UI generation
BOL Extraction Service
Complex real-world API demonstrating:
- Multiple blueprints
- Complex nested models
- Blueprint-level organization
- Production-ready structure
cd examples/basic-user-api # or bol-extraction-service
pip install -r requirements.txt
func start
# Visit http://localhost:7071/api/docs
See examples/README.md for more details.
License
MIT License - see LICENSE for details.
Contributing
Contributions welcome! This library aims to stay lightweight and unopinionated. Please open an issue before starting work on major features.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file azure_functions_openapi_pydantic-1.0.0b1.tar.gz.
File metadata
- Download URL: azure_functions_openapi_pydantic-1.0.0b1.tar.gz
- Upload date:
- Size: 31.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c290abb6c102a80c1c11a69479deaf4301656756381de899ea628628ced46193
|
|
| MD5 |
1739b44ebfc195f7edcc699dcc79b570
|
|
| BLAKE2b-256 |
503d479f20fa95225412fa036437d0a5a760231ad87274d5dda197fe7b6ea207
|
File details
Details for the file azure_functions_openapi_pydantic-1.0.0b1-py3-none-any.whl.
File metadata
- Download URL: azure_functions_openapi_pydantic-1.0.0b1-py3-none-any.whl
- Upload date:
- Size: 12.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f396031e56e6eccc37619837b6621ab17534d71beb9ecdddc635d6796c43380f
|
|
| MD5 |
ef0f82bdc185d4240fdd0d969c6e9fbe
|
|
| BLAKE2b-256 |
1490d8e1821a8b548c908440969c5815a67111eb7dbbf2f45e942503de922b18
|