Implementing a Generic DAO Base Class with SQLAlchemy and Reflection

In SQLAlchemy development, writing separate CRUD operations for each entity class can lead to repetitive code. Drawing inspiration from Java's abstract classes and reflection capabilities, we can implement a generic DAO base class in Python to minimize code duplication. This article presents a straightforward implementation of such a solution.

Entity Class Example

Our entities are simple classes with only attributes and no additional methods. Here's an example:

Base = declarative_base()

class StudentProfile(Base):
    """
    Student profile entity
    Table name: student_profiles
    Stores fundamental student information. During data import, 
    existing records are cleared and replaced with new data.
    """
    __tablename__ = 'student_profiles'
    
    id = Column(Integer, primary_key=True)
    full_name = Column(String(50))
    """Student full name, limited to 50 characters"""
    identification_number = Column(String(18))
    """National ID number, limited to 18 characters"""
    enrollment_year = Column(Integer)
    """Year of enrollment, numeric value"""
    class_number = Column(Integer)
    """Class identifier, numeric value"""
    gender = Column(String(1))
    """Gender, single character"""
    student_id = Column(String(20))
    """Unique student identifier"""

Database Connector

The DatabaseConnector class manages database connections and sessions, using SQLite in this example:

class DatabaseConnector:
    """Database connection factory for managing SQLAlchemy sessions and engines"""
    
    def __init__(self):
        """Initialize with private session and engine variables set to None"""
        self.session = None
        self.engine = None
        self.initialize_database()
    
    def initialize_database(self, db_path="university.db"):
        """
        Configure the database engine
        :param db_path: String specifying the database file location
                       Defaults to 'university.db' in the application directory
        :return: None
        """
        self.engine = create_engine(f'sqlite:///{db_path}', echo=False)
    
    def get_session(self):
        """
        Retrieve a session instance
        Creates a new session if one doesn't exist
        :return: SQLAlchemy Session instance
        """
        if self.engine is None:
            self.initialize_database()
        
        Session = sessionmaker(bind=self.engine)
        if self.session is None:
            self.session = Session()
        return self.session
    
    def create_tables(self):
        """
        Generate database tables based on defined models
        Typically called during application setup
        :return: None
        """
        Base.metadata.create_all(self.engine)

Generic DAO Implementation

The core of our solution is the GenericDAO abstract base class:

from abc import ABC, abstractmethod

class GenericDAO(ABC):
    """
    Abstract base class for Data Access Objects in MVC architecture
    Implements common database operations using SQLAlchemy
    """
    
    def __init__(self):
        """Initialize with a DatabaseConnector instance"""
        self.db_connector = DatabaseConnector()
    
    @abstractmethod
    def get_entity_class(self):
        """
        Abstract method returning the entity class this DAO manages
        Used in query construction
        :return: Entity class
        """
        pass
    
    def find_by_id(self, entity_id):
        """
        Retrieve an entity by its primary key
        :param entity_id: Primary key value of the entity
        :return: Entity instance
        :raises: NoResultError if entity not found
        """
        return self.db_connector.get_session().query(
            self.get_entity_class()
        ).filter(
            self.get_entity_class().id == entity_id
        ).one()
    
    def create(self, entity):
        """
        Persist a new entity to the database
        :param entity: Entity instance to be saved
        :return: None
        """
        self.db_connector.get_session().add(entity)
        self.db_connector.get_session().commit()
    
    def update(self, entity):
        """
        Update an existing entity in the database
        Handles both managed (session-tracked) and detached entities
        
        For managed entities (retrieved within the same session),
        modifications are tracked automatically and only commit is needed.
        
        For detached entities (only ID present), all attributes are compared
        and differences are updated before committing.
        
        :param entity: Modified entity instance
        :raises: EntityNotFoundError if entity doesn't exist
        :raises: EntityTypeMismatch if entity type doesn't match DAO's entity class
        :return: None
        """
        if isinstance(entity, self.get_entity_class()):
            try:
                existing_entity = self.find_by_id(entity.id)
                if existing_entity is None:
                    raise EntityNotFoundError(f"Entity with ID {entity.id} not found")
                
                # Update all non-private attributes that have changed
                for attribute_name in dir(existing_entity):
                    if not attribute_name.startswith('_'):
                        current_value = getattr(entity, attribute_name)
                        existing_value = getattr(existing_entity, attribute_name)
                        if current_value != existing_value:
                            setattr(existing_entity, attribute_name, current_value)
                
                self._commit()
            except Exception as error:
                raise error
        else:
            raise EntityTypeMismatch("Entity type doesn't match DAO's managed entity class")
    
    def _commit(self):
        """Internal method to commit the current session"""
        self.db_connector.get_session().commit()
    
    @abstractmethod
    def delete(self, entity):
        """
        Abstract method for deleting an entity
        :param entity: Entity instance to be deleted
        :return: None
        """
        pass

This implementation provides a clean, reusable foundation for database operations across different entity classes, leveraging Python's abstract base classes and SQLAlchemy's powerful ORM capabilities.

Tags: SQLAlchemy DAO python database ORM

Posted on Fri, 06 Nov 2026 16:29:13 +0000 by harsh00008