or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-routing.mdconfiguration.mddto.mdexceptions.mdhttp-handlers.mdindex.mdmiddleware.mdopenapi.mdplugins.mdrequest-response.mdsecurity.mdtesting.mdwebsocket.md

dto.mddocs/

0

# Data Transfer Objects (DTOs)

1

2

Serialization and validation system using DTOs for request/response data transformation. Litestar's DTO system supports dataclasses, Pydantic models, and msgspec structs with automatic validation and conversion.

3

4

## Capabilities

5

6

### Abstract DTO Base Class

7

8

Base class for all DTO implementations providing common functionality.

9

10

```python { .api }

11

class AbstractDTO:

12

config: DTOConfig

13

14

@classmethod

15

def create_for_field_definition(

16

cls,

17

field_definition: FieldDefinition,

18

config: DTOConfig | None = None,

19

) -> type[AbstractDTO]:

20

"""

21

Create a DTO class for a field definition.

22

23

Parameters:

24

- field_definition: Field definition to create DTO for

25

- config: Optional DTO configuration

26

27

Returns:

28

DTO class configured for the field definition

29

"""

30

31

@classmethod

32

def generate_field_definitions(

33

cls,

34

model_type: type[Any],

35

) -> Generator[DTOFieldDefinition, None, None]:

36

"""Generate field definitions from model type."""

37

38

def decode_bytes(self, raw: bytes) -> Any:

39

"""Decode bytes to Python object."""

40

41

def decode_builtins(self, builtins: dict[str, Any] | list[Any]) -> Any:

42

"""Decode builtin types to Python object."""

43

44

def encode_data(self, data: Any) -> dict[str, Any] | list[dict[str, Any]]:

45

"""Encode Python object to serializable data."""

46

```

47

48

### DTO Configuration

49

50

Configuration class for customizing DTO behavior.

51

52

```python { .api }

53

class DTOConfig:

54

def __init__(

55

self,

56

*,

57

exclude: set[str] | None = None,

58

include: set[str] | None = None,

59

rename_fields: dict[str, str] | None = None,

60

forbid_unknown_fields: bool = False,

61

max_nested_depth: int = 1,

62

partial: bool = False,

63

underscore_fields_private: bool = True,

64

experimental_features: list[DTOConfigFeatures] | None = None,

65

):

66

"""

67

Configure DTO behavior.

68

69

Parameters:

70

- exclude: Fields to exclude from serialization

71

- include: Fields to include in serialization (mutually exclusive with exclude)

72

- rename_fields: Mapping of field names to rename

73

- forbid_unknown_fields: Reject unknown fields in input data

74

- max_nested_depth: Maximum depth for nested object serialization

75

- partial: Allow partial updates (fields become optional)

76

- underscore_fields_private: Treat underscore-prefixed fields as private

77

- experimental_features: List of experimental features to enable

78

"""

79

80

def create_for_field_definition(

81

self,

82

field_definition: FieldDefinition,

83

) -> DTOConfig:

84

"""Create config for specific field definition."""

85

```

86

87

### Built-in DTO Implementations

88

89

Pre-built DTO classes for common Python data structures.

90

91

```python { .api }

92

class DataclassDTO(AbstractDTO):

93

"""DTO implementation for dataclasses."""

94

95

@classmethod

96

def create_for_field_definition(

97

cls,

98

field_definition: FieldDefinition,

99

config: DTOConfig | None = None,

100

) -> type[DataclassDTO]:

101

"""Create DataclassDTO for field definition."""

102

103

class MsgspecDTO(AbstractDTO):

104

"""DTO implementation for msgspec Structs."""

105

106

@classmethod

107

def create_for_field_definition(

108

cls,

109

field_definition: FieldDefinition,

110

config: DTOConfig | None = None,

111

) -> type[MsgspecDTO]:

112

"""Create MsgspecDTO for field definition."""

113

```

114

115

### DTO Data Structures

116

117

Core data structures used by the DTO system.

118

119

