Unit 05.00: A model is a contract, not a convenience
A Pydantic model is your published contract, generated from one declaration.
One declaration, three uses
A request model and the JSON schema it produces.
The code prints the schema.
from pydantic import BaseModel, Field
class ClassifyRequest(BaseModel):
text: str = Field(min_length=1, max_length=4000)
hint: str | None = Field(default=None, max_length=200)
schema = ClassifyRequest.model_json_schema()
print("the contract, generated from the model:")
for name, spec in schema["properties"].items():
required = name in schema.get("required", [])
print(f" {name:6} required={required!s:5} {spec}")
print("""
This schema is what the caller reads, what the validation enforces, and what
the documentation shows -- from one declaration.
The consequence: a change here is a change to the published contract. Adding a
required field breaks every existing caller, which is the subject of the last
unit in the next module.
""")
The same declaration is the validation, the documentation and the type the editor understands. There is no second artefact to keep in step.
The consequence is that a change here is a change to a published contract. Adding a required field breaks every existing caller - which is why the compatibility rules in the next module matter as much as the syntax here.
The mistake this prevents
The mistake is treating models as internal convenience objects. The request and response models are public; internal shapes should be separate types, so refactoring the internals cannot change what callers receive.
Takeaway
A request or response model is a published contract. Keep internal shapes in separate types so refactoring cannot alter what callers see.
