Skip to main content

Lightweight, zero-dependency shape-based payload filtering and sanitization for Python

Project description

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

Python 3.9+ Zero Dependencies Type Hinted MIT License


🛡️ 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 protectionpassword, token, secret automatically 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

payloadguard-1.0.0.tar.gz (27.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

payloadguard-1.0.0-py3-none-any.whl (26.6 kB view details)

Uploaded Python 3

File details

Details for the file payloadguard-1.0.0.tar.gz.

File metadata

  • Download URL: payloadguard-1.0.0.tar.gz
  • Upload date:
  • Size: 27.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for payloadguard-1.0.0.tar.gz
Algorithm Hash digest
SHA256 3d80bcb76f205a8c9ca9e0850edf69c6a90093eaec0f396d4501e9efdeb32803
MD5 d3f5b14afeafd0356749947f664bcd00
BLAKE2b-256 06f51cc4797a9c578f53796e741bca9473c808f3cdd1f1c9f626489cea75af44

See more details on using hashes here.

File details

Details for the file payloadguard-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: payloadguard-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 26.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for payloadguard-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bda37f243a9b5dd6f81fd9fe6cee1c9a1179ddf77eeadea1f9c3c5a48dabf2ae
MD5 7c8b2de66333e85c2205b60e869f9e64
BLAKE2b-256 f2f756b6ca41e173091130bd9ee3bcc8513cb3035e985012000b88f68bbc1aab

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page