What are Data Access Object and Data Transfer Object in Python?

Advertisement

Jun 16, 2025 By Alison Perry

Writing organized Python code becomes more essential as your app moves beyond quick scripts and grows into something layered. Without a structure, things can quickly spiral. Query logic sneaks into business functions. Data is passed around as raw dictionaries. And changes? They turn into a hunt across multiple files. That’s why two design patterns — Data Access Object (DAO) and Data Transfer Object (DTO) — help bring some sanity into your project.

DAO keeps your interaction with the database in one place. DTO gives you a structured way to move data through your app. Used properly, they help separate responsibilities, reduce code duplication, and make everything easier to maintain.

What DAO Actually Does

The Data Access Object acts as the middle layer between your program and the database. Instead of spreading raw SQL throughout your codebase, you centralize it inside a DAO. That way, all create, read, update, and delete operations are in one class or module.

For instance, if your program manages contacts, your DAO would be the one sending and receiving data from the contacts table. Your app doesn’t need to know what the SQL looks like. It just makes a method call like contact_dao.get_all() or contact_dao.save(new_contact).

This doesn’t just improve readability. If you need to switch from SQLite to PostgreSQL, or update your schema, you won’t have to track down scattered queries. You update the DAO, and everything else keeps running.

DAO in Python: An Example

Let’s take a look at a basic DAO for managing contacts:

python

CopyEdit

import sqlite3

from typing import List, Optional

from contact_dto import ContactDTO

class ContactDAO:

def __init__(self, db_path):

self.conn = sqlite3.connect(db_path)

def get_by_id(self, contact_id: int) -> Optional[ContactDTO]:

cursor = self.conn.cursor()

cursor.execute("SELECT id, name, email FROM contacts WHERE id = ?", (contact_id,))

row = cursor.fetchone()

return ContactDTO(*row) if row else None

def get_all(self) -> List[ContactDTO]:

cursor = self.conn.cursor()

cursor.execute("SELECT id, name, email FROM contacts")

rows = cursor.fetchall()

return [ContactDTO(*row) for row in rows]

def save(self, contact: ContactDTO):

cursor = self.conn.cursor()

cursor.execute(

"INSERT INTO contacts (name, email) VALUES (?, ?)",

(contact.name, contact.email)

)

self.conn.commit()

By wrapping database operations in this class, you're creating a single source of truth for interacting with your data storage. That makes debugging easier and cuts down on accidental duplication.

How DTO Fits In

The Data Transfer Object is much simpler in behavior — in fact, it doesn’t do much. And that’s by design. A DTO holds structured data. Nothing more. No logic. No dependencies. It’s just a container for data that needs to be passed between layers of your app.

This becomes useful when you’re handling more than a couple of fields. Instead of juggling a mix of dictionaries and lists with unclear keys, you use a DTO that clearly defines its structure.

Defining a DTO in Python

Python makes defining DTOs easy with dataclass:

python

CopyEdit

from dataclasses import dataclass

@dataclass

class ContactDTO:

id: int

name: str

email: str

If some fields are optional, you can provide default values:

python

CopyEdit

@dataclass

class ContactDTO:

id: int = 0

name: str = ''

email: str = ''

This structured format helps keep your code consistent. Developers know exactly what to expect from a ContactDTO object, and tools like autocomplete become more helpful.

You also avoid the silent errors that come with loosely structured data — like typos in dictionary keys or missing fields.

How DAO and DTO Work as a Pair

In a well-structured program, the DAO pulls data from the database and hands it off in the form of a DTO. That DTO can then move freely through the rest of your app, whether you're sending it to a user interface, an API endpoint, or some internal logic.

Here’s a quick example to show the handoff:

python

CopyEdit

def print_contacts():

dao = ContactDAO("contacts.db")

contacts = dao.get_all()

for contact in contacts:

print(f"{contact.id}: {contact.name} - {contact.email}")

In this case, the application code never touches SQL or raw tuples. It deals with clean Python objects. That makes the code easier to test, easier to read, and less prone to errors.

Building a Python Project Using DAO and DTO – Step by Step

If you're starting a new project or refactoring an old one, here's how you can use DAO and DTO properly from the beginning.

Step 1: Create Your DTO Class

Start by defining the structure of the data you want to work with.

python

CopyEdit

from dataclasses import dataclass

@dataclass

class ContactDTO:

id: int

name: str

email: str

This will represent a single record in your contacts system. Whether you’re displaying a list or adding new entries, this object will carry your data.

Step 2: Write Your DAO Class

Next, you build the class that manages reading from and writing to the database. Avoid adding any business logic here. Keep it focused on data handling only.

python

CopyEdit

import sqlite3

class ContactDAO:

def __init__(self, db_path):