```python { .api }

120

class DTOData:

121

def __init__(

122

self,

123

*,

124

data_as_builtins: dict[str, Any] | list[dict[str, Any]],

125

data_as_bytes: bytes,

126

data_as_model_instance: Any,

127

):

128

"""

129

Container for DTO data in different formats.

130

131

Parameters:

132

- data_as_builtins: Data as built-in Python types

133

- data_as_bytes: Data as encoded bytes

134

- data_as_model_instance: Data as model instance

135

"""

136

137

@property

138

def as_builtins(self) -> dict[str, Any] | list[dict[str, Any]]:

139

"""Get data as built-in Python types."""

140

141

@property

142

def as_bytes(self) -> bytes:

143

"""Get data as encoded bytes."""

144

145

@property

146

def as_model_instance(self) -> Any:

147

"""Get data as model instance."""

148

149

class DTOFieldDefinition:

150

def __init__(

151

self,

152

dto_field: DTOField,

153

model_name: str,

154

field_definition: FieldDefinition | None = None,

155

default_value: Any = Empty,

156

):

157

"""

158

Definition of a DTO field.

159

160

Parameters:

161

- dto_field: DTO field configuration

162

- model_name: Name of the model this field belongs to

163

- field_definition: Optional field definition

164

- default_value: Default value for the field

165

"""

166

167

@property

168

def serialization_name(self) -> str:

169

"""Get the serialization name for this field."""

170

171

@property

172

def is_required(self) -> bool:

173

"""Check if this field is required."""

174

175

@property

176

def is_excluded(self) -> bool:

177

"""Check if this field is excluded from serialization."""

178

```

179

180

### DTO Field Configuration

181

182

Field-level configuration and marking utilities.

183

184

```python { .api }

185

class DTOField:

186

def __init__(

187

self,

188

mark: Mark | None = None,

189

default: Any = Empty,

190

):

191

"""

192

DTO field configuration.

193

194

Parameters:

195

- mark: Field marking for inclusion/exclusion

196

- default: Default value for the field

197

"""

198

199

def dto_field(

200

mark: Mark | None = None,

201

default: Any = Empty,

202

) -> Any:

203

"""

204

Create a DTO field with configuration.

205

206

Parameters:

207

- mark: Field marking for inclusion/exclusion

208

- default: Default value for the field

209

210

Returns:

211

DTO field configuration

212

"""

213

214

class Mark(Enum):

215

"""Field marking for DTO behavior."""

216

READ_ONLY = "read_only"

217

WRITE_ONLY = "write_only"

218

PRIVATE = "private"

219

```

220

221

### Rename Strategies

222

223

Strategies for field name transformation during serialization.

224

225

```python { .api }

226

class RenameStrategy(str, Enum):

227

"""Field renaming strategies."""

228

UPPER = "upper"

229

LOWER = "lower"

230

CAMEL = "camel"

231

PASCAL = "pascal"

232

233

def convert_case(value: str, strategy: RenameStrategy) -> str:

234

"""

235

Convert field name case according to strategy.

236

237

Parameters:

238

- value: Original field name

239

- strategy: Renaming strategy to apply

240

241

Returns:

242

Transformed field name

243

"""

244

```

245

246

## Usage Examples

247

248

### Basic Dataclass DTO

249

250

```python

251

from litestar import Litestar, post, get

252

from litestar.dto import DataclassDTO, DTOConfig

253

from dataclasses import dataclass

254

from typing import Optional

255

256

@dataclass

257

class User:

258

name: str

259

email: str

260

age: int

261

id: Optional[int] = None

262

263

# Create DTO for User dataclass

264

UserDTO = DataclassDTO[User]

265

266

@post("/users", dto=UserDTO)

267

def create_user(data: User) -> User:

268

# data is automatically validated and converted from request JSON

269

# Generate ID for new user

270

data.id = 123

271

return data

272

273

@get("/users/{user_id:int}", return_dto=UserDTO)

274

def get_user(user_id: int) -> User:

275

# Response is automatically serialized using DTO

276

return User(id=user_id, name="Alice", email="alice@example.com", age=30)

277

278

app = Litestar(route_handlers=[create_user, get_user])

279

```

