ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。
要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。在Python中类的动态定义可以由元类来实现。
元类
type
type函数不仅可以查看一个类型或变量的类型,type也能动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。
type可以像这样工作:
type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
比如下面的代码:
1  | class Hello(object):  | 
可以手动像这样创建:
1  | def fn(self, name='world'):  | 
可以看到使用type函数创建的类和直接写class是完全一样的,因为type就是Python在背后用来创建所有类的元类。Python解释器在遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
metaclass
除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
下面的代码使用元类将模块里所有的类的属性都修改为大写形式:
1  | 
  | 
有了UpperAttrMetaClass,在定义类的时候还要指示使用UpperAttrMetaClass来定制类,传入关键字参数metaclass:
1  | class Foo(object, metaclass=UpperAttrMetaClass):  | 
当传入关键字参数metaclass时,它指示Python解释器在创建Foo类时,要通过ListMetaclass.__new__()来创建,在此,将类的属性都修改为大写形式然后,返回修改后的定义。
__new__()方法接收到的参数依次是:
当前准备创建的类的对象;
类的名字;
类继承的父类集合;
类的属性集合。
1  | print(hasattr(Foo, 'bar'))  | 
ORM
设计ORM需要从上层调用者角度来设计,如:定义一个User类来操作对应的数据库表User,我们期待他写出这样的代码:
1  | class User(Model):  | 
在User类中的__table__、id和name是类的属性,不是实例的属性。所以,在类级别上定义的属性用来描述User对象和表的映射关系,而实例属性必须通过__init__()方法去初始化,所以两者互不干扰.
定义Field类
Field类,它负责保存数据库表的字段名和字段类型:
1  | class Field(object):  | 
StringField、FloatField
映射varchar的StringField,映射rear的FloatField:
1  | class StringField(Field):  | 
在Field的基础上,进一步定义各种类型的Field,比如IntegerField等等。
ModelMetaclass
ModelMetaclass用于保存子类如User的映射信息:
1  | def create_args_string(num):  | 
在ModelMetaclass中,一共做了几件事情:
排除掉对
Model类的修改把表名保存到
__table__中,如果当前类没有定义__table__属性,则使用类名作为表名在当前类(比如
User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中,防止主键属性重复;从类属性中删除该
Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性)__select__、__insert__、__update__等,对应于数据表的SQL操作
定义Model
当用户定义一个class User(Model)时,Python解释器首先在当前类User的定义中查找metaclass,如果没有找到,就继续在父类Model中查找metaclass,找到了,就使用Model中定义的metaclass的ModelMetaclass来创建User类,也就是说,metaclass可以隐式地继承到子类。
1  | class Model(dict, metaclass=ModelMetaClass):  | 
Model从dict继承,所以具备所有dict的功能,同时又实现了特殊方法__getattr__()和__setattr__(),因此又可以像引用普通字段那样写:
>>> user['id']
123
>>> user.id
123
Model类定义了一个save的实例方法,可以把一个User实例存入数据库,save方法使用的异步IO来操作数据库:
1  | user = User(email='test@test.com', passwd='123456', name='test')  | 
get_value_or_default函数获得对应属性的值,如果对应属性的值为None,则取__mappings__里的默认值,如果default属性是callable,则取出调用后的值。如User类的id属性为default=next_id。
创建连接池
上面的save方法使用的是异步IO来操作数据库,在Python中,aiomysql为MySQL数据库提供了异步IO的驱动。
下面的代码创建了一个连接池,由全局变量__pool存储:
1  | async def create_pool(loop, **kw):  | 
执行INSERT语句
上面的Model类的save的实例方法,可以把一个实例对象保存到数据库中,由excute函数执行insert语句来完成。
1  | 
  | 
