Skip to content

About

Zero-dependency input validation. Like Pydantic in one file. 247K val/s. Part of the tiny-* ecosystem.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

tiny-validator β€” Zero-Dependency Input Validation

CI

Like Pydantic, but in one file. Zero dependencies.

version 0.5.0 Python 3.8+ License: MIT Zero Dependencies Part of the tiny-* ecosystem

tiny-validator is a single-file, declarative input validation library. No Pydantic, no marshmallow, no attrs. Pure Python β€” chainable field types, nested schemas, defaults, and structured error reporting.

✨ Features

  • πŸͺΆ Zero dependencies β€” stdlib re only
  • πŸ“¦ Single file β€” drop tiny_validator.py anywhere
  • 🧱 Declarative schemas β€” Schema({...}) with fields.* namespace
  • 🎯 12+ field types β€” string, integer, float, boolean, list, dict, email, URL, UUID, date, datetime, decimal
  • πŸͺ† Nested schemas β€” fields.Dict(schema=...) for objects
  • πŸ›‘οΈ Strict mode β€” reject unknown fields
  • 🎁 Defaults β€” built into Field
  • 🚨 Structured errors β€” ValidationError.errors is a list of {path, message}
  • πŸŽ€ Decorator β€” @validate(body=..., query=..., headers=...)
  • 🧬 JSON Schema import β€” json_schema() / from_json_schema() for MCP tool contracts
  • πŸ“¦ Batch validation β€” schema.validate_many(items) splits valid/invalid
  • 🩹 Partial validation β€” schema.partial() ignores unknown fields

πŸš€ Quick Start

from tiny_validator import Schema, fields, ValidationError, validate

user_schema = Schema({
    "name": fields.String(min_length=1, max_length=100),
    "email": fields.Email(),
    "age":  fields.Integer(min_value=0, max_value=150, required=False),
    "role": fields.String(choices=["admin", "user"]),
}, strict=True)

try:
    clean = user_schema({"name": "Hussain", "email": "h@x.co", "role": "admin"})
except ValidationError as exc:
    print(exc.errors)

πŸŽ€ Async Validation

from tiny_validator import AsyncField, AsyncValidator, fields

async def check_repo(value: str) -> str | None:
    """Return None if valid, error string if not."""
    exists = await github_api.repo_exists(value)
    return None if exists else "repository not found"

schema = AsyncValidator({
    "repo": AsyncField(validator_fn=check_repo),
    "limit": fields.Integer(default=10),
})

errors = await schema.validate({"repo": "owner/name"})
# [] if repo exists, else [{"path": "repo", "message": "repository not found"}]

# Raises ValidationError like Schema
result = await schema({"repo": "owner/name"})

🧬 JSON Schema import

For MCP tools and external contracts that already speak JSON Schema:

from tiny_validator import json_schema, from_json_schema

# From a dict
js = {
    "type": "object",
    "properties": {
        "name":  {"type": "string", "minLength": 1},
        "age":   {"type": "integer", "minimum": 0},
        "email": {"type": "string", "format": "email"},
    },
    "required": ["name", "email"],
    "additionalProperties": False,
}
validator = json_schema(js)
errors = validator.validate({"name": "Alice", "email": "alice@example.com"})

# From a JSON string
schema = from_json_schema('{"type": "object", "properties": {"x": {"type": "integer"}}}')

πŸ“¦ Batch validation

Validate a list of items and split into valid/invalid buckets:

from tiny_validator import Schema, fields

schema = Schema({
    "name":  fields.String(min_length=1),
    "email": fields.Email(),
})

result = schema.validate_many([
    {"name": "Alice", "email": "alice@example.com"},
    {"name": "",      "email": "bad"},
])
# result["valid"]   = [first_item]
# result["invalid"] = [{"item": bad_item, "errors": [...]}]

🩹 Partial validation

Ignore unknown fields when the source might include extras (e.g. webhook payloads):

from tiny_validator import Schema, fields

schema = Schema({"name": fields.String(min_length=1)})

partial = schema.partial()
errors = partial.validate({"name": "Alice", "extra": "ignored"})
# errors == []  (extra field is dropped)

πŸŽ€ Decorator

@validate(
    body=Schema({"query": fields.String(min_length=3), "limit": fields.Integer(default=10)}),
    query=Schema({"lang": fields.String(choices=["en", "ar"], required=False)}),
)
def search_handler(body, query):
    return {"q": body["query"], "limit": body["limit"]}

🧱 Field Reference

Field Common kwargs
String min_length, max_length, pattern, choices, case_insensitive
Integer min_value, max_value, choices
Float min_value, max_value, allow_int
Boolean β€”
AsyncField(validator_fn) required, default β€” async field with await-able validator
List(item_field=…) min_length, max_length, unique
Dict(schema=…) required (alias Object)
Email / Url / Uuid (all extend String)
Date / DateTime (ISO-8601 strings)
Decimal min_value, max_value, max_digits, decimal_places
Any allow_none=True by default

All fields accept required=True/False and default=....

πŸ›‘οΈ Error Shape

exc.errors == [
    {"path": "user.email", "message": "value must be a valid email address"},
    {"path": "user.age",   "message": "value must be <= 150"},
]

exc.to_dict() == {
    "error": "validation_failed",
    "details": [
        {"path": "user.email", "message": "value must be a valid email address"},
        ...
    ],
}