280

281

### DTO Configuration with Field Exclusion

282

283

```python

284

from litestar.dto import DataclassDTO, DTOConfig

285

286

@dataclass

287

class UserWithPassword:

288

name: str

289

email: str

290

password: str

291

created_at: datetime

292

id: Optional[int] = None

293

294

# Exclude sensitive fields from serialization

295

read_dto_config = DTOConfig(exclude={"password"})

296

UserReadDTO = DataclassDTO[UserWithPassword].with_config(read_dto_config)

297

298

# Only include necessary fields for creation

299

write_dto_config = DTOConfig(include={"name", "email", "password"})

300

UserWriteDTO = DataclassDTO[UserWithPassword].with_config(write_dto_config)

301

302

@post("/users", dto=UserWriteDTO, return_dto=UserReadDTO)

303

def create_user_secure(data: UserWithPassword) -> UserWithPassword:

304

# Input: only name, email, password allowed

305

# Output: password excluded, all other fields included

306

data.id = 123

307

data.created_at = datetime.utcnow()

308

return data

309

```

310

311

### Field Renaming and Case Conversion

312

313

```python

314

from litestar.dto import DTOConfig, RenameStrategy

315

316

@dataclass

317

class APIResponse:

318

user_id: int

319

full_name: str

320

email_address: str

321

is_active: bool

322

323

# Convert snake_case to camelCase for API

324

camel_case_config = DTOConfig(

325

rename_fields={

326

"user_id": "userId",

327

"full_name": "fullName",

328

"email_address": "emailAddress",

329

"is_active": "isActive"

330

}

331

)

332

APIResponseDTO = DataclassDTO[APIResponse].with_config(camel_case_config)

333

334

@get("/api/user/{user_id:int}", return_dto=APIResponseDTO)

335

def get_user_api(user_id: int) -> APIResponse:

336

return APIResponse(

337

user_id=user_id,

338

full_name="Alice Smith",

339

email_address="alice@example.com",

340

is_active=True

341

)

342

# Response JSON: {"userId": 123, "fullName": "Alice Smith", ...}

343

```

344

345

### Partial Updates with DTOs

346

347

```python

348

from litestar.dto import DTOConfig

349

350

@dataclass

351

class User:

352

name: str

353

email: str

354

age: int

355

id: int

356

357

# Allow partial updates - all fields become optional

358

partial_config = DTOConfig(partial=True)

359

UserPartialDTO = DataclassDTO[User].with_config(partial_config)

360

361

@patch("/users/{user_id:int}", dto=UserPartialDTO, return_dto=DataclassDTO[User])

362

def update_user(user_id: int, data: User) -> User:

363

# Only provided fields will be present in data

364

# Merge with existing user data

365

existing_user = get_user_from_db(user_id)

366

367

# Update only provided fields

368

if hasattr(data, 'name') and data.name is not None:

369

existing_user.name = data.name

370

if hasattr(data, 'email') and data.email is not None:

371

existing_user.email = data.email

372

if hasattr(data, 'age') and data.age is not None:

373

existing_user.age = data.age

374

375

return existing_user

376

```

377

378

### Nested Object Serialization

379

380

```python

381

from dataclasses import dataclass

382

from typing import List

383

384

@dataclass

385

class Address:

386

street: str

387

city: str

388

country: str

389

390

@dataclass

391

class Company:

392

name: str

393

address: Address

394

395

@dataclass

396

class Employee:

397

name: str

398

company: Company

399

addresses: List[Address]

400

401

# Configure nested depth

402

nested_config = DTOConfig(max_nested_depth=2)

403

EmployeeDTO = DataclassDTO[Employee].with_config(nested_config)

404

405

@get("/employees/{emp_id:int}", return_dto=EmployeeDTO)

406

def get_employee(emp_id: int) -> Employee:

407

return Employee(

408

name="John Doe",

409

company=Company(

410

name="Tech Corp",

411

address=Address("123 Main St", "City", "Country")

412

),

413

addresses=[

414

Address("456 Home St", "Home City", "Country"),

415

Address("789 Vacation Ave", "Vacation City", "Country")

416

]

417

)

418

```

