Skip to course content
Free FastAPI backend course

FastAPI for AI Backend Development

Unit 05.02: Constraints beyond types

Types accept nine thousand and "BTC". Policy has to be stated separately.

Ceilings and allowed sets belong in the model

A refund model with a pattern, a numeric ceiling, an allowed currency set and a minimum reason length.

The code validates four payloads.

from pydantic import BaseModel, Field, ValidationError
from typing import Literal


class Refund(BaseModel):
    account: str = Field(pattern=r"^ACC-\d+$")
    amount: float = Field(gt=0, le=500)
    currency: Literal["USD", "EUR"]
    reason: str = Field(min_length=10, max_length=500)


CASES = [
    {"account": "ACC-1", "amount": 120.0, "currency": "USD",
     "reason": "duplicate charge on 14 June"},
    {"account": "ACC-1", "amount": 9000.0, "currency": "USD",
     "reason": "duplicate charge on 14 June"},
    {"account": "ACC-1", "amount": 120.0, "currency": "BTC",
     "reason": "duplicate charge on 14 June"},
    {"account": "ACC-1", "amount": 120.0, "currency": "USD", "reason": "dup"},
]
for payload in CASES:
    try:
        Refund(**payload)
        print(f"OK    {payload['amount']:>8} {payload['currency']}")
    except ValidationError as exc:
        e = exc.errors()[0]
        print(f"FAIL  {str(e['loc'][0]):>8} {e['type']}")

# Types alone accept 9,000 and "BTC". The ceiling and the allowed set are
# policy, and putting them in the model means the policy is in the contract
# and in the documentation, not only in a handler somewhere.

The nine-thousand payload is a well-formed float and fails on the ceiling. "BTC" is a well-formed string and fails on the allowed set. Both are policy decisions, not type errors.

Putting them in the model means the policy is in the published schema - a caller sees the maximum and the allowed values before sending anything, rather than discovering them by rejection.

The mistake this prevents

The mistake is enforcing policy in the service and leaving the model permissive. The contract then advertises something more permissive than the system accepts, and callers write code against the advertised version.

Takeaway

Put policy limits - ceilings, allowed values, minimum lengths - on the model. They become part of the published contract rather than a surprise.