Usage Examples

Advanced usage patterns and examples for Apathetic Python Schema.

Basic Schema Validation

Simple Configuration Validation

from apathetic_schema import apathetic_schema, ApatheticSchema_ValidationSummary
from apathetic_utils import schema_from_typeddict, load_jsonc
from typing import TypedDict
from pathlib import Path

class AppConfig(TypedDict):
    name: str
    version: str
    port: int
    debug: bool

# Load configuration from JSONC
config = load_jsonc(Path("config.jsonc"))

# Validate against schema
summary = ApatheticSchema_ValidationSummary(valid=True, errors=[], strict_warnings=[], warnings=[], strict=False)
schema = schema_from_typeddict(AppConfig)

is_valid = apathetic_schema.check_schema_conformance(
    config,
    schema,
    "in config.jsonc",
    strict_config=False,
    summary=summary,
)

if summary.valid:
    print("Configuration is valid!")
else:
    for error in summary.errors:
        print(f"Error: {error}")
    for warning in summary.warnings:
        print(f"Warning: {warning}")

Nested TypedDicts

Complex Nested Configuration

from apathetic_schema import apathetic_schema, ApatheticSchema_ValidationSummary
from apathetic_utils import schema_from_typeddict
from typing import TypedDict

class DatabaseConfig(TypedDict):
    host: str
    port: int
    database: str
    user: str
    password: str

class LoggingConfig(TypedDict):
    level: str
    file: str

class AppConfig(TypedDict):
    name: str
    database: DatabaseConfig
    logging: LoggingConfig

config = {
    "name": "MyApp",
    "database": {
        "host": "localhost",
        "port": 5432,
        "database": "mydb",
        "user": "admin",
        "password": "secret"
    },
    "logging": {
        "level": "INFO",
        "file": "/var/log/app.log"
    }
}

summary = ApatheticSchema_ValidationSummary(valid=True, errors=[], strict_warnings=[], warnings=[], strict=False)
schema = schema_from_typeddict(AppConfig)

apathetic_schema.check_schema_conformance(
    config,
    schema,
    "in configuration",
    strict_config=False,
    summary=summary,
)

Lists and Collections

Lists of Primitives

from apathetic_schema import apathetic_schema, ApatheticSchema_ValidationSummary
from apathetic_utils import schema_from_typeddict
from typing import TypedDict

class ServerConfig(TypedDict):
    name: str
    ports: list[int]
    tags: list[str]

config = {
    "name": "web-server",
    "ports": [8080, 8081, 8082],
    "tags": ["web", "production", "load-balanced"]
}

summary = ApatheticSchema_ValidationSummary(valid=True, errors=[], strict_warnings=[], warnings=[], strict=False)
schema = schema_from_typeddict(ServerConfig)

apathetic_schema.check_schema_conformance(
    config,
    schema,
    "in configuration",
    strict_config=False,
    summary=summary,
)

Lists of TypedDicts

from apathetic_schema import apathetic_schema, ApatheticSchema_ValidationSummary
from apathetic_utils import schema_from_typeddict
from typing import TypedDict

class EndpointConfig(TypedDict):
    path: str
    method: str
    handler: str

class APIConfig(TypedDict):
    base_url: str
    endpoints: list[EndpointConfig]

config = {
    "base_url": "https://api.example.com",
    "endpoints": [
        {"path": "/users", "method": "GET", "handler": "get_users"},
        {"path": "/users", "method": "POST", "handler": "create_user"},
        {"path": "/users/{id}", "method": "GET", "handler": "get_user"}
    ]
}

summary = ApatheticSchema_ValidationSummary(valid=True, errors=[], strict_warnings=[], warnings=[], strict=False)
schema = schema_from_typeddict(APIConfig)

apathetic_schema.check_schema_conformance(
    config,
    schema,
    "in configuration",
    strict_config=False,
    summary=summary,
)

Strict Mode

Enforcing Strict Validation

from apathetic_schema import apathetic_schema, ApatheticSchema_ValidationSummary
from apathetic_utils import schema_from_typeddict
from typing import TypedDict

class Config(TypedDict):
    name: str
    port: int

# Config with unknown keys
config = {
    "name": "MyApp",
    "port": 8080,
    "unknown_key": "value",  # This will be an error in strict mode
    "another_unknown": 123
}

summary = ApatheticSchema_ValidationSummary(valid=True, errors=[], strict_warnings=[], warnings=[], strict=False)
schema = schema_from_typeddict(Config)

# Strict mode treats warnings as errors
apathetic_schema.check_schema_conformance(
    config,
    schema,
    "in configuration",
    strict_config=True,  # Enable strict mode
    summary=summary,
)

if not summary.valid:
    print("Validation failed!")
    print("Errors:", summary.errors)
    print("Strict warnings:", summary.strict_warnings)

Ignoring Keys

Partial Validation

from apathetic_schema import apathetic_schema, ApatheticSchema_ValidationSummary
from apathetic_utils import schema_from_typeddict
from typing import TypedDict

