Skip to content

Typed values at the boundaries

A function that crosses a boundary returns a typed structure and not a bare dict.

What to read

  • Point is a frozen @dataclass, the shape of a boundary that needs no validation. origin returns it.
  • NewUser is a pydantic BaseModel of the domain, the shape of a boundary whose fields are validated. normalize takes one and returns one.

The rules report the two shapes of a bare dict at a boundary:

def origin() -> dict:  # a `dict` return annotation
    return {"x": 0, "y": 0}  # a returned `{...}` literal

What to adapt

  • Point and NewUser are examples. Name the structure for each boundary of your code.
  • Use a dataclass when no validation is needed and a BaseModel where it is.
  • The input and the output of an API are request and response schemas, with their own names and settings: see the topic api-schemas.

References

code/app/core/models.py:10-20

a boundary that needs no validation: a dataclass that a function returns

@dataclass(frozen=True)
class Point:
    """A point of the plane: a dataclass, since nothing in it needs validation."""

    x: int
    y: int


def origin() -> Point:
    """Return the origin of the plane."""
    return Point(x=0, y=0)

code/app/core/models.py:23-32

a boundary whose fields are validated: a pydantic model of the domain

class NewUser(BaseModel):
    """A user before it is stored: a model of the domain whose fields are validated."""

    email: str
    display_name: str


def normalize(user: NewUser) -> NewUser:
    """Return the user with the email in lower case and the name without surrounding spaces."""
    return NewUser(email=user.email.lower(), display_name=user.display_name.strip())

Cited by