Pydantic¶
Pydantic models are typed by the plugin and still validated by pydantic at run time.
Run it¶
Two shapes, kept apart¶
A pydantic model has a constructor shape and a validated output shape, and
the plugin distinguishes them. User(id="123", name="ada") is legal
because pydantic coerces the string. user.id + 1 is int arithmetic,
because the checker types the field from the model, not from the argument.
Validation still runs as pydantic wrote it.
Constraints become facts¶
class Pixel(BaseModel):
r: Annotated[int, Field(ge=0, le=255)]
g: Annotated[int, Field(ge=0, le=255)]
b: Annotated[int, Field(ge=0, le=255)]
@ppy.pure
def luminance(pixel: Pixel) -> float:
return 0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b
Field(ge=0, le=255) is read as an integer range as well as a validator,
the same refinement ppy.Range(0, 255) gives. Inside luminance the three
fields are known to fit a byte, and the arithmetic needs no overflow guard.
These spellings all count:
Annotated[int, Field(...)]count: int = Field(ge=0, le=100)conint
Schema building at build time¶
Schema building is code the model runs at import. It falls under the same
policy as JAX export: [tool.ppy] build-execution decides whether a build
may execute project code, and the default is deny.
What it prints¶
python pydantic_models.ppy, ppy pydantic_models.ppy, ppy run pydantic_models.ppy
Read on: Plugins: Pydantic ยท Uvicorn and FastAPI
pydantic_models.ppy is hand-written; there is no .py source and no
conversion step.
06_pydantic/pydantic_models.ppy¶
from typing import Annotated
import ppy
from pydantic import BaseModel, Field
class Pixel(BaseModel):
r: Annotated[int, Field(ge=0, le=255)]
g: Annotated[int, Field(ge=0, le=255)]
b: Annotated[int, Field(ge=0, le=255)]
class User(BaseModel):
id: int
name: str
@ppy.pure
def luminance(pixel: Pixel) -> float:
return 0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b
def main() -> None:
pixel = Pixel(r=255, g=128, b=0)
user = User(id="123", name="ada")
print(round(luminance(pixel), 4), user.id + 1, user.name)
if __name__ == "__main__":
main()
Source: examples/06_pydantic.