编写Model
创建一个users表对应的Model:
1  | import time  | 
初始化数据库表
1  | drop database if exists test;  | 
把SQL脚本放到MySQL命令行里执行:
mysql -u root -p < schema.sql
可以在数据库中看到,创建了一个test数据库,并创建类一个users表,users表中字段对应于User类的各个属性:
1  | mysql> USE test;  | 
编写数据访问代码
接下来,就可以真正开始编写代码操作对象了。比如,对于User对象,我们就可以做如下操作:
1  | import asyncio  | 
执行成功后,可以到如下log:
INFO:root:found model: User (table: users)
INFO:root:  found mapping: passwd ==> <StringField, varchar(50):None>
INFO:root:  found mapping: id ==> <StringField, varchar(50):None>
INFO:root:  found mapping: email ==> <StringField, varchar(50):None>
INFO:root:  found mapping: name ==> <StringField, varchar(50):None>
INFO:root:  found mapping: created_at ==> <FloatField, real:None>
INFO:root:create database connection pool...
INFO:root:SQL: insert into `users` (`passwd`, `email`, `name`, `created_at`, `id`) values (?, ?, ?, ?, ?)
INFO:root:SQL: insert into `users` (`passwd`, `email`, `name`, `created_at`, `id`) values (?, ?, ?, ?, ?)
INFO:root:SQL: insert into `users` (`passwd`, `email`, `name`, `created_at`, `id`) values (?, ?, ?, ?, ?)
INFO:root:SQL: insert into `users` (`passwd`, `email`, `name`, `created_at`, `id`) values (?, ?, ?, ?, ?)
查看数据库,可以看到数据插入成功:
1  | mysql> SELECT * FROM users;  | 
SELECT语句
根据主键查找
在Model类中定义一个find的类方法,find函数根据传入的主键执行select函数,返回相应的记录。
1  | class Model(dict, metaclass=ModelMetaClass):  | 
select函数
select函数从数据库查找相应的记录:
1  | async def select(sql, args, size=None):  | 
执行下面find方法的调用:
1  | async def test(loop):  | 
输出如下:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `name`, `created_at`, `email`, `passwd` from `users` where `id`=?
INFO:root:rows returned: 1
{'created_at': 1527820029.53404, 'id': '001527820029534e0c48803c8cc4c44a278d1491190a069000', 'email': 'test1@test.com', 'passwd': '123456', 'name': 'test1'}
根据where、order by、limit查找
1  | class Model(dict, metaclass=ModelMetaClass):  | 
默认查找数据表中的所有记录,如下代码:
1  | async def test(loop):  | 
输出如下结果:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `email`, `passwd`, `created_at`, `name` from `users`
INFO:root:rows returned: 4
[{'id': '001527820029534e0c48803c8cc4c44a278d1491190a069000', 'email': 'test1@test.com', 'passwd': '123456', 'created_at': 1527820029.53404, 'name': 'test1'}, {'id': '0015278200295492bdd1c9ba409444eb35211844501a663000', 'email': 'test2@test.com', 'passwd': '123456', 'created_at': 1527820029.54944, 'name': 'test2'}, {'id': '0015278200295619ab4469834ec4b3290a460d079c91f3b000', 'email': 'test3@test.com', 'passwd': '123456', 'created_at': 1527820029.56111, 'name': 'test3'}, {'id': '0015278200295635ed7e1100f294efb8b6ef8f57dbd7188000', 'email': 'test4@test.com', 'passwd': '123456', 'created_at': 1527820029.56394, 'name': 'test2'}]
根据where条件查找,如下代码:
1  | async def test(loop):  | 
输出如下结果:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `created_at`, `email`, `passwd`, `name` from `users` where name=?
INFO:root:rows returned: 2
[{'created_at': 1527820029.54944, 'name': 'test2', 'email': 'test2@test.com', 'passwd': '123456', 'id': '0015278200295492bdd1c9ba409444eb35211844501a663000'}, {'created_at': 1527820029.56394, 'name': 'test2', 'email': 'test4@test.com', 'passwd': '123456', 'id': '0015278200295635ed7e1100f294efb8b6ef8f57dbd7188000'}]
使用limit,如下代码:
1  | async def test(loop):  | 
输出如下结果:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `passwd`, `email`, `created_at`, `name` from `users` limit ?, ?
INFO:root:rows returned: 2
[{'passwd': '123456', 'email': 'test2@test.com', 'name': 'test2', 'created_at': 1527820029.54944, 'id': '0015278200295492bdd1c9ba409444eb35211844501a663000'}, {'passwd': '123456', 'email': 'test3@test.com', 'name': 'test3', 'created_at': 1527820029.56111, 'id': '0015278200295619ab4469834ec4b3290a460d079c91f3b000'}]
使用where、limit查找,如下代码:
1  | async def test(loop):  | 
输出如下结果:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `passwd`, `created_at`, `name`, `email` from `users` where name=? limit ?
INFO:root:rows returned: 1
[{'name': 'test2', 'email': 'test2@test.com', 'id': '0015278200295492bdd1c9ba409444eb35211844501a663000', 'passwd': '123456', 'created_at': 1527820029.54944}]
使用order by,如下代码:
1  | async def test(loop):  | 
输出如下结果:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `created_at`, `name`, `email`, `passwd` from `users` order by name
INFO:root:rows returned: 4
[{'passwd': '123456', 'created_at': 1527820029.53404, 'id': '001527820029534e0c48803c8cc4c44a278d1491190a069000', 'name': 'test1', 'email': 'test1@test.com'}, {'passwd': '123456', 'created_at': 1527820029.54944, 'id': '0015278200295492bdd1c9ba409444eb35211844501a663000', 'name': 'test2', 'email': 'test2@test.com'}, {'passwd': '123456', 'created_at': 1527820029.56394, 'id': '0015278200295635ed7e1100f294efb8b6ef8f57dbd7188000', 'name': 'test2', 'email': 'test4@test.com'}, {'passwd': '123456', 'created_at': 1527820029.56111, 'id': '0015278200295619ab4469834ec4b3290a460d079c91f3b000', 'name': 'test3', 'email': 'test3@test.com'}]
其他SQL语句
上面实现了数据库的insert、select操作,还可以自行定义其他SQL的执行操作,如uptate、delete,代码实现如下:
1  | class Model(dict, metaclass=ModelMetaClass):  |