Claude Code for schematics: Data Modeling and Validation in Python — Claude Skills 360 Blog
Blog / AI / Claude Code for schematics: Data Modeling and Validation in Python
AI

Claude Code for schematics: Data Modeling and Validation in Python

Published: April 13, 2028
Read time: 5 min read
By: Claude Skills 360

schematics provides class-based data models with validation and serialization. pip install schematics. Model: from schematics.models import Model; from schematics.types import StringType, IntType. Define: class User(Model): name = StringType(required=True); age = IntType(min_value=0). Create: u = User({"name": "Alice", "age": 30}). Validate: u.validate() — raises DataError. from schematics.exceptions import DataError. u.errors — dict of errors. to_primitive: u.to_primitive() → dict. to_native: User(data).to_native(). Serialize: u.serialize() — same as to_primitive. Roles: class User(Model): class Options: roles = {"public": blacklist("password")}; u.to_primitive(role="public"). Types: StringType, IntType, FloatType, BooleanType, LongType, DateTimeType, DateType, UUIDType, EmailType, URLType, MD5Type, IPv4Type. ListType(StringType()). DictType(IntType()). ModelType(Address) — nested model. PolyModelType({"user":User,"org":Org}). Default: StringType(default="N/A"). Choices: StringType(choices=["a","b"]). Regex: from schematics.types import StringType; StringType(regex=r"\d+"). Custom: from schematics.types.base import BaseType; class PositiveIntType(BaseType): def validate_positive(self, value, data): if value <= 0: raise ConversionError("Must be positive"). Serializer keys: serialized_name="camelCase". Import: ModelType, ListType, PolyModelType. Claude Code generates schematics data models, API request/response models, and validation pipelines.

CLAUDE.md for schematics

## schematics Stack
- Version: schematics >= 2.1 | pip install schematics
- Model: class MyModel(Model): field = TypeClass(required=True, ...)
- Validate: model.validate() → raises DataError | model.errors → dict
- Serialize: model.to_primitive() → dict | model.serialize(role="public")
- Roles: class Options: roles = {"public": blacklist("secret_field")}
- Nested: ModelType(OtherModel) | ListType(ModelType(Item)) | PolyModelType(map)
- Custom: subclass BaseType, add validate_<rule>(self, value, data) methods

schematics Data Modeling Pipeline

# app/models.py — schematics Model, types, validation, roles, and nested models
from __future__ import annotations

import uuid
from typing import Any

from schematics.exceptions import ConversionError, DataError, ValidationError
from schematics.models import Model
from schematics.transforms import blacklist, whitelist
from schematics.types import (
    BooleanType,
    DateTimeType,
    EmailType,
    FloatType,
    IntType,
    ListType,
    StringType,
    UUIDType,
    URLType,
)
from schematics.types.compound import DictType, ModelType
from schematics.types.base import BaseType


# ─────────────────────────────────────────────────────────────────────────────
# 1. Custom field types
# ─────────────────────────────────────────────────────────────────────────────

class SlugType(StringType):
    """
    StringType that validates slug format: lowercase alphanumeric + hyphens.
    Usage: slug = SlugType(required=True)
    """
    MESSAGES = {"invalid_slug": "Must be a valid slug (lowercase letters, digits, hyphens)"}

    def validate_slug(self, value, data):
        import re
        if not re.match(r"^[a-z0-9]+(?:-[a-z0-9]+)*$", value):
            raise ValidationError(self.MESSAGES["invalid_slug"])


class PositiveIntType(IntType):
    """IntType that enforces value > 0."""
    MESSAGES = {"positive": "Must be a positive integer (> 0)"}

    def validate_positive(self, value, data):
        if value <= 0:
            raise ValidationError(self.MESSAGES["positive"])


class NonNegativeFloatType(FloatType):
    """FloatType that enforces value >= 0."""
    def validate_non_negative(self, value, data):
        if value < 0:
            raise ValidationError("Must be a non-negative number")


# ─────────────────────────────────────────────────────────────────────────────
# 2. Base models
# ─────────────────────────────────────────────────────────────────────────────

class TimestampedMixin(Model):
    """
    Mixin that adds created_at and updated_at fields.
    Not a standalone model — inherit from this alongside your model base.
    """
    created_at = DateTimeType()
    updated_at = DateTimeType()


