Read only fields#

Sometimes, fields should never be able to be specified by the client. For example, upon creation of a new resource instance, the id field of a model should be generated by the server and not specified by the client.

 1from __future__ import annotations
 2
 3from dataclasses import dataclass
 4
 5from litestar import Litestar, post
 6from litestar.dto import DataclassDTO, DTOConfig
 7
 8
 9@dataclass
10class Person:
11    name: str
12    age: int
13    email: str
14    id: int
15
16
17class ReadDTO(DataclassDTO[Person]):
18    config = DTOConfig(exclude={"email"})
19
20
21class WriteDTO(DataclassDTO[Person]):
22    config = DTOConfig(exclude={"id"})
23
24
25@post("/person", dto=WriteDTO, return_dto=ReadDTO, sync_to_thread=False)
26def create_person(data: Person) -> Person:
27    # Logic for persisting the person goes here
28    return data
29
30
31app = Litestar(route_handlers=[create_person])

In this adjustment, we add an id field to the Person model. We also create a new class, WriteDTO that is instructed to ignore the id attribute.

The WriteDTO class is assigned to the handler via the dto kwarg (dto=WriteDTO) meaning that the id field will be ignored from any data received from the client when creating a new Person instance.

When we try to create a new Person instance with an id field specified, we get an error:

../../_images/read_only_fields_error.png

What’s happening? The DTO is trying to construct an instance of the Person model but we have excluded the id field from the accepted client data. The id field is required by the Person model, so the model constructor raises an error.

There is more than one way to address this issue, for example we could assign a default value to the id field and overwrite the default in the handler, or we could create an entirely separate model that has no id field, and transfer the data from that to the Person model in the handler. However, Litestar has a built-in solution for this: DTOData.