419

420

### Msgspec Integration

421

422

```python

423

import msgspec

424

from litestar.dto import MsgspecDTO

425

426

@msgspec.defstruct

427

class Product:

428

name: str

429

price: float

430

category: str

431

in_stock: bool = True

432

433

ProductDTO = MsgspecDTO[Product]

434

435

@post("/products", dto=ProductDTO, return_dto=ProductDTO)

436

def create_product(data: Product) -> Product:

437

# msgspec provides very fast serialization

438

return data

439

440

@get("/products", return_dto=MsgspecDTO[List[Product]])

441

def list_products() -> List[Product]:

442

return [

443

Product("Laptop", 999.99, "Electronics"),

444

Product("Book", 29.99, "Education", False)

445

]

446

```

447

448

### Custom DTO Implementation

449

450

```python

451

from litestar.dto import AbstractDTO, DTOConfig

452

from typing import TypeVar, Generic

453

454

T = TypeVar("T")

455

456

class CustomDTO(AbstractDTO, Generic[T]):

457

"""Custom DTO with additional validation."""

458

459

def decode_builtins(self, builtins: dict[str, Any] | list[Any]) -> T:

460

# Custom deserialization logic

461

data = super().decode_builtins(builtins)

462

463

# Add custom validation

464

if hasattr(data, 'email') and '@' not in data.email:

465

raise ValueError("Invalid email format")

466

467

return data

468

469

def encode_data(self, data: T) -> dict[str, Any] | list[dict[str, Any]]:

470

# Custom serialization logic

471

encoded = super().encode_data(data)

472

473

# Add metadata

474

if isinstance(encoded, dict):

475

encoded['_serialized_at'] = datetime.utcnow().isoformat()

476

477

return encoded

478

479

@post("/custom", dto=CustomDTO[User])

480

def create_with_custom_dto(data: User) -> dict:

481

return {"status": "created", "user": data}

482

```

483

484

### Validation and Error Handling

485

486

```python

487

from litestar.exceptions import ValidationException

488

489

@dataclass

490

class CreateUserRequest:

491

name: str

492

email: str

493

age: int

494

495

def __post_init__(self):

496

# Custom validation in dataclass

497

if not self.name.strip():

498

raise ValueError("Name cannot be empty")

499

if self.age < 0:

500

raise ValueError("Age must be positive")

501

if '@' not in self.email:

502

raise ValueError("Invalid email format")

503

504

UserCreateDTO = DataclassDTO[CreateUserRequest]

505

506

@post("/users/validated", dto=UserCreateDTO)

507

def create_user_validated(data: CreateUserRequest) -> dict:

508

# DTO automatically handles validation

509

# Any validation errors are converted to ValidationException

510

return {"message": f"User {data.name} created successfully"}

511

512

# Custom exception handler for DTO validation errors

513

def dto_validation_handler(request: Request, exc: ValidationException) -> Response:

514

return Response(

515

content={

516

"error": "Validation failed",

517

"details": exc.extra,

518

"message": exc.detail

519

},

520

status_code=422

521

)

522

```

523

524

## Types

525

526

```python { .api }

527

# Generic DTO type

528

DTOType = TypeVar("DTOType", bound=AbstractDTO)

529

530

# DTO configuration features

531

class DTOConfigFeatures(str, Enum):

532

EXPERIMENTAL_GENERIC_SUPPORT = "experimental_generic_support"

533

534

# Field definition types

535

FieldDefinition = Any # From litestar._signature module

536

537

# Default values

538

class Empty:

539

"""Sentinel value for empty/unset fields."""

540

541

# Encoding types

542

EncodedData = bytes | str | dict[str, Any] | list[dict[str, Any]]

543

DecodedData = Any

544

```