max
max

Reputation: 52273

SQLAlchemy: avoiding repetition in declarative style class definition

I'm using SQLAlchemy, and many classes in my object model have the same two attributes: id and (integer & primary key), and name (a string). I'm trying to avoid declaring them in every class like so:

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)
    #...

What's a good way to do that? I tried using metaclasses but it didn't work yet.

Upvotes: 12

Views: 2148

Answers (3)

dhaffey
dhaffey

Reputation: 1374

You could factor out your common attributes into a mixin class, and multiply inherit it alongside 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: You might think this would result in C1 and C2 sharing the same Column objects, but as noted in the SQLAlchemy docs, Column objects are copied when originating from a mixin class. I've updated the code sample to demonstrate this behavior.

Upvotes: 14

jkmacc
jkmacc

Reputation: 6427

Could you also use the Column's copy method? This way, fields can be defined independently of tables, and those fields that are reused are just 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()
    #...

Upvotes: 2

max
max

Reputation: 52273

I think I got it to work.

I created a metaclass that derives from DeclarativeMeta, and made that the metaclass of C1 and C2. In that new metaclass, I simply said

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)

And it seems to work fine.

Upvotes: 1

Related Questions