9

写了个基于SQLAlchemy的ORM - Limboy's HQ

 3 years ago
source link: https://limboy.me/2012/02/10/introduce-thing-an-sqlalchemy-based-orm/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

写了个基于SQLAlchemy的ORM

2012-02-10

看Rails时,觉得Rails的ORM用起来好方便,就想找找python有没有类似的,没发现太尽如人意的,就按照自己的意愿,基于SQLAlchemy Core重新写了个,取名为Thing,项目主页: “https://github.com/lzyy/thing":https://github.com/lzyy/thing

使用方便,灵活

支持事件触发

支持多数据库连接

不想把ORM做得太magic,将来优化起来会不太方便,所以只是简单地封装了下,既保证了使用起来比较方便,将来涉及到分库分表或缓存时也可以从容应付。

推荐使用virtualenvwrapper

mkvirtualenv thing
cdvirtualenv
pip install "git+git://github.com/lzyy/thing.git"

创建一个继承Thing的基类,主要是设置数据库连接

from sqlalchemy import create_engine
import thing

master_engine = create_engine('mysql://root:123456@localhost:3306/test')
slave_engine = create_engine('mysql://root:123456@localhost:3307/test')

class BaseThing(thing.Thing):
    def __init__(self):
        thing.Thing.__init__(self, {'master': master_engine, 
                                    'slave': slave_engine})

h5. 注意事项:

所有的模型类都要继承BaseThing

如果没有在子类里定义_tablename,则默认使用小写的子类名作为表名

表字段会被自动获取

假设有这么个场景:一个用户有多个答案,每个答案可以被多人投票。我们可以新建3个Model

import thing
from sqlalchemy import create_engine
from formencode import validators
from blinker import signal

vote_before_insert = signal('vote.before_insert')

class Member(BaseThing):
    # 验证email字段
    email = validators.Email(messages = {'noAt': u'invalid email'})

    @property
    def answers(self):
        return Answer().where('member_id', '=', self.id)

class Answer(BaseThing):
    @property
    def votes(self):
        return Vote().where('answer_id', '=', self.id)

    @vote_before_insert.connect
    def _vote_before_insert(vote, data):
        if vote.answer.title == 'test':
            vote.errors = {'answer': 'signal test'}

class Vote(BaseThing):
    @property
    def member(self):
        return Member().where('id', '=', self.member_id).find()

    @property
    def answer(self):
        return Answer().where('id', '=', self.answer_id).find()

用户与答案是一对多的关系,这里通过@property装饰器来实现,在answers方法内,可以很灵活地实现答案获取的方法。

在Answer模型里有一个vote_before_insert装饰器,在vote执行insert操作前_vote_before_insert方法会被触发,可以在这里做很多事,如缓存的处理,数据的验证等等。如果验证不通过,可以设置sender的errors属性,该属性一旦被设置,后续的操作将被中断,在这里vote就不会执行insert操作。

h5. 注意事项:

验证使用的是formencode,这个库支持很多的验证操作,“http://www.formencode.org/en/latest/Validator.html":http://www.formencode.org/en/latest/Validator.html

一共有6类事件:model.before_validation / after_validation / before_insert / after_insert / before_update / after_update

事件触发时第一个参数为model本身,第二个参数为数据,如果在某个事件响应函数处,设置了model.errors属性,则此次事件之后的代码都不会执行。

列出一个用户的id>10的所有回答,每次取10个

member = Member().find(1)

for answer in member.answers.where('id', '>', 10).findall(limit=10, offset=0):
    print answer.title

创建新用户

member = Member()
member.email = '[email protected]'
member.password = '123'
member.save()
print member.saved # True
print member.email # [email protected]

更新用户信息

member = Member().find(1)
member.email = '[email protected]'
member.save()
print member.saved # True
print member.email # [email protected]
member = Member()
member.password = '123'
member.email = 'foo'
member.save()
print member.errors['email'] # invalid email

多数据库连接

member = Member().find(1, 'slave')

在执行find / findall / save操作时,有一个db_section选项,如果忽略,则默认使用初始化时传入的engide dict的第一项,在这里就是master,如果想选择其他的数据库,传入该数据库对应的key就行,比如slave

查看某次插入或更新是否成功,可以检查errors属性,如果为空表示执行成功

如果model的key中包含主键,如id,则执行save时是一个更新操作,否则为插入

欢迎fork / test / feedback


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK