sqlalchemy 示例

正文开始前,不得不吐糟一下 ORM。

在很久很久之前,我是一个坚定的 ORM 黑,基于对数据库优化有点小研究,感觉这东西就是脱了裤子放 P,在带来性能下降的同时,带来的很多很重包依赖,项目中也在使用基本类似 SQL 语句的方案读写数据库。合作的人多了起来,直到有一天,我发现程序变成了这个样子:

class User(object):
    def get_by_id(self, id):
        ...
    def get_by_email(self, email):
        ...
    def get_all(self):
        ...

这个例子有点夸张,但总结一下,就是没有固定的标准,合作起来就容易错乱。

然后我要做的当然是整理下代码结构,不要这么乱来,结果越整理,发现越像 ORM,所以还是直接用 ORM 算了~


好了,正文开始。

几乎所有的例子都是从这个开始的:

class User(Base):
    __tablename__ = 'users'

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

但我觉得这种定义对我毫无意义:

首先,我的数据库是多个项目都需要读写的,我有一个单独的 alembic 项目负责维护数据库的变动,我没有理由要在其它项目需要的字段发生变动时,跑到这个项目里修改代码;

其次,这些值的类型在 alembic 里都定义过,数据库里都是可以取到的,我没有理由需要重新定义一遍,我应该只需要定义 alembic 里没有的,需要用到的外键。

在问过几个练过 sqlalchemy 的朋友之后,得到的结论是:我太懒了,写几个不会死人,要为懒付出代价。 = ____ =

很喜欢 Ruby 里的 Active Record ,它几乎是一个完美的框架,sqlalchemy 倒是有一个叫 Elixir (这货不是 Erlang VM 上的语言,只是同名而已。。。)的扩展,跟 Active Record 很像,但最近更新时间是三年之前。

不多说了,贴代码了,各种 Google + 文档 + 看源码 的总结:

1. 以下代码很大程度上参考了 flask-sqlalchemy 项目。

2. 以下代码只适合接入已有数据库,不创建和修改表结构

from sqlalchemy.orm.exc import UnmappedClassError
from sqlalchemy.ext.declarative import declared_attr, declarative_base
from sqlalchemy import create_engine

engine = create_engine('mysql://root:@127.0.0.1/db?charset=utf8', echo=True)
Session = sessionmaker(bind=engine)
Base = declarative_base()

__all__ = ['db']


class Base(object):
    @declared_attr
    def __table__(cls):
        return Table(cls.__tablename__, MetaData(), autoload=True, autoload_with=engine)


class _QueryProperty(object):
    def __init__(self, sa):
        self.sa = sa

    def __get__(self, obj, t):
        try:
            mapper = class_mapper(t)
            if mapper:
                return t.query_class(mapper, session=self.sa.session)
        except UnmappedClassError:
            return None


class MyDB(object):
    def __init__(self):
        self.Model = self.make_declarative_base()
        self.session = Session()

    def make_declarative_base(self):
        base = declarative_base(cls=Base)
        base.query = _QueryProperty(self)
        base.query_class = Query
        return base


db = MyDB()

address.py

import  db


class Address(db.Model):
    __tablename__ = 'addresses'

user.py

from sqlalchemy.orm import relationship, foreign
from address import Address
import db


class User(db.Model):
    __tablename__ = 'users'

    address = relationship('Address', primaryjoin='User.address_id == foreign(Address.id)', uselist=False)

接下来就可以在程序里

u = User.query.get(1)
u.address

这样就算是基本完成了,以后有时间会把 Entity 整理一下,越来越觉得,Grape 真是个了不起的 web 框架!