上篇文章介绍了怎么写一个简单的服务器。接下来开始拓展一些基本功能。
数据库交互是十分常见的,今天我们就介绍一下怎么连接数据库--以mysql为例。
注意:此篇代码版本要求Python3.6以上,aiohttp 3.1以上(最新是3.4)。
拆分文件
由于数据库连接又需要配置地址密码等,最好放进一个配置文件中,我们先拆分出几个文件像这样:
配置文件conf.json专门放置诸如数据库连接配置等常数,
db.py是连接数据库的代码,
settings.py读取conf.json的配置,
views.py是各种url对应的视图函数。
配置文件在我们的项目中可以写成这样:
省略号部分填上自己的数据库表名,用户名,密码和地址。
update_interval暂时不用管是什么。
然后我们在settings.py读取配置:
数据共享
aiohttp.web给Application和request,response实现了一个collections.abc.MutableMapping接口,简单来说,就变成了类似dict的对象,以Application为例,可以这样存储数据:
from settings import config
app['config'] = config
即使在views.py中,也可以通过request.app读取:
打开http://127.0.0.1:8080/conf,就会看到:
当然,这只是个例子,实际可别这样暴露自己的配置文件。
读取了配置文件后,就要开始连接数据库了。
由于应该在开始运行时连接数据库,在程序停止运行时关闭连接,就得用到信号处理程序。
信号
信号处理程序没有返回值,可以修改传入参数。
通过按顺序订阅Application.on_startup和Application.on_cleanup信号可以依序设置Application的组件并拆除。
从aiohttp 3.0起,信号程序必须是协程,也就是异步的。
所以我们连接mysql需要用到一个异步连接库--aiomysql。基本语法和pymysql相似,就不再介绍了。
以下代码可以正确初始化一个aiomysql连接池:
但这段代码还是会有一个问题:
由于Application.on_startup和Application.on_cleanup彼此独立,互相不知道彼此的状态,结果就算前者发生异常,还是会执行后者的清理代码。
因此,实际上应该用Application.cleanup_ctx,代码改成这样:
然后在main.py中添加一行:
app.cleanup_ctx.append(mysql_engine)
Application.cleanup_ctx是一个存储异步的生成器的列表。不知道什么是异步的生成器?没关系,只要知道存储的是这个名词,不影响下面的理解。
Python3.6起才支持异步的生成器。Python3.5需要pip install async_generator。
在aiohttp的官方文档中这样介绍的:
每个异步生成器的代码中以yield分界,之前的代码在初始化时运行,相当于Application.on_startup部分,之后的代码执行清理部分,相当于Application.on_cleanup。
每个异步生成器只能有1个yield。
aiohttp可以保证清理部分只在初始化成功后才运行。
这样,我们就成功连接了mysql数据库,
假设有一个名为crawl_result的表,至少有2个字段id和url,
那么在views.py中可以这样执行sql,以获取数据库crawl_result表中id对应的url:
注意aiomysql的语法,先获取一个连接conn,再获取游标cur,之后基本上就是在处理cur有关的代码行前加个await,其他与pymysql一致。
运行一下:
这样就获取了表中id是8的url字段。
aiohttp连接数据库的基本操作就是如此了。
源码分析
上面说到,aiohttp可以保证Application.cleanup_ctx的清理部分代码一定在初始化成功后才执行,那么这是怎么实现的呢?
来看对应的源码部分:
首先,至少需要知道生成器的相关知识,不知道异步生成器没关系,下面以这个前提来简要分析一下:
__aiter__和__anext__其实就是__iter__和__next__的异步版本。
self就是Application.cleanup_ctx这个list,for cb in self是在循环这个列表。
it = cb(app).__aiter__()从当前生成器获取了一个迭代器it,之后await it.__anext__()让代码运行到yield部分,接着self.exits存储运行到yield部分的这个迭代器,_on_startup这部分结束。
_on_cleanup方法中,把self._exits反转后执行for循环,从末尾的异步生成器开始向前,每个生成器执行yield之后的清理程序。并且有一些诸如保证每个异步生成器只能有1个yield的异常处理代码。
可以看出,假如_on_startup部分最后一行之前的代码抛出异常,self.exits就不会存储数据,于是_on_cleanup里不会执行抛出异常的生成器中清理部分的代码。
这样,aiohttp就保证了yield之后的清理代码只会在初始化成功后运行。
今天的全部代码可以从https://github.com/lucays/toutiao/tree/master/16获取。
记得把conf.json中的省略号替换成自己数据库对应的配置噢。