Skip to content

Instantly share code, notes, and snippets.

@wware
Last active February 4, 2026 21:16
Show Gist options
  • Select an option

  • Save wware/ac08632ac5ef33f96f0e21e954f3a8dd to your computer and use it in GitHub Desktop.

Select an option

Save wware/ac08632ac5ef33f96f0e21e954f3a8dd to your computer and use it in GitHub Desktop.

Writing good Python schemas

Good Python schemas specify interfaces and protocols without specifying implementation. They leverage type-hinting and linting tools like pylint and mypy.

Three Primary Goals

  1. Early Design Documentation: Define comprehensive interface specifications before writing implementation code using Python's abc module, strict type annotations, detailed docstrings, and frozen Pydantic models. This creates a clear blueprint with static validation via mypy.

  2. Guardrails for LLM Code Generation: Explicit interfaces, type hints, immutable data models, and runtime validation via Pydantic's validate_call reduce ambiguity and minimize LLM hallucinations or structural deviations.

  3. Simplified Testing: Strict separation between abstract interfaces and concrete implementations naturally promotes dependency injection, making it straightforward to substitute mocks during testing.

Schema Characteristics

  • Interface Classes: Use abc.ABC with abstract methods, full type annotations, and comprehensive docstrings. Prefix interface names with "I" for clarity.
  • No Implementation Logic: Interfaces contain only method signatures and documentation.
  • Frozen Pydantic Models: Data models use BaseModel with frozen=True for immutability.
  • Pydantic Field Annotations: Use Field() for inline documentation, examples, and validation constraints (ranges, patterns, lengths). This serves both as developer documentation and as guardrails.
  • Runtime Validation: All functions use @pydantic.validate_call to enforce type hints at runtime.
  • Default Returns: Schema functions return default values of correct type as placeholders.
  • Static Validation: Run mypy to ensure type consistency.

Basic Example

from abc import ABC, abstractmethod
from pydantic import BaseModel, ConfigDict, Field, validate_call


class User(BaseModel):
    model_config = ConfigDict(frozen=True)
    
    id: int = Field(
        ..., 
        gt=0, 
        description="Unique user identifier",
        examples=[1, 42, 1337]
    )
    name: str = Field(
        ..., 
        min_length=1, 
        max_length=100,
        description="User's display name",
        examples=["Alice", "Bob Smith"]
    )

default_user = User(id=1, name="")


class IUserRepository(ABC):
    """Interface for user repository operations."""

    @abstractmethod
    @validate_call
    def get_user(self, user_id: int) -> User:
        """
        Retrieve a user by their unique ID.

        Args:
            user_id (int): The user's unique identifier.

        Returns:
            User: The user object.
        """
        return default_user  # Placeholder


@validate_call
def calculate_discount(price: float, percentage: float) -> float:
    """Calculate discounted price."""
    return 0.0  # Placeholder

Parallelogram Pattern: Extending Schemas and Implementations

Schemas can extend other schemas, and implementations can extend other implementations, maintaining the abstraction-to-concrete relationship at each level.

Example: In a knowledge graph framework, a base schema defines domain-agnostic operations (document ingestion, entity extraction, graph construction). A medical literature schema extends this base, adding domain-specific concepts like evidence strength, claim validation, and citation networks. Both schemas have corresponding implementations that maintain the same inheritance structure:

  • IKnowledgeGraphSchemaIMedicalLiteratureSchema (schema layer)
  • KnowledgeGraphImplMedicalLiteratureImpl (implementation layer)

This parallelogram structure keeps domain logic separated while preserving type safety and testability at both abstraction levels.

Design Notes

  • Keep ABC and Pydantic Separate: Mixing abstract base classes with Pydantic models adds complexity around schema generation and serialization. Use Pydantic for pure data structures, ABC for functional interfaces.
  • Immutability Caveat: frozen=True prevents attribute reassignment but doesn't deeply freeze mutable fields (lists/dicts inside models remain mutable).
  • When to Use: Best for codebases requiring strict contracts and validation—financial, healthcare, mission-critical applications.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment