The primary goal of a DTO is to simplify communication between different layers of an application, particularly when transmitting data through various boundary interfaces such as web services, REST APIs, message brokers, or other mechanisms of remote interaction. When exchanging information with other systems, it is crucial to minimize unnecessary costs, such as redundant serialization/deserialization, and to ensure a clear data structure that represents a specific contract between the sender and receiver. In this article, I intend to explore the capabilities that Python offers for implementing DTOs. Starting from built-in tools and extending to specialized libraries. Among the core functionalities, I want to emphasize type and data validation, object creation, and conversion to dictionaries. DTO Based on a Python Class Let's consider an example of a DTO based on a Python class. Let's assume we have a user model that includes a first name and a last name: class UserDTO: def __init__(self, **kwargs): self.first_name = kwargs.get("first_name") self.last_name = kwargs.get("last_name") self.validate_lastname() def validate_lastname(self): if len(self.last_name) <= 2: raise ValueError("last_name length must be more then 2") def to_dict(self): return self.__dict__ @classmethod def from_dict(cls, dict_obj): return cls(**dict_obj) We have implemented methods in the DTO class for creating an instance of the class, converting data to a dictionary, and adding validation logic. Now, let's see how this can be used: >>> user_dto = UserDTO.from_dict({'first_name': 'John', 'last_name': 'Doe'}) >>> user_dto.to_dict() {'first_name': 'John', 'last_name': 'Doe'} >>> user_dto = UserDTO.from_dict({'first_name': 'John', 'last_name': 'Do'}) ValueError: last_name length must be more then 2 This is a highly simplified example. This approach allows you to implement various functionalities. The only downside is that you need to define everything manually, and even with inheritance, there might be a significant amount of code. NamedTuple Another way to create a DTO in Python is by using . NamedTuple is a class from the Python standard library (introduced in Python 3.6) representing an immutable tuple with named properties. It is a typed and more readable version of the class from the module. NamedTuple namedtuple collections We can create a DTO based on , containing the first name and last name of a user from the example using classes: NamedTuple from typing import NamedTuple class UserDTO(NamedTuple): first_name: str last_name: str Now, we can create UserDTO objects as follows, as well as convert the object to a dictionary and create an object from a dictionary: >>> user_dto = UserDTO(**{'first_name': 'John', 'last_name': 'Doe'}) >>> user_dto.first_name 'John' >>> user_dto UserDTO(first_name='John', last_name='Doe'}) >>> user_dto._asdict() {'first_name': 'John', 'last_name': 'Doe'} >>> user_dto.first_name = 'Bill' AttributeError: can't set attribute There is no built-in type and data validation, but it provides a more compact definition and a readable format. It is also immutable, which provides more security during operation. Only the defined arguments can be passed as input, and there is a method for converting it to a dictionary. _asdict For more details, see . here TypedDict Another option for creating DTO objects in Python is to use , which has been introduced in the language starting from version 3.8. This data type allows you to create dictionaries with a fixed set of keys and annotations for value types. TypedDict This approach makes a good choice for creating DTO objects when you need to use a dictionary with a specific set of keys. TypedDict To create an object, you need to import the data type from the typing module. Let's create a for the user model: TypedDict TypedDict from typing import TypedDict class UserDTO(TypedDict): first_name: str last_name: str In this example, we define the class, which is a subclass of . We can create a object and populate it with data: UserDTO TypedDict UserDTO >>> user_dto = UserDTO(**{first_name: 'John', last_name: 'Doe'}) >>> user_dto {first_name: 'John', last_name: 'Doe'} >>> type(user_dto) <class 'dict'> We can use it to define dictionaries with a fixed set of keys and annotations for value types. This makes the code more readable and predictable. Additionally, provides the ability to use dictionary methods such as keys() and values(), which can be useful in certain cases. TypedDict For more details, see . here Dataclass is a decorator that provides a simple way to create classes for storing data. uses type annotations to define fields and then generates all the necessary methods for creating and using objects of that class. Dataclass Dataclass To create a DTO using , you need to add the decorator and define fields with type annotations. For example, we can create a DTO for a user model using like this: dataclass dataclass dataclass from dataclasses import asdict, dataclass @dataclass class UserDTO: first_name: str last_name: str = '' def __post_init__(self): self.validate_lastname() def validate_lastname(self): if len(self.last_name) <= 2: raise ValueError("last_name length must be more then 2") Now, we can easily create objects, convert them to dictionaries, and create new objects based on dictionaries: UserDTO >>> user_dto = UserDTO(**{'first_name': 'John', 'last_name': 'Doe'}) >>> user_dto UserDTO(first_name='John', last_name='Doe') >>> asdict(user_dto) {'first_name': 'John', 'last_name': 'Doe'} >>> user_dto = UserDTO(**{'first_name': 'John', 'last_name': 'Do'}) ValueError: last_name length must be more then 2 To create an immutable object, you can pass the argument to the decorator. There's an method for converting to a dictionary. Additionally, you can implement validation methods and use default values. frozen=True asdict In general, are more compact than plain classes and more functional than the previously discussed options. dataclasses For more details, see . here Attr Another way to create DTOs is by using the module. It works similarly to but is more functional and provides a more compact description. This library can be installed using the command . attr dataclass pip install attrs import attr @attr.s class UserDTO: first_name: str = attr.ib(default="John", validator=attr.validators.instance_of(str)) last_name: str = attr.ib(default="Doe", validator=attr.validators.instance_of(str)) Here, using the decorator, we define a DTO class with attributes and , while also setting default values and validation. first_name last_name >>> user_dto = UserDTO(**{'first_name': 'John', 'last_name': 'Doe'}) >>> user_dto UserDTO(first_name='John', last_name='Doe') >>> user_dto = UserDTO() >>> user_dto UserDTO(first_name='John', last_name='Doe') >>> user_dto = UserDTO(**{'first_name': 1, 'last_name': 'Doe'}) TypeError: ("'first_name' must be <class 'str'>... In this way, the module offers powerful and flexible tools for defining DTO classes, such as validation, default values, and transformations. The DTO object can also be made immutable using the decorator attribute . It can also be initialized through the decorator. attrs frozen=True define For more details, see . here Pydantic library is a tool for data validation and data conversion in Python. It utilizes type annotations to define data schemas and converts JSON data into Python objects. Pуdantic is used for the convenient handling of data from web requests, configuration files, databases, and other scenarios where data validation and conversion are required. Pydantic It can be installed using the command pip install pydantic. from pydantic import BaseModel, Field, field_validator class UserDTO(BaseModel): first_name: str last_name: str = Field(min_length=2, alias="lastName") age: int = Field(lt=100, description="Age must be a positive integer") @field_validator("age") def validate_age(cls, value): if value < 18: raise ValueError("Age must be at least 18") return value In this example, we've defined a model with basic validations for string length and maximum age. We've also specified that data for the attribute will come through the parameter . UserDTO last_name lastName Additionally, as an example, a custom validator for minimum age is demonstrated. >>> user_dto = UserDTO(**{'first_name': 'John', 'lastName': 'Doe', 'age': 31}) >>> user_dto UserDTO(first_name='John', last_name='Doe', age=31) >>> user_dto.model_dump() {'first_name': 'John', 'last_name': 'Doe', 'age': 31} >>> user_dto.model_dump_json() '{"first_name":"John","last_name":"Doe","age":31}' >>> user_dto = UserDTO(**{'first_name': 'John', 'lastName': 'D', 'age': 3}) pydantic_core._pydantic_core.ValidationError: 2 validation errors for UserDTO lastName String should have at least 2 characters [type=string_too_short, input_value='D', input_type=str] age Value error, Age must be at least 18 [type=value_error, input_value=3, input_type=int] is a versatile tool. It is the default data schema and validation tool used in FastAPI. It simplifies the serialization and deserialization of objects to JSON format using built-in methods and provides more readable runtime hints. Pydantic For more details, see . here Conclusion In this article, I covered different ways of implementing DTOs in Python, starting from simple approaches to more complex ones. The choice of which method to use for your project depends on various factors. This includes the Python version in your project and whether you have the ability to install new dependencies. It also depends on whether you plan to use validation, conversion, or if simple type annotations are sufficient. I hope this article will help those who are seeking suitable methods for implementing DTOs in Python for their projects.