self.conn = sqlite3.connect(db_path)

def save(self, contact: ContactDTO):

cursor = self.conn.cursor()

cursor.execute(

"INSERT INTO contacts (name, email) VALUES (?, ?)",

(contact.name, contact.email)

)

self.conn.commit()

The goal is to wrap each database operation in its method. That way, any part of your app that needs to access the data can do it through a clear and reusable function.

Step 3: Work with These Components in Application Code

With both DAO and DTO in place, your app code becomes more readable and less error-prone.

python

CopyEdit

def main():

dao = ContactDAO("contacts.db")

new_contact = ContactDTO(name="Charlie", email="[email protected]")

dao.save(new_contact)

contacts = dao.get_all()

for contact in contacts:

print(contact)

The business logic here — creating a new contact, saving it, and printing it — stays clean. No SQL. No field-matching. Just plain method calls and structured data.

Step 4: Keep Responsibilities Separate

It's important that you resist the temptation to add utility methods or logic inside the DTO. This class should not make decisions or interact with the database. It just holds data. If you need logic around contact validation or formatting, handle that in a service layer or elsewhere in your codebase.

Wrapping It Up

DAO and DTO are simple patterns that bring clear structure to a Python application. DAO manages the communication with your data source and provides a central place for all data operations. DTO offers a lightweight, consistent container for passing data through your application.

Used together, they help reduce duplication, improve testability, and make your project easier to understand and maintain, especially as it grows in complexity. Whether you're building a tiny tool or a larger application, these patterns are easy to adopt and pay off almost immediately in the form of cleaner, more reliable code.

Advertisement

You May Like

Top

Explainable Artificial Intelligence (XAI): A Guide for AI and ML Engineers

How explainable artificial intelligence helps AI and ML engineers build transparent and trustworthy models. Discover practical techniques and challenges of XAI for engineers in real-world applications

Jul 15, 2025
Read
Top

Getting Practical with Sentence Transformers: Training and Fine-Tuning Explained

How to train and fine-tune sentence transformers to create high-performing NLP models tailored to your data. Understand the tools, methods, and strategies to make the most of sentence embedding models

Jun 30, 2025
Read
Top

5 Exciting Python Libraries to Watch in 2025

Looking for the next big thing in Python development? Explore upcoming libraries like PyScript, TensorFlow Quantum, FastAPI 2.0, and more that will redefine how you build and deploy systems in 2025

Jun 18, 2025
Read
Top

ACID vs. BASE: Two Approaches to Consistency in Data Engineering

Explore how ACID and BASE models shape database reliability, consistency, and scalability. Learn when to prioritize structure versus flexibility in your data systems

Jun 20, 2025
Read
Top

Why BigQuery Is the Backbone of Modern Data Analytics

Discover how Google BigQuery revolutionizes data analytics with its serverless architecture, fast performance, and versatile features

Jun 19, 2025
Read
Top

Avoid These PyTorch Pitfalls to Improve Your Workflow

Are you running into frustrating bugs with PyTorch? Discover the common mistakes developers make and learn how to avoid them for smoother machine learning projects

Jun 16, 2025
Read
Top

PPO Explained: A Practical Guide to Smarter Policy Learning

Explore Proximal Policy Optimization, a widely-used reinforcement learning algorithm known for its stable performance and simplicity in complex environments like robotics and gaming

Jun 30, 2025
Read
Top

How to Use Google Mediapipe Tasks API for Easy Real-Time Machine Learning

Learn how to simplify machine learning integration using Google’s Mediapipe Tasks API. Discover its key features, supported tasks, and step-by-step guidance for building real-time ML applications

Jun 14, 2025
Read
Top

Why Redis OM for Python Is a Game-Changer for Fast, Structured Data

Learn how Redis OM for Python transforms Redis into a model-driven, queryable data layer with real-time performance. Define, store, and query structured data easily—no raw commands needed

Jun 18, 2025
Read
Top

What Gradio Joining Hugging Face Means for AI Development

Gradio is joining Hugging Face in a move that simplifies machine learning interfaces and model sharing. Discover how this partnership makes AI tools more accessible for developers, educators, and users

Jul 04, 2025
Read
Top

The Role of the Expert Acceleration Program in Advancing Sempre Health ML Roadmap

How Sempre Health is accelerating its ML roadmap with the help of the Expert Acceleration Program, improving model deployment, patient outcomes, and internal efficiency

Jul 01, 2025
Read
Top

Using N-gram Language Models to Boost Wav2Vec2 Performance in Transformers

Improve automatic speech recognition accuracy by boosting Wav2Vec2 with an n-gram language model using Transformers and pyctcdecode. Learn how shallow fusion enhances transcription quality

Jul 03, 2025
Read