'Column Inheritance for Independent ORM Mapper Classes In SQL Alchemy

I am trying to replicate the following SQLite CREATE TABLE statements using the declarative ORM mapper classes in SQLALchemy.

CREATE TABLE IF NOT EXISTS question (
    id INTEGER NOT NULL PRIMARY KEY,
    title             TEXT NOT NULL,
    body              TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS solution (
    id INTEGER NOT NULL PRIMARY KEY,
    title               TEXT NOT NULL,
    body                TEXT NOT NULL,
    source_code         TEXT NOT NULL,
    runtime_complexity  TEXT NOT NULL,
    storage_complexity  TEXT NOT NULL,
    understood TEXT NOT NULL
);

Is there a way to specify inheritance in ORM mapper classes such that the question and solution tables remain independent (i.e. Querying against a subclass in the hierarchy should not render as an SQL JOIN along all tables in its inheritance path) while also reducing the amount of repeated code shown below? As indicated in the comments below, these mapper classes have some of the same columns with the same names and datatypes, and have the same __repr__ methods.

from sqlalchemy import Column, Sequence, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Question(Base):
    __tablename__ = 'question'

    # vvv Both Solution and Question classes have these columns 
    id = Column(Integer, Sequence('id_seq'), primary_key=True)
    title = Column(String, nullable=False)
    body = Column(Integer, nullable=False)
    # ^^^

    # vvv Both Solution and Question classes have this method
    def __repr__(self):
        class_name = self.__class__.__name__
        column_names = tuple(col.name for col in self.__table__.columns)
        return f"<{class_name}{column_names}>"
    # ^^^

    
    
   
class Solution(Base):
    __tablename__ = 'solution'

    # vvv same in Question class
    id = Column(Integer, Sequence('id_seq'), primary_key=True)
    title = Column(String, nullable=False)
    body = Column(Integer, nullable=False)
    # ^^^

    source_code = Column(Integer, nullable=False)
    runtime_complexity = Column(Integer, nullable=False)
    storage_complexity = Column(Integer, nullable=False)
    understood = Column(Integer, nullable=False)

    # vvv same in Question class
    def __repr__(self):
        class_name = self.__class__.__name__
        column_names = tuple(col.name for col in self.__table__.columns)
        return f"<{class_name}{column_names}>"
    # ^^^

From what I read in the Mapping Class Inheritance Hierarchies page in the SQLAlchemy docs, there are three methods of inheritance:

SQLAlchemy supports three forms of inheritance:

  1. single table inheritance, where several types of classes are represented by a single table.
  2. concrete table inheritance, where each type of class is represented by independent tables.
  3. joined table inheritance, where the class hierarchy is broken up among dependent tables, each class represented by its own table that only includes those attributes local to that class..

That section would lead me to assume that concrete table inheritance is what I need. However, under the concreate table inheritance section, it reads:

Concrete inheritance maps each subclass to its own distinct table, each of which contains all columns necessary to produce an instance of that class.

Here's what I'm trying to accomplish (roughly). Is something like this possible in SQLAlchemy?

from sqlalchemy import Column, Sequence, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class QS(Base): 
    # an abstract class that does NOT map to any table in the database. 
    # just holds column names and column data types,
    # along with methods common to question and solution mapper classes
    __abstract__ = True
    id = Column(Integer, Sequence('id_seq'), primary_key=True)
    title = Column(String, nullable=False)
    body = Column(Integer, nullable=False)

    def __repr__(self):
        class_name = self.__class__.__name__
        column_names = tuple(col.name for col in self.__table__.columns)
        return f"<{class_name}{column_names}>"

class Question(QS): 
    __tablename__ = 'question'
    # actually maps to table called question
    # which has columns id, title, body
 
class Solution(QS):
    __tablename__ = 'solution'
    # actually maps to table called solution,
    # which has columns id, title, body, source_code, runtime_complexity, and storage_complexity
    # (has a few more columns than the base class)

    source_code = Column(Integer, nullable=False)
    runtime_complexity = Column(Integer, nullable=False)
    storage_complexity = Column(Integer, nullable=False)
    understood = Column(Integer, nullable=False)


Solution 1:[1]

Thanks to @rfkortekaas' comment, which suggested the use of Mixin Classes, I was able to declare the parent class Document from which Question and Solution inherit.

from sqlalchemy import Column, String, Sequence, Integer

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import declarative_mixin
from sqlalchemy.orm import declared_attr

Base = declarative_base()

@declarative_mixin
class Document:

    def __repr__(self):
        return f"<{self.__class__.__name__}{self.__dict__}>"

    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

    id    = Column(Integer, Sequence('id_seq'), primary_key=True)
    title = Column(String, nullable=False)
    body  = Column(String, nullable=False)

class Question(Base, Document):
    pass

class Solution(Base, Document):
    source_code        = Column(String, nullable=False)
    runtime_complexity = Column(String, nullable=False)
    storage_complexity = Column(String, nullable=False)
    understood         = Column(String, nullable=False)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1