Skip to main content

A TypeScript Zod-inspired validation library for Python with excellent type safety and developer experience

Project description

Zodic

PyPI version Python versions License: MIT CI codecov Code style: black Imports: isort security: bandit Downloads Downloads

A TypeScript Zod-inspired validation library for Python with excellent type safety and developer experience.

Features

  • Type-safe validation with excellent IDE support and autocompletion
  • Intuitive chainable API for building complex schemas
  • Lightning-fast performance - 2M+ operations/second
  • Comprehensive error reporting with detailed nested paths
  • Zero dependencies - lightweight and fast imports
  • Rich validation types - strings, numbers, dates, enums, literals, and more
  • Advanced string validation - email, URL, regex patterns
  • Date/time parsing - ISO formats and common date strings
  • Union types - flexible validation with | operator
  • Extensible architecture for custom validators
  • Framework agnostic - works with FastAPI, Django, Flask, etc.

Installation

pip install zodic

Requires Python 3.9+

Quick Start

import zodic as z

# Basic validation
name_schema = z.string().min(1).max(100)
name = name_schema.parse("Alice")  # Returns "Alice"

# Object validation  
user_schema = z.object({
    "name": z.string().min(1),
    "age": z.number().int().min(0).max(120),
    "email": z.string().email().optional(),
    "is_active": z.boolean().default(True),
    "role": z.enum(["admin", "user", "guest"])
})

# Parse and validate data
user_data = {
    "name": "Alice Johnson", 
    "age": 30,
    "email": "alice@example.com"
}

user = user_schema.parse(user_data)
# Returns: {"name": "Alice Johnson", "age": 30, "email": "alice@example.com", "is_active": True}

Documentation

Basic Types

import zodic as z

# Primitives
z.string()     # str
z.number()     # int | float
z.boolean()    # bool  
z.none()       # None

# String validation
z.string().min(5)              # Minimum length
z.string().max(100)            # Maximum length  
z.string().length(10)          # Exact length

# Number validation
z.number().int()               # Must be integer
z.number().positive()          # > 0
z.number().min(0).max(100)     # Range validation

# Collections
z.array(z.string())            # List[str]
z.object({"name": z.string()}) # Dict with typed fields

Advanced Features

# Optional and nullable
z.string().optional()          # str | None (can be missing)
z.string().nullable()          # str | None (can be null)
z.string().default("hello")    # Default value if missing

# Transformations
z.string().transform(str.upper)           # Transform after validation
z.number().transform(lambda x: x * 2)     # Custom transformations

# Custom validation
z.string().refine(
    lambda x: x.startswith("prefix_"),
    "Must start with 'prefix_'"
)

# Union types  
z.union([z.string(), z.number()])         # str | int | float
z.string() | z.number()                   # Same as above (v0.2.0+)

New in v0.2.0

# Literal and enum validation
z.literal("admin")                        # Exact value match
z.enum(["red", "green", "blue"])         # Multiple choice

# Enhanced string validation
z.string().email()                        # Email format validation
z.string().url()                          # URL format validation
z.string().regex(r"^[A-Z]{2,3}$")        # Custom regex patterns

# Date and datetime validation
z.date()                                  # Parse dates from strings or objects
z.datetime()                              # Parse datetimes with timezone support
z.date().min(date(2024, 1, 1))           # Date range validation

# Examples
email_schema = z.string().email()
email_schema.parse("user@example.com")   # Valid

theme_schema = z.enum(["light", "dark"])
theme_schema.parse("light")               # Valid

date_schema = z.date()
date_schema.parse("2024-12-19")          # Returns date(2024, 12, 19)
date_schema.parse(datetime.now())        # Converts to date

Error Handling

# Parse (throws ZodError on failure)
try:
    result = schema.parse(data)
except z.ZodError as e:
    print(f"Validation failed: {e}")
    print(f"Issues: {e.issues}")

# Safe parse (returns result object)
result = schema.safe_parse(data)
if result["success"]:
    print(f"Valid data: {result['data']}")
else:
    print(f"Validation errors: {result['error']}")

# Error formatting
try:
    schema.parse(invalid_data)
except z.ZodError as e:
    # Get flattened errors
    errors = e.flatten()
    # {"field.path": ["error message"]}
    
    # Get formatted errors  
    formatted = e.format()
    # [{"code": "invalid_type", "message": "...", "path": [...]}]

Real-World Example