Agent Workflow Fit

tiny-validator works well for agent-built services where bad inputs should fail early and explain themselves:

  • Webhook payload gates β€” validate provider callbacks before they reach handlers.
  • Tool-call contracts β€” check JSON arguments before running filesystem, browser, or GitHub actions.
  • Bounty repro inputs β€” keep proof-of-concept scripts honest with strict, readable schemas.
  • Config and env checks β€” validate generated config dictionaries before a cron job starts.

Pair it with tiny-router for HTTP endpoints, tiny-cli for operator commands, and tiny-secret when validation errors must avoid leaking sensitive values.

JSON Schema tool contracts

Use from_json_schema() when a tool, MCP server, or generated config already describes its arguments as JSON Schema:

from tiny_validator import from_json_schema

schema = from_json_schema({
    "type": "object",
    "required": ["repo", "limit"],
    "properties": {
        "repo": {"type": "string", "pattern": r"^[^/]+/[^/]+$"},
        "limit": {"type": "integer", "minimum": 1, "maximum": 20},
        "include_closed": {"type": "boolean", "default": False},
    },
    "additionalProperties": False,
})

args = schema({"repo": "hussain-alsaibai/tiny-validator", "limit": 5})

The bridge intentionally supports a practical subset and raises ValueError for unsupported keywords, so tool-call validation fails loudly instead of silently accepting a partial contract.

See also:

πŸ“Š Comparison

Feature tiny-validator Pydantic v2 marshmallow
Dependencies 0 0 (core) 0 (core)
File count 1 multiple multiple
Type-driven ❌ (declarative) βœ… ❌
Strict mode βœ… βœ… βœ…
Nested schemas βœ… βœ… βœ…
Decorator βœ… (depends) ❌
Email / URL / UUID βœ… βœ… (str + format) needs extra
Startup time <10 ms ~200 ms ~50 ms

Use tiny-validator when you want fast, transparent, no-surprise validation, and you don't need Pydantic's serialization/deserialization, model export, or computed fields.

πŸ§ͺ Testing

python test_tiny_validator.py -v

MCP sidecar (v0.5.0)

Validation for the Model Context Protocol server-manifest format lives in the opt-in sidecar module tiny_validator_mcp.py. The core package stays at zero dependencies; importing the sidecar only pulls in tiny_validator (already a dependency).

from tiny_validator import Schema, String
from tiny_validator_mcp import McpServer, McpTool, validate_mcp_manifest

server = McpServer(
    name="my-mcp",
    version="0.1.0",
    title="My MCP Server",
    tools=[McpTool(name="echo", description="Echo the input back to the caller.",
                    input_schema=Schema({"text": String(min_length=1)}))],
    metrics=["mcp_my_mcp_calls_total", "mcp_my_mcp_latency_seconds_bucket"],
)
errors = validate_mcp_manifest(server.to_dict())
assert not errors

The sidecar validates three layers: (1) structural via SERVER_SCHEMA, (2) per-element via TOOL_SCHEMA/RESOURCE_SCHEMA/PROMPT_SCHEMA, (3) metric labels via metric_label_check, enforcing the de-facto mcp_<server>_<unit>_(total|bucket|sum|count|created|info) pattern.

See tiny_validator_mcp.py for the full API and test_v050_mcp.py for 39 tests covering it.

Ecosystem

Part of the tiny-* zero-dependency toolkit for Python agent infrastructure:

  • tiny-router β€” HTTP router, 76K req/s
  • tiny-log β€” structured logging
  • tiny-validator β€” input validation, 247K val/s (v0.2.0: JSON Schema import, batch/partial)
  • tiny-config β€” layered config loader (v0.2.0: validation, secrets, profiles)
  • tiny-cli β€” CLI builder with colors
  • fast-cache β€” LRU + TTL + SWR cache
  • tiny-rate β€” rate limiter (token / fixed / sliding)
  • tiny-retry β€” retry + backoff + circuit breaker
  • tiny-pool β€” ThreadPool + AsyncPool
  • tiny-agent β€” zero-dep agent framework
  • tiny-mcp
  • tiny-mcp-skel - stdlib-only MCP server skeleton (combines tiny-router blueprints + tiny-validator McpServer schema, ships today). β€” Model Context Protocol
  • tiny-embed β€” embeddings + vector search
  • tiny-compose β€” Stack any decorators in any order, declaratively
  • tiny-trace β€” OTel-compatible tracing, sync + async, W3C propagation
  • tiny-secret β€” Zero-dep secret loader + redacting printer
  • tiny-cron β€” cron-style scheduler + intervals
  • tiny-flags β€” feature flags, percentage rollout
  • tiny-queue β€” persistent FIFO queue, retries
  • tiny-metrics β€” Prometheus-compatible metrics
  • tiny-timeout β€” hard timeouts + cooperative deadlines
  • tiny-idempotency β€” Stripe-style idempotency keys
  • tiny-budget β€” runtime cost + token enforcement for AI agents
  • tiny-eventbus β€” durable pub/sub with JSONL replay
  • snapdb β€” embedded DB

21 repos, ~14,700 LOC, zero dependencies across the entire stack. All single-file, MIT, fully type-hinted. Built by OpenClaw.

License

MIT β€” see LICENSE.

About

Zero-dependency input validation. Like Pydantic in one file. 247K val/s. Part of the tiny-* ecosystem.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages