payloadguard
Python version of the popular Node.js payload-guard package Part of the Professional Python Backend Toolkit
🛡️ Lightweight, zero-dependency shape-based payload filtering and sanitization for Python
🛡️ Workflow
Request → Gatekeeper → Shape Check → Redact & Clean → Secure Response
↓
Strict Error / Fail Safe
✨ Features
- Shape-based filtering — Define what you want, auto-remove everything else
- Sensitive field protection —
password,token,secretautomatically removed - Zero dependencies — Pure Python, no external packages required
- Framework support — FastAPI, Flask, Django middleware included
- Pydantic integration — Seamless integration with Pydantic models
- Type-safe — Full type hints for better IDE support
- Blazing fast — Optimized for production performance
- Never crashes — Graceful failure mode, production-safe
📦 Installation
Basic Installation
pip install payloadguard
With Framework Support
# FastAPI
pip install "payloadguard[fastapi]"
# Flask
pip install "payloadguard[flask]"
# Django
pip install "payloadguard[django]"
# Pydantic
pip install "payloadguard[pydantic]"
# All extras
pip install "payloadguard[all]"
🚀 Quick Start
Basic Usage
from payloadguard import guard
# Define a shape
user_shape = guard.shape({
"id": "number",
"name": "string",
"email": "string",
})
# Filter data
raw_data = {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"password": "secret123", # ❌ Will be removed
"internal_notes": "VIP", # ❌ Will be removed
}
safe_data = user_shape(raw_data)
# Result: {"id": 1, "name": "John Doe", "email": "john@example.com"}
Advanced Validation
from payloadguard import guard
user_shape = guard.shape({
"email": guard.string().email().to_lower().trim(),
"age": guard.number().min(18).max(100).default(18),
"username": guard.string().min(3).max(20),
"role": guard.string().validate(lambda v: v in ["admin", "user"]).default("user"),
})
data = user_shape({
"email": " JOHN@Example.com ",
"age": 25,
"username": "john_doe",
})
# Result: {"email": "john@example.com", "age": 25, "username": "john_doe"}
🎯 Nested Object Validation (v1.0+)
Define shapes for deeply nested objects:
from payloadguard import guard
user_shape = guard.shape({
"id": "number",
"profile": {
"name": "string",
"email": guard.string().email(),
"age": guard.number().min(18),
"address": {
"street": "string",
"city": "string",
"zip_code": guard.string().regex(r"^\d{5}(-\d{4})?$"),
},
},
"posts": guard.array({
"id": "number",
"title": "string",
"tags": guard.array("string"),
}),
})
data = user_shape({
"id": 1,
"profile": {
"name": "John",
"email": "john@example.com",
"age": 30,
"address": {
"street": "123 Main St",
"city": "NYC",
"zip_code": "10001",
},
},
"posts": [
{"id": 1, "title": "Hello", "tags": ["intro"]},
],
})
🔒 Custom Error Messages (v1.0+)
from payloadguard import guard
user_shape = guard.shape({
"email": guard
.string()
.email()
.error("Please provide a valid email address"),
"username": guard
.string()
.min(5)
.error(lambda value, field: f"{field} must be at least 5 characters"),
"age": guard
.number()
.min(18)
.error_codes({"min": "AGE_TOO_YOUNG", "max": "AGE_TOO_OLD"}),
})
Error Collection
from payloadguard import compile_shape, FieldConfig
errors = []
validator = compile_shape(
{
"email": FieldConfig(type="string", email=True),
"age": FieldConfig(type="number", min=18),
},
collect_errors=True,
errors=errors,
)
validator({"email": "invalid", "age": 15})
print(errors)
# [
# ValidationError(field="email", message="...", code="email"),
# ValidationError(field="age", message="...", code="min"),
# ]
🌐 Framework Integration
FastAPI
from fastapi import FastAPI
from payloadguard.middleware.fastapi import PayloadGuardMiddleware
app = FastAPI()
request_shape = {
"name": "string",
"email": guard.string().email(),
}
app.add_middleware(
PayloadGuardMiddleware,
request_shape=request_shape,
dev_mode=True,
)
@app.post("/users")
async def create_user(request: dict):
# Request body is already sanitized
return request
Flask
from flask import Flask, request, jsonify
from payloadguard.middleware.flask import PayloadGuardMiddleware, route_guard
app = Flask(__name__)
PayloadGuardMiddleware(app, request_shape={
"name": "string",
"email": "string",
})
@app.route("/users", methods=["POST"])
@route_guard({
"name": "string",
"email": guard.string().email(),
})
def create_user():
from payloadguard.middleware.flask import get_sanitized_body
data = get_sanitized_body()
return jsonify(data)
Django
# settings.py
MIDDLEWARE = [
# ...
"payloadguard.middleware.django.PayloadGuardMiddleware",
]
PAYLOAD_GUARD_CONFIG = {
"request_shape": {
"name": "string",
"email": "string",
},
"dev_mode": True,
}
# views.py
from django.http import JsonResponse
from payloadguard.middleware.django import route_guard, get_sanitized_body
@route_guard({
"name": "string",
"email": "string",
})
def create_user(request):
data = get_sanitized_body(request)
return JsonResponse(data)
🔷 Pydantic Integration
from pydantic import BaseModel
from payloadguard.integrations.pydantic import GuardedModel, guard_model
# Option 1: Using GuardedModel base class
class UserCreate(GuardedModel):
name: str
email: str
age: int
class Config:
guard_shape = {
"name": "string",
"email": "string",
"age": "number",
}
# Create from raw data
user = UserCreate.from_raw({
"name": "John",
"email": "john@example.com",
"age": 30,
"extra": "field", # Will be removed
})
# Option 2: Using decorator
@guard_model({
"name": "string",
"email": "string",
})
class UserUpdate(BaseModel):
name: str
email: str
user = UserUpdate.from_raw({"name": "Jane", "email": "jane@example.com"})
📖 API Reference
Main API
from payloadguard import guard, Guard, build_shape, compile_shape
# Default guard instance
guard = Guard()
# Create shape
shape = guard.shape({...})
# Create custom guard with config
custom_guard = Guard()
custom_guard.config(
dev_mode=True,
sensitive_fields=["custom_secret"],
)
# String builder
guard.string()
.min(length)
.max(length)
.email()
.regex(pattern)
.trim()
.to_lower()
.to_upper()
.required()
.default(value)
.transform(fn)
.validate(fn)
.error(message)
.error_codes(codes)
# Number builder
guard.number()
.min(value)
.max(value)
.integer()
.positive()
.required()
.default(value)
.transform(fn)
.validate(fn)
.error(message)
.error_codes(codes)
Configuration Options
guard.config(
sensitive_fields=["custom_field"], # Additional sensitive fields
dev_mode=True, # Enable dev warnings
strict=True, # Strict type validation
max_array_length=1000, # Max array items to process
collect_errors=True, # Collect validation errors
fail_open=True, # Return original on error
)
⚡ Performance
| Scenario | ops/sec | avg (ms) |
|---|---|---|
| Small payload (5 fields) | 125,000+ | 0.008ms |
| Medium payload (50 items) | 18,000+ | 0.055ms |
| Large payload (1000 items) | 3,500+ | 0.285ms |
🛡️ Security Features
- Automatic sensitive field removal — password, token, secret, etc.
- Circular reference protection — No infinite loops
- Prototype pollution prevention — Safe key handling
- Graceful error handling — Never crashes in production
📝 Complete Example
E-commerce Order Validation
from payloadguard import guard
order_shape = guard.shape({
"order_id": "string",
"customer": {
"id": "number",
"name": "string",
"email": guard.string().email(),
"shipping_address": {
"street": "string",
"city": "string",
"zip_code": guard.string().regex(r"^\d{5}$"),
"country": "string",
},
},
"items": guard.array({
"product_id": "string",
"quantity": guard.number().min(1).max(100),
"price": guard.number().min(0),
}),
"payment": {
"method": "string",
"card_last4": guard.string().regex(r"^\d{4}$"),
# cvv, token automatically removed
},
})
order = order_shape({
"order_id": "ORD-123",
"customer": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"shipping_address": {
"street": "123 Main St",
"city": "New York",
"zip_code": "10001",
"country": "USA",
},
},
"items": [
{"product_id": "PROD-1", "quantity": 2, "price": 29.99},
],
"payment": {
"method": "credit_card",
"card_last4": "1234",
"cvv": "123", # Removed automatically
},
})
🧪 Testing
# Install dev dependencies
pip install "payloadguard[dev]"
# Run tests
pytest
# Run with coverage
pytest --cov=payloadguard --cov-report=html
# Run specific test file
pytest tests/test_core.py -v
📄 License
MIT
🤝 Contributing
Contributions welcome! Please read CONTRIBUTING.md first.
🔮 Roadmap
- Async validation support
- GraphQL integration
- OpenAPI schema generation
- Custom validation decorators
- Redis-based rate limiting integration
Made with ❤️ by @sannuk79
Part of the Professional Backend Toolkit
Metadata
Release files for payloadguard 1.0.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 | Provenance | |
|---|---|---|---|---|
| payloadguard-1.0.0.tar.gz | 27.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| payloadguard-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.4 kB
Release files / payloadguard-1.0.0.tar.gz
| Download URL | payloadguard-1.0.0.tar.gz |
|---|---|
| Size | 27.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3d80bcb76f205a8c9ca9e0850edf69c6a90093eaec0f396d4501e9efdeb32803
|
|
BLAKE2b-256 checksum How to use checksums |
06f51cc4797a9c578f53796e741bca9473c808f3cdd1f1c9f626489cea75af44
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.7
|
Release files / payloadguard-1.0.0-py3-none-any.whl
| Download URL | payloadguard-1.0.0-py3-none-any.whl |
|---|---|
| Size | 26.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bda37f243a9b5dd6f81fd9fe6cee1c9a1179ddf77eeadea1f9c3c5a48dabf2ae
|
|
BLAKE2b-256 checksum How to use checksums |
f2f756b6ca41e173091130bd9ee3bcc8513cb3035e985012000b88f68bbc1aab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.7
|
Security for payloadguard 1.0.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.