class IdentifiedMixin(Model):
    """Mixin that adds an id field (UUID string)."""
    id = UUIDType(default=uuid.uuid4)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Domain models
# ─────────────────────────────────────────────────────────────────────────────

class Address(Model):
    """Embeddable address model."""
    street  = StringType(required=True, min_length=1, max_length=200)
    city    = StringType(required=True, min_length=1)
    state   = StringType(max_length=50)
    country = StringType(required=True, min_length=2, max_length=2)
    postal_code = StringType(max_length=20)


class UserProfile(Model):
    """Nested profile embedded in User."""
    bio      = StringType(max_length=500)
    website  = URLType()
    location = StringType()


class User(Model):
    """
    User model with roles for public and admin serialization.
    """
    id       = UUIDType(default=uuid.uuid4)
    name     = StringType(required=True, min_length=1, max_length=100)
    email    = EmailType(required=True)
    role     = StringType(choices=["admin", "moderator", "user"], default="user")
    active   = BooleanType(default=True)
    password = StringType()   # excluded from "public" role
    profile  = ModelType(UserProfile)
    address  = ModelType(Address)
    tags     = ListType(StringType(), default=list)

    class Options:
        roles = {
            "public":  blacklist("password"),
            "admin":   whitelist("id", "name", "email", "role", "active"),
            "minimal": whitelist("id", "name"),
        }

    def validate_name(self, data, value):
        """Cross-field validator: name must be non-whitespace."""
        if not value.strip():
            raise ValidationError("Name must not be blank")
        return value.strip()


class Product(Model):
    """Product model with pricing and inventory."""
    id          = PositiveIntType(required=True)
    name        = StringType(required=True, min_length=1, max_length=200)
    slug        = SlugType(required=True)
    price       = NonNegativeFloatType(required=True)
    stock       = IntType(default=0, min_value=0)
    active      = BooleanType(default=True)
    tags        = ListType(StringType(), default=list)
    metadata    = DictType(StringType())

    class Options:
        roles = {
            "public":  blacklist("metadata"),
            "summary": whitelist("id", "name", "price", "slug"),
        }


class OrderItem(Model):
    """Line item in an order."""
    product_id = PositiveIntType(required=True)
    quantity   = PositiveIntType(required=True)
    unit_price = NonNegativeFloatType(required=True)

    @property
    def line_total(self) -> float:
        if self.quantity and self.unit_price:
            return round(self.quantity * self.unit_price, 2)
        return 0.0


class Order(Model):
    """Order with nested items and customer reference."""
    order_id   = StringType(required=True, min_length=1)
    customer   = ModelType(User, required=True)
    items      = ListType(ModelType(OrderItem), required=True, min_size=1)
    shipping   = ModelType(Address)
    notes      = StringType(default="")
    status     = StringType(choices=["pending","confirmed","shipped","delivered","cancelled"],
                            default="pending")

    def validate_items(self, data, value):
        if not value:
            raise ValidationError("Order must have at least one item")
        return value


# ─────────────────────────────────────────────────────────────────────────────
# 4. Validation helpers
# ─────────────────────────────────────────────────────────────────────────────

def validate_model(model: Model) -> tuple[bool, dict[str, Any]]:
    """
    Validate a schematics model instance.
    Returns (True, {}) on success or (False, {field: [errors]}) on failure.
    """
    try:
        model.validate()
        return True, {}
    except DataError as e:
        return False, _flatten_errors(e.errors)


def _flatten_errors(errors: Any, prefix: str = "") -> dict[str, list[str]]:
    """Recursively flatten DataError error dict to {field_path: [messages]}."""
    flat: dict[str, list[str]] = {}
    if isinstance(errors, dict):
        for key, val in errors.items():
            full_key = f"{prefix}.{key}" if prefix else str(key)
            flat.update(_flatten_errors(val, full_key))
    elif isinstance(errors, list):
        messages = []
        for item in errors:
            if isinstance(item, Exception):
                messages.append(str(item))
            elif isinstance(item, dict):
                flat.update(_flatten_errors(item, prefix))
            else:
                messages.append(str(item))
        if messages:
            flat[prefix] = messages
    elif isinstance(errors, Exception):
        flat[prefix] = [str(errors)]
    return flat


