Examples

Minimal create/load/update

Build common views from a single model and use them as function signatures.

from pydantic import BaseModel
from pydantic_views import BuilderCreate, BuilderCreateResult, BuilderLoad, BuilderUpdate

class User(BaseModel):
        id: int
        email: str
        password: str

UserCreate = BuilderCreate().build_view(User)
UserCreateResult = BuilderCreateResult().build_view(User)
UserLoad = BuilderLoad().build_view(User)
UserUpdate = BuilderUpdate().build_view(User)

def create_user(input: UserCreate) -> UserCreateResult: ...
def get_user(user_id: int) -> UserLoad: ...
def update_user(user_id: int, input: UserUpdate) -> UserLoad: ...

Nested models and selective fields

Views cascade into nested models and respect access modes you annotate.

from typing import Annotated

from pydantic import BaseModel, computed_field
from pydantic_views import AccessMode, ReadOnly, ReadOnlyOnCreation, BuilderLoad

class Address(BaseModel):
        street: str
        city: str
        zip_code: str

class Profile(BaseModel):
        username: str
        email: ReadOnly[str]
        # Hide on create/update, show after creation
        api_token: ReadOnlyOnCreation[str]
        # Expose only when reading (load views)
        score: Annotated[int, AccessMode.READ_ONLY]

        @computed_field
        def location(self) -> str:
                return f"{self.username} @ {self.email}"

        address: Address

ProfileLoad = BuilderLoad().build_view(Profile)

profile = Profile(
        username="alice",
        email="alice@example.com",
        api_token="secret",
        score=42,
        address=Address(street="Main", city="Springfield", zip_code="00000"),
)

loaded = ProfileLoad.view_build_from(profile)
assert loaded.address.city == "Springfield"
# api_token and score are present, write-only fields would have been stripped

Applying partial updates

Update views accept only the fields you want to change and merge them into an instance.

from pydantic import BaseModel
from pydantic_views import BuilderUpdate

class Settings(BaseModel):
        theme: str
        locale: str
        marketing_opt_in: bool

SettingsUpdate = BuilderUpdate().build_view(Settings)

current = Settings(theme="light", locale="en", marketing_opt_in=False)
patch = SettingsUpdate(theme="dark")

updated = patch.view_apply_to(current)

assert updated.theme == "dark"
assert updated.locale == "en"  # unchanged

Create + result pair with computed fields

Use BuilderCreate to accept input and BuilderCreateResult to return read-only and computed fields.

from pydantic import BaseModel, computed_field
from pydantic_views import BuilderCreate, BuilderCreateResult, ReadOnly

class Invoice(BaseModel):
        id: ReadOnly[int]
        description: str
        units: int
        unit_price: float

        @computed_field
        def total(self) -> float:
                return self.units * self.unit_price

InvoiceCreate = BuilderCreate().build_view(Invoice)
InvoiceCreateResult = BuilderCreateResult().build_view(Invoice)

new_invoice = InvoiceCreate(description="Hosting", units=2, unit_price=25.0)
stored = Invoice(id=1, **new_invoice.model_dump())

result = InvoiceCreateResult.view_build_from(stored)
assert result.total == 50.0

Custom builder with nullable fields

Craft a bespoke builder to allow nullable fields and expose only certain access modes.

from pydantic import BaseModel
from pydantic_views import Builder, AccessMode

SoftDeleteBuilder = Builder(
        view_name="SoftDelete",
        access_modes=(AccessMode.READ_AND_WRITE,),
        all_nullable=True,
)

class Document(BaseModel):
        title: str
        body: str
        deleted_at: float | None

DocumentSoftDelete = SoftDeleteBuilder.build_view(Document)

doc = Document(title="Plan", body="...")
soft_delete = DocumentSoftDelete(deleted_at=1720000000.0)

deleted_doc = soft_delete.view_apply_to(doc)
assert deleted_doc.deleted_at is not None