SQLAlchemy: избегать повторения в определении класса декларативного стиля

Я использую SQLAlchemy, и многие классы в моей объектной модели имеют одинаковые два атрибута: id и (целочисленный и первичный ключ) и имя (строка). Я пытаюсь избежать объявления их в каждом классе, например:

class C1(declarative_base()):
    id = Column(Integer, primary_key = True)
    name = Column(String)
    #...

class C2(declarative_base()):
    id = Column(Integer, primary_key = True)
    name = Column(String)
    #...

Какой хороший способ сделать это? Я попытался использовать метаклассы, но он пока не работает.

Ответ 1

Вы можете отчислить свои общие атрибуты в класс mixin и умножить наследование вместе с declarative_base():

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

class IdNameMixin(object):
    id = Column(Integer, primary_key=True)
    name = Column(String)

class C1(declarative_base(), IdNameMixin):
    __tablename__ = 'C1'

class C2(declarative_base(), IdNameMixin):
    __tablename__ = 'C2'

print C1.__dict__['id'] is C2.__dict__['id']
print C1.__dict__['name'] is C2.__dict__['name']

EDIT. Возможно, вы думаете, что это приведет к тому, что C1 и C2 будут использовать одни и те же объекты Column, но, как отмечено в SQLAlchemy docs, объекты столбца копируются при создании из класса mixin. Я обновил образец кода, чтобы продемонстрировать это поведение.

Ответ 2

Вы также можете использовать метод копирования столбцов? Таким образом, поля могут быть определены независимо от таблиц, а те поля, которые используются повторно, - это просто field.copy() - ed.

id = Column(Integer, primary_key = True)
name = Column(String)

class C1(declarative_base()):
    id = id.copy()
    name = name.copy()
    #...

class C2(declarative_base()):
    id = id.copy()
    name = name.copy()
    #...

Ответ 3

Я думаю, что у меня это получилось.

Я создал метакласс, который происходит от DeclarativeMeta, и сделал это метаклассом C1 и C2. В этом новом метаклассе я просто сказал

def __new__(mcs, name, base, attr):
  attr['__tablename__'] = name.lower()
  attr['id'] = Column(Integer, primary_key = True)
  attr['name'] = Column(String)
  return super().__new__(mcs, name, base, attr)

И это работает нормально.