def from_dict(model_cls: type, data: dict, validate: bool = True) -> tuple[Any, dict]:
    """
    Instantiate a schematics model from a dict.
    Returns (model_instance, {}) or (None, errors_dict).
    """
    try:
        m = model_cls(data)
        if validate:
            m.validate()
        return m, {}
    except DataError as e:
        return None, _flatten_errors(e.errors)


def to_dict(model: Model, role: str | None = None) -> dict:
    """Serialize a model to a plain dict using optional role."""
    return model.to_primitive(role=role)


def bulk_validate(model_cls: type, items: list[dict]) -> tuple[list, dict[int, dict]]:
    """
    Validate a list of dicts as model instances.
    Returns (valid_models, {index: errors}).
    """
    valid: list = []
    errors: dict[int, dict] = {}
    for i, item in enumerate(items):
        m, errs = from_dict(model_cls, item)
        if errs:
            errors[i] = errs
        else:
            valid.append(m)
    return valid, errors


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== User — valid ===")
    user, errs = from_dict(User, {
        "name": "Alice",
        "email": "[email protected]",
        "role": "admin",
        "password": "secret",
        "tags": ["python", "backend"],
    })
    print("valid:", errs == {})
    print("public view:", to_dict(user, role="public"))
    print("admin view: ", to_dict(user, role="admin"))
    print("minimal:    ", to_dict(user, role="minimal"))

    print("\n=== User — invalid ===")
    _, errs = from_dict(User, {"name": "", "email": "not-an-email", "role": "superuser"})
    for field, msgs in errs.items():
        print(f"  {field}: {msgs}")

    print("\n=== Product ===")
    prod, errs = from_dict(Product, {
        "id": 1,
        "name": "Python Dev Kit",
        "slug": "python-dev-kit",
        "price": 49.99,
        "tags": ["python", "tools"],
    })
    print("product summary:", to_dict(prod, role="summary"))

    print("\n=== Order with nested models ===")
    order_data = {
        "order_id": "ORD-001",
        "customer": {"name": "Bob", "email": "[email protected]"},
        "items": [
            {"product_id": 1, "quantity": 2, "unit_price": 49.99},
        ],
        "shipping": {"street": "123 Main", "city": "Portland", "country": "US"},
    }
    order, errs = from_dict(Order, order_data)
    if not errs:
        print(f"order {order.order_id}: {len(order.items)} items")
        print(f"line total: ${order.items[0].line_total}")
    else:
        print("errors:", errs)

    print("\n=== bulk_validate ===")
    product_rows = [
        {"id": 1, "name": "Widget",  "slug": "widget",  "price": 9.99},
        {"id": -1, "name": "",       "slug": "Bad Slug", "price": -5},
        {"id": 2, "name": "Gadget",  "slug": "gadget",  "price": 14.99},
    ]
    valid_prods, errs = bulk_validate(Product, product_rows)
    print(f"Valid: {len(valid_prods)}, Invalid: {len(errs)}")
    for idx, e in errs.items():
        print(f"  [item {idx}]:", {k: v for k, v in e.items()})

For the attrs/dataclasses alternative — attrs and dataclasses provide structured types with type hints, but validation is not built-in; schematics provides field-level type validation, serialization with roles, coerce-on-assign, and nested model validation out of the box without writing __post_init__ validators. For the pydantic alternative — Pydantic v2 is faster and tighter with Python type annotations; schematics is older but uses explicit field descriptors (StringType, IntType) that are self-documenting, supports MongoDB-style use cases natively, and has a roles system for view-specific serialization that Pydantic doesn’t have built-in. The Claude Skills 360 bundle includes schematics skill sets covering Model class definition, all built-in types (String/Int/Float/Bool/UUID/Email/URL/DateTime/List/Dict/Model), SlugType/PositiveIntType custom field types, validate_model()/from_dict()/to_dict()/bulk_validate() helpers, roles with blacklist/whitelist, nested ModelType/ListType, cross-field model validators, IdentifiedMixin/TimestampedMixin base classes, and User/Product/Order/Address domain models. Start with the free tier to try schematics data model code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free