class Config(TypedDict):
    name: str
    port: int
    # We'll ignore 'metadata' and 'internal' keys

config = {
    "name": "MyApp",
    "port": 8080,
    "metadata": {"created": "2024-01-01", "author": "admin"},
    "internal": {"debug": True, "trace": False}
}

summary = ApatheticSchema_ValidationSummary(valid=True, errors=[], strict_warnings=[], warnings=[], strict=False)
schema = schema_from_typeddict(Config)

# Ignore specific keys during validation
apathetic_schema.check_schema_conformance(
    config,
    schema,
    "in configuration",
    strict_config=False,
    summary=summary,
    ignore_keys={"metadata", "internal"},
)

Error Aggregation

Using SchemaErrorAggregator

from apathetic_schema import (
    apathetic_schema,
    ApatheticSchema_ValidationSummary,
    ApatheticSchema_SchemaErrorAggregator,
)
from apathetic_utils import schema_from_typeddict
from typing import TypedDict

class Config(TypedDict):
    name: str
    port: int

config = {
    "name": "MyApp",
    "port": "invalid",  # Wrong type
    "dry_run": True,    # Deprecated key
}

summary = ApatheticSchema_ValidationSummary(valid=True, errors=[], strict_warnings=[], warnings=[], strict=False)
agg: ApatheticSchema_SchemaErrorAggregator = {}
schema = schema_from_typeddict(Config)

# Warn about deprecated keys
deprecated_keys = {"dry_run"}
apathetic_schema.warn_keys_once(
    "deprecated",
    deprecated_keys,
    config,
    "in configuration",
    "The '{key}' key is deprecated",
    strict_config=False,
    summary=summary,
    agg=agg,
)

# Validate schema
apathetic_schema.check_schema_conformance(
    config,
    schema,
    "in configuration",
    strict_config=False,
    summary=summary,
)

# Flush aggregated errors
apathetic_schema.flush_schema_aggregators(summary=summary, agg=agg)

if not summary.valid:
    print("Validation Summary:")
    if summary.errors:
        print("Errors:")
        for error in summary.errors:
            print(f"  - {error}")
    if summary.warnings:
        print("Warnings:")
        for warning in summary.warnings:
            print(f"  - {warning}")

Field Examples

Providing Field Examples for Better Errors

from apathetic_schema import apathetic_schema, ApatheticSchema_ValidationSummary
from apathetic_utils import schema_from_typeddict
from typing import TypedDict

class Config(TypedDict):
    name: str
    port: int
    mode: str

config = {
    "name": "MyApp",
    "port": "invalid",  # Should be int
    "mode": "invalid_mode"
}

# Provide examples for better error messages
field_examples = {
    "port": "8080",
    "mode": "development, production, or testing"
}

summary = ApatheticSchema_ValidationSummary(valid=True, errors=[], strict_warnings=[], warnings=[], strict=False)
schema = schema_from_typeddict(Config)

apathetic_schema.check_schema_conformance(
    config,
    schema,
    "in configuration",
    strict_config=False,
    summary=summary,
    field_examples=field_examples,
)

# Error messages will include examples:
# "in configuration: key `port` expected int (e.g. 8080), got str"

Real-World Application

Complete Application Configuration

from apathetic_schema import apathetic_schema, ApatheticSchema_ValidationSummary
from apathetic_utils import schema_from_typeddict, load_jsonc
from typing import TypedDict, NotRequired
from pathlib import Path

class DatabaseConfig(TypedDict):
    host: str
    port: int
    database: str
    user: str
    password: str
    pool_size: NotRequired[int]

class RedisConfig(TypedDict):
    host: str
    port: int
    db: NotRequired[int]

class AppConfig(TypedDict):
    name: str
    version: str
    debug: bool
    database: DatabaseConfig
    redis: NotRequired[RedisConfig]
    workers: int
    timeout: NotRequired[int]

def load_and_validate_config(config_path: Path) -> tuple[dict, bool]:
    """Load and validate configuration file."""
    # Load config
    config = load_jsonc(config_path)
    if config is None:
        raise ValueError(f"Failed to load configuration from {config_path}")

    # Validate
    summary = ApatheticSchema_ValidationSummary(valid=True, errors=[], strict_warnings=[], warnings=[], strict=False)
    schema = schema_from_typeddict(AppConfig)

    is_valid = apathetic_schema.check_schema_conformance(
        config,
        schema,
        f"in {config_path}",
        strict_config=False,
        summary=summary,
    )

    # Report results
    if not summary.valid:
        print("Configuration validation failed:")
        for error in summary.errors:
            print(f"  ERROR: {error}")
        for warning in summary.warnings:
            print(f"  WARNING: {warning}")
        return config, False

    if summary.warnings:
        print("Configuration validated with warnings:")
        for warning in summary.warnings:
            print(f"  WARNING: {warning}")

    return config, True

# Usage
config, is_valid = load_and_validate_config(Path("app.jsonc"))
if is_valid:
    print(f"Loaded configuration for {config['name']} v{config['version']}")

Next Steps