import zodic as z
from datetime import date

# API request validation
create_user_schema = z.object({
    "personal_info": z.object({
        "first_name": z.string().min(1).max(50),
        "last_name": z.string().min(1).max(50),
        "email": z.string().email(),
        "birth_date": z.date().max(date.today()),
        "phone": z.string().regex(r"^\+?1?\d{9,15}$").optional()
    }),
    "account": z.object({
        "username": z.string().min(3).max(30).regex(r"^[a-zA-Z0-9_]+$"),
        "password": z.string().min(8).regex(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)"),
        "role": z.enum(["admin", "moderator", "user"]).default("user"),
        "permissions": z.array(z.string()).default([])
    }),
    "preferences": z.object({
        "theme": z.enum(["light", "dark", "auto"]).default("auto"),
        "language": z.string().regex(r"^[a-z]{2}$").default("en"),
        "notifications": z.boolean().default(True)
    }).optional(),
    "metadata": z.object({
        "source": z.literal("api"),
        "version": z.string().regex(r"^\d+\.\d+\.\d+$"),
        "created_at": z.datetime().default(lambda: datetime.now())
    })
})

# Usage
try:
    user = create_user_schema.parse(request_data)
    # user is fully typed and validated
    print(f"Creating user: {user['personal_info']['email']}")
except z.ZodError as e:
    return {"error": "Validation failed", "details": e.flatten()}

Framework Integration

FastAPI

from fastapi import FastAPI, HTTPException
import zodic as z

app = FastAPI()

UserSchema = z.object({
    "name": z.string().min(1),
    "email": z.string().email(),
    "age": z.number().int().min(18)
})

@app.post("/users")
async def create_user(data: dict):
    try:
        user = UserSchema.parse(data)
        # Process validated user data
        return {"user": user}
    except z.ZodError as e:
        raise HTTPException(status_code=422, detail=e.flatten())

Django

from django.http import JsonResponse
import zodic as z

ContactSchema = z.object({
    "name": z.string().min(1).max(100),
    "email": z.string().email(),
    "message": z.string().min(10).max(1000)
})

def contact_view(request):
    if request.method == "POST":
        try:
            data = ContactSchema.parse(request.POST.dict())
            # Process validated data
            return JsonResponse({"status": "success"})
        except z.ZodError as e:
            return JsonResponse({"errors": e.flatten()}, status=400)

Performance

Zodic is designed for high performance:

  • 2M+ validations/second for simple schemas
  • 500K+ validations/second for complex nested objects
  • Zero dependencies - fast imports and minimal overhead
  • Optimized error handling - detailed errors without performance cost
  • Memory efficient - minimal allocations during validation

Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

# Clone the repository
git clone https://github.com/Seyamalam/zodic.git
cd zodic

# Install dependencies
poetry install

# Run tests
poetry run pytest

# Run quality checks
poetry run black zodic tests
poetry run isort zodic tests  
poetry run mypy zodic
poetry run flake8 zodic tests

License

MIT License - see LICENSE file for details.

Changelog

See CHANGELOG.md for version history and migration guides.

Support

Star us on GitHub if Zodic helps you build better Python applications!

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

zodic-0.2.0.tar.gz (17.4 kB view details)

Uploaded Source

Built Distribution

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

zodic-0.2.0-py3-none-any.whl (17.9 kB view details)

Uploaded Python 3

File details

Details for the file zodic-0.2.0.tar.gz.

File metadata

  • Download URL: zodic-0.2.0.tar.gz
  • Upload date:
  • Size: 17.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.12.4 Windows/11

File hashes

Hashes for zodic-0.2.0.tar.gz
Algorithm Hash digest
SHA256 1b1d6c5cda6579e4bfe9c7c00c9c18558052aeed4e092777be0740f5122387ef
MD5 8edec47b619f4c46de219bcc16c621d3
BLAKE2b-256 f75e35257ae014fbb643b38d516b394a1a730e2bcde816e8d048f15d9e8e3d40

See more details on using hashes here.

File details

Details for the file zodic-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: zodic-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 17.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.12.4 Windows/11

File hashes

Hashes for zodic-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ef4ffa29a7a5dc15727635e9701fdf28a85e1310e310edad25d18ba075b45c71
MD5 f1af86c96b1007d11a47949dfa07651e
BLAKE2b-256 98fc7a90735202a05f386befa6461636d7381be35ede627c59c3d6571150c6cb

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