Cheatsheet for extending Pydantic beyond BaseModel.

Custom type via Annotated (easiest)

from typing import Annotated
from pydantic import BeforeValidator, AfterValidator

def coerce(v):
    if isinstance(v, str): return int(v.strip())
    return v

def positive(v: int):
    if v <= 0: raise ValueError("must be positive")
    return v

PositiveInt = Annotated[int, BeforeValidator(coerce), AfterValidator(positive)]

class M(BaseModel):
    count: PositiveInt

Composable.

get_pydantic_core_schema

For deep integration:

from pydantic import GetCoreSchemaHandler
from pydantic_core import CoreSchema, core_schema

class MyType:
    def __init__(self, value: str):
        self.value = value
    
    @classmethod
    def __get_pydantic_core_schema__(cls, source_type, handler: GetCoreSchemaHandler) -> CoreSchema:
        return core_schema.no_info_after_validator_function(
            cls,
            core_schema.str_schema(),
        )

Total control over validation + serialization. Most apps don’t need this.

TypeAdapter (non-BaseModel)

from pydantic import TypeAdapter

UserListAdapter = TypeAdapter(list[User])
DictAdapter = TypeAdapter(dict[str, int])

users = UserListAdapter.validate_python(raw_list)
d = DictAdapter.validate_json('{"a": 1}')
DictAdapter.dump_json(d)

Use module-scope (don’t recreate per call).

RootModel

from pydantic import RootModel

class Names(RootModel[list[str]]):
    pass

n = Names(["Alice", "Bob"])
n.root           # ["Alice", "Bob"]

For JSON that’s a list/primitive at the top level.

pydantic.dataclasses

from pydantic.dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str

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

Stdlib-dataclass syntax + Pydantic validation.

Compose Annotated metadata

from typing import Annotated
from pydantic import Field, BeforeValidator, AfterValidator, StringConstraints

Username = Annotated[
    str,
    StringConstraints(min_length=3, max_length=32, pattern=r"^[a-z0-9_]+$"),
    BeforeValidator(lambda v: v.lower().strip() if isinstance(v, str) else v),
    AfterValidator(lambda v: v),
    Field(description="Username"),
]

Multiple metadata items combined.

get_pydantic_json_schema

For custom JSON schema:

from pydantic import GetJsonSchemaHandler
from pydantic.json_schema import JsonSchemaValue

class MyType:
    @classmethod
    def __get_pydantic_json_schema__(cls, schema, handler):
        result = handler(schema)
        result["title"] = "MyType"
        result["description"] = "Custom"
        return result

Conditional fields via model_validator

from pydantic import model_validator

class M(BaseModel):
    type: str
    a: str | None = None
    b: int | None = None
    
    @model_validator(mode="after")
    def required_by_type(self):
        if self.type == "alpha" and self.a is None:
            raise ValueError("a required when type=alpha")
        return self

Or use a discriminated union (cleaner).

Validate per-call context

from pydantic import ValidationInfo

class User(BaseModel):
    email: str
    
    @field_validator("email")
    @classmethod
    def check(cls, v, info: ValidationInfo):
        ctx = info.context or {}
        if ctx.get("strict") and "@example.com" in v:
            raise ValueError("disallowed domain")
        return v

User.model_validate({"email": "..."}, context={"strict": True})

from_attributes for ORM

class UserOut(BaseModel):
    id: int
    email: str
    model_config = {"from_attributes": True}

UserOut.model_validate(sa_user)    # SQLAlchemy → Pydantic

Pickle support

import pickle
data = pickle.dumps(user)
restored = pickle.loads(data)

BaseModels are picklable.

Common mistakes

  • Subclassing int / str for custom types — loses Pydantic features.
  • TypeAdapter created per call — schema compilation per call.
  • RootModel for multi-field — overkill; use BaseModel.
  • Mixing pydantic.dataclass and BaseModel inheritance — quirky.

Read this next

If you want my custom-type library + TypeAdapter helpers, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .