Tales of a lazy developer
Posted on Sun 22 January 2017 in programming
Some say laziness makes a good developer. I don't know if that's always true.
Anyhow, just like everybody else, I hate typing, so I've created some helpers for sqlalchemy to get rid of the boring stuff in creating ORM-Classes
import sqlalchemy.orm.exc
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import relationship, backref
def pluralize(word):
if word.lower()[-1] == 'y':
return word[0:-1] + 'ies'
elif word.lower()[-2:] == 'is':
return word[0:-2] + 'es'
elif word.lower()[-1] != 's':
return word + "s"
else:
return word
def references(clsname, colname=None, primary=False, nullable=True, join_path=None, backrefs=True):
if colname is None:
colname = clsname.lower()
@declared_attr
def column(cls):
return Column(colname,
ForeignKey(pluralize(clsname) + '.id', ondelete='RESTRICT', onupdate='CASCADE'),
primary_key=primary, nullable=nullable)
if backrefs:
@declared_attr
def relation(cls):
return relationship(clsname, backref=backref(pluralize(cls.__name__.lower())), foreign_keys=join_path)
else:
@declared_attr
def relation(cls):
return relationship(clsname, foreign_keys=join_path)
methods = {colname + '_id': column,
colname: relation}
return type('Ref'+clsname+'Mixin', (object,), methods)
def selfreferences():
@declared_attr
def column(cls):
return Column('parent',
ForeignKey(pluralize(cls.__name__) + '.id', ondelete='RESTRICT', onupdate='CASCADE'))
@declared_attr
def children(cls):
return relationship(cls.__name__, backref=backref('parent', remote_side=[cls.id]), post_update=True)
return type('SelfrefMixin', (object,), {'parent_id': column,
'children': children})
class TableNameMixin:
""" Mixin for defining the Tablename
for the implementing ORM-Class
"""
@declared_attr
def __tablename__(cls):
return pluralize(cls.__name__)
class PrimaryTableMixin(TableNameMixin):
""" Mixin for Tables with a Primary key
adds a column "id" which will be
the primary key for the Table
"""
id = Column(Integer, primary_key=True, autoincrement=True)
class CodedTableMixin:
""" Mixin for Tables with a human "readable" code as unique """
code = Column(String, unique=True, nullable=False)
@classmethod
def get(cls, code):
try:
return cls.query.filter(cls.code == code).one()
except sqlalchemy.orm.exc.NoResultFound:
raise sqlalchemy.orm.exc.NoResultFound('No "{cls}" found with code "{code}"'.format(cls=cls.__name__,
code=code))
This allows for some nice code like:
class Field(PrimaryTableMixin, Base):
pass
class Sender(PrimaryTableMixin, Base):
pass
class SenderField(PrimaryTableMixin,
references('Sender', nullable=False),
references('Field', nullable=False),
Base):
pass
which would easily some 20 line monster if done the intended way:
class Field(Base):
__tablename__ = 'Fields'
id = Column(Integer, primary_key=True, autoincrement=True)
class Sender(Base):
__tablename__ = 'Senders'
id = Column(Integer, primary_key=True, autoincrement=True)
class SenderField(Base):
__tablename__ = 'SenderFields'
id = Column(Integer, primary_key=True, autoincrement=True)
sender_id = Column('sender', ForeignKey('Senders.id', ondelete='RESTRICT', onupdate='CASCADE'), primary_key=primary, nullable=nullable)
field_id = Column('field', ForeignKey('Fields.id', ondelete='RESTRICT', onupdate='CASCADE'), primary_key=primary, nullable=nullable)
sender = relationship(Sender, backref=backref('senderfields'))
field = relationship(Fields, backref=backref('senderfields'))