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