源码见廖雪峰github-day9,目前也只能是对源码进行理解,如果要自己写出来这些代码,还是很困难的。
一、基本结构
首先,是一个webapp最基本的结构
async def init():
await orm.create_pool(host='127.0.0.1', port=3306, user='root', password='', db='awesome')
app = web.Application(loop=loop, middlewares=[
logger_factory, response_factory
])
#加载模板,然后在middlewares的response_factory中被调用模板
init_jinja2(app, filters=dict(datetime=datetime_filter))
#添加路线add.router.add_route(method,path,handler),只不过在后面首先写了一个requesthandler类,接受app和handler参数
#然后从request中找出handler需要的参数,并传入到handler中,等待返回结果(其实就相当于handler(request)
#定义了一个add_route函数,执行add.router.add_route(method,path,handler);定义一个add_routes函数,对模块中所有符合条件的hanler函数,全部执行这个方法
add_routes(app, 'handlers')
add_static(app)
srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
logging.info('server started at http://127.0.0.1:8000...')
return srv
loop = asyncio.get_event_loop()
loop.run_until_complete(init())
loop.run_forever()
首先创建一个协程函数,内容具体可以分为以下几条:
1、启动数据库
这个web app需要用户注册登录等事项,所以要先实现连接数据库,这个靠await orm.create_pool(host='127.0.0.1', port=3306, user='root', password='', db='awesome')
来实现。
2、创建web.application对象
这个对象接收middleware参数,它是一个列表,列表里有一系列函数,能够在url处理函数之前进行事先处理,或者在url处理函数之后对输出进行整理。
3、加载响应模板
服务器一般会以html的形式响应浏览器的请求,init_jinja2(app, filters=dict(datetime=datetime_filter))
用来加载html的模板,模板中会用到一些静态文件css、js等,因此要加载静态文件,靠 add_static(app)
实现。
4、给web.application对象添加请求方法(get or post),请求路径url,处理函数hanler
一般是靠app.router.add_route(method, path, handler)来实现
,但是一个网站的处理函数和请求的url太多,不能靠这种人工方式来实现,因此将其集成到了一个add_routes函数中,能够遍历所有的url、请求方法和响应函数。
5、启动服务器srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
,里面参数包括函数app.make_handler(),本机的地址,还有响应端口
loop = asyncio.get_event_loop()
loop.run_until_complete(init())
loop.run_forever()
然后就是启动协程对象,并且运行协程对象,保证服务器始终是开着的,能够响应。
二、middleware处理函数
中间件处理函数主要有三个:
1、打印request的方法和路径
#定义middleware
#记录middleware,打印request方法和路径
async def logger_factory(app, handler):
async def logger(request):
logging.info('Request: %s %s' % (request.method, request.path))
# await asyncio.sleep(0.3)
return (await handler(request))
return logger
2、如果请求是post类型,打印请求的data
#记录middleware,打印request的data
async def data_factory(app, handler):
async def parse_data(request):
if request.method == 'POST':
if request.content_type.startswith('application/json'):
request.__data__ = await request.json()
logging.info('request json: %s' % str(request.__data__))
elif request.content_type.startswith('application/x-www-form-urlencoded'):
request.__data__ = await request.post()
logging.info('request form: %s' % str(request.__data__))
return (await handler(request))
return parse_data
```
3、等待处理函数对请求做出处理后,对处理的内容进行再加工处理,
- 但是具体这块儿内容我也没有太理解,这边挖一个小坑,就是关于request的内容是什么不太清楚,导致了后面处理函数返回的内容是什么不太懂,明天补坑
#返回middleware
async def response_factory(app, handler):
async def response(request):
logging.info('Response handler...')
r = await handler(request)
#本身即可返回的web.SteamResponse类型直接返回
if isinstance(r, web.StreamResponse):
return r
#bytes类型添加content_type后直接返回
if isinstance(r, bytes):
resp = web.Response(body=r)
resp.content_type = 'application/octet-stream'
return resp
#str类型分为两种,一种redict开头,返回后面的内容,一种用utf-8编码后返回
if isinstance(r, str):
if r.startswith('redirect:'):
return web.HTTPFound(r[9:])
resp = web.Response(body=r.encode('utf-8'))
resp.content_type = 'text/html;charset=utf-8'
return resp
#dict类型,如果有template选项,则加载模板后返回,如果没有则json编码后返回
if isinstance(r, dict):
template = r.get('__template__')
if template is None:
resp = web.Response(body=json.dumps(r, ensure_ascii=False, default=lambda o: o.__dict__).encode('utf-8'))
resp.content_type = 'application/json;charset=utf-8'
return resp
else:
resp = web.Response(body=app['__templating__'].get_template(template).render(**r).encode('utf-8'))
resp.content_type = 'text/html;charset=utf-8'
return resp
if isinstance(r, int) and r >= 100 and r < 600:
return web.Response(r)
if isinstance(r, tuple) and len(r) == 2:
t, m = r
if isinstance(t, int) and t >= 100 and t < 600:
return web.Response(t, str(m))
# default:
resp = web.Response(body=str(r).encode('utf-8'))
resp.content_type = 'text/plain;charset=utf-8'
return resp
return response
三、处理函数
day-9只有两个处理函数
1、前面的@get(’/’)是装饰器,加上这个就相当于执行了函数get(’/’)(index(request)),具体用法见廖雪峰装饰器教程,这个处理函数用于呈现主页面,他将blog里的内容返回,随后返回的内容进入中间件处理函数response_factory中,因为返回的是一个dict,所以经过对应的处理语句(见代码),最终返回给浏览器。其中__template__内容为对应的jinjia模板,blogs为模板的替换内容(具体见下面jinjia2_模板节)
@get('/')
def index(request):
summary = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
blogs = [
Blog(id='1', name='Test Blog', summary=summary, created_at=time.time()-120),
Blog(id='2', name='Something New', summary=summary, created_at=time.time()-3600),
]
return {
'__template__': 'blogs.html',
'blogs': blogs
}
#装饰器get,将函数的属性__method__定义为get,将函数的属性__route__定义为path
def get(path):
''' def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
return func(*args, **kw)
wrapper.__method__ = 'GET'
wrapper.__route__ = path
return wrapper
return decorator
#装饰器,原理如上
def post(path):
'''
Define decorator @post('/path')
'''
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
return func(*args, **kw)
wrapper.__method__ = 'POST'
wrapper.__route__ = path
return wrapper
return decorator
#中间件处理函数中对应的内容
if isinstance(r, dict):
template = r.get('__template__')
if template is None:
resp = web.Response(body=json.dumps(r, ensure_ascii=False, default=lambda o: o.__dict__).encode('utf-8'))
resp.content_type = 'application/json;charset=utf-8'
return resp
else:
resp = web.Response(body=app['__templating__'].get_template(template).render(**r).encode('utf-8'))
resp.content_type = 'text/html;charset=utf-8'
return resp
2、这个处理的路径为(‘/api/users’),找到所有数据库里储存的user内容,将password进行保密处理后,以dict的形式返回,经过response_factory进行json处理后返回给浏览器
@get('/api/users')
async def api_get_users():
users = await User.findAll(orderBy='created_at desc')
for u in users:
u.passwd = '******'
return dict(users=users)
四、添加请求方法、url和处理函数
- (留个小坑,等handler函数添加完全后,对add_routes函数进行进一步仔细分析)
这个都是通过add.router.add_route(method,path,handler)实现的。handler就是上面所提到的处理函数。对于处理函数,建立了一个类RequestHandler,这个类接受app参数和处理函数fn,它能够分析处理函数需要的参数,并调用处理函数进行处理,然后用__call__方法实现类似函数的输出功能,其实就是承担了处理函数的功能。
#定义一个RequestHandler类,接受app对象和函数fn,它能够调用以上关于函数参数的函数,并将其绑定到对应属性中
class RequestHandler(object):
def __init__(self, app, fn):
self._app = app
self._func = fn
self._has_request_arg = has_request_arg(fn)
self._has_var_kw_arg = has_var_kw_arg(fn)
self._has_named_kw_args = has_named_kw_args(fn)
self._named_kw_args = get_named_kw_args(fn)
self._required_kw_args = get_required_kw_args(fn)
async def __call__(self, request):
kw = None
if self._has_var_kw_arg or self._has_named_kw_args or self._required_kw_args:
if request.method == 'POST':
if not request.content_type:
return web.HTTPBadRequest('Missing Content-Type.')
ct = request.content_type.lower()
if ct.startswith('application/json'):
params = await request.json()
if not isinstance(params, dict):
return web.HTTPBadRequest('JSON body must be object.')
kw = params
elif ct.startswith('application/x-www-form-urlencoded') or ct.startswith('multipart/form-data'):
params = await request.post()
kw = dict(**params)
else:
return web.HTTPBadRequest('Unsupported Content-Type: %s' % request.content_type)
if request.method == 'GET':
qs = request.query_string
if qs:
kw = dict()
for k, v in parse.parse_qs(qs, True).items():
kw[k] = v[0]
if kw is None:
kw = dict(**request.match_info)
else:
if not self._has_var_kw_arg and self._named_kw_args:
# remove all unamed kw:
copy = dict()
for name in self._named_kw_args:
if name in kw:
copy[name] = kw[name]
kw = copy
# check named arg:
for k, v in request.match_info.items():
if k in kw:
logging.warning('Duplicate arg name in named arg and kw args: %s' % k)
kw[k] = v
if self._has_request_arg:
kw['request'] = request
# check required kw:
if self._required_kw_args:
for name in self._required_kw_args:
if not name in kw:
return web.HTTPBadRequest('Missing argument: %s' % name)
logging.info('call with args: %s' % str(kw))
try:
r = await self._func(**kw)
return r
except APIError as e:
return dict(error=e.error, data=e.data, message=e.message)
对于单个处理函数,可以用下面函数实现:
#添加路径
def add_route(app, fn):
#__method__和__route__属性被上面提到的@get和@post装饰器所添加
method = getattr(fn, '__method__', None)
path = getattr(fn, '__route__', None)
if path is None or method is None:
raise ValueError('@get or @post not defined in %s.' % str(fn))
if not asyncio.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn):
fn = asyncio.coroutine(fn)
logging.info('add route %s %s => %s(%s)' % (method, path, fn.__name__, ', '.join(inspect.signature(fn).parameters.keys())))
app.router.add_route(method, path, RequestHandler(app, fn))
而对于一个模块中的所有处理函数,如果一个个添加则过于复杂,所以用add_routes函数实现:
#把一个module里的所有文件路径添加
def add_routes(app, module_name):
n = module_name.rfind('.')
if n == (-1):
mod = __import__(module_name, globals(), locals())
else:
name = module_name[n+1:]
mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)
for attr in dir(mod):
if attr.startswith('_'):
continue
fn = getattr(mod, attr)
if callable(fn):
method = getattr(fn, '__method__', None)
path = getattr(fn, '__route__', None)
if method and path:
add_route(app, fn)
五、加载jinja2模板
前面提到中间处理函数的语句
else:
resp=web.Response(body=app['__templating__'].get_template(template).render(**r).encode('utf-8'))
其中这个语句就是通过加载jinja2模板init_jinja2(app,filters=dict(datetime=datetime_filter))
实现的。下面这个函数能够找到本地储存模板的文件并进行加载,然后赋予app[‘templating’]属性,能够随时被上面中间件函数的语句调用。接受web.application对象和一个filter字典为参数。这个filter字典里面的内容用于html的渲染,在html内容中会有体现
def init_jinja2(app, **kw):
logging.info('init jinja2...')
#加载模板时的各种参数
options = dict(
autoescape = kw.get('autoescape', True),
block_start_string = kw.get('block_start_string', '{%'),
block_end_string = kw.get('block_end_string', '%}'),
variable_start_string = kw.get('variable_start_string', '{{'),
variable_end_string = kw.get('variable_end_string', '}}'),
auto_reload = kw.get('auto_reload', True)
)
#寻找path,如果为None,则返回本文件夹下面的templates路径
path = kw.get('path', None)
if path is None:
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
logging.info('set jinja2 template path: %s' % path)
#从本地文件系统中加载path中的模板
env = Environment(loader=FileSystemLoader(path), **options)
#filter模板滤镜
filters = kw.get('filters', None)
if filters is not None:
for name, f in filters.items():
env.filters[name] = f
app['__templating__'] = env
这些加载的模板一般都是html文件,如下。html文件里面会用到静态的css,js等文件,因此要加载这些静态文件。关于html文件的仔细构成建议自行百度。
add_static(app)
def add_static(app):
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
app.router.add_static('/static/', path)
logging.info('add static %s => %s' % ('/static/', path))
<!DOCTYPE html>
<!--
{% macro pagination(url, page) %}
<ul class="uk-pagination">
{% if page.has_previous %}
<li><a href="{{ url }}{{ page.page_index - 1 }}"><i class="uk-icon-angle-double-left"></i></a></li>
{% else %}
<li class="uk-disabled"><span><i class="uk-icon-angle-double-left"></i></span></li>
{% endif %}
<li class="uk-active"><span>{{ page.page_index }}</span></li>
{% if page.has_next %}
<li><a href="{{ url }}{{ page.page_index + 1 }}"><i class="uk-icon-angle-double-right"></i></a></li>
{% else %}
<li class="uk-disabled"><span><i class="uk-icon-angle-double-right"></i></span></li>
{% endif %}
</ul>
{% endmacro %}
-->
<html>
<head>
<meta charset="utf-8" />
{% block meta %}<!-- block meta -->{% endblock %}
<title>{% block title %} ? {% endblock %} - Awesome Python Webapp</title>
<link rel="stylesheet" href="/static/css/uikit.min.css">
<link rel="stylesheet" href="/static/css/uikit.gradient.min.css">
<link rel="stylesheet" href="/static/css/awesome.css" />
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/sha1.min.js"></script>
<script src="/static/js/uikit.min.js"></script>
<script src="/static/js/sticky.min.js"></script>
<script src="/static/js/vue.min.js"></script>
<script src="/static/js/awesome.js"></script>
{% block beforehead %}<!-- before head -->{% endblock %}
</head>
<body>
<nav class="uk-navbar uk-navbar-attached uk-margin-bottom">
<div class="uk-container uk-container-center">
<a href="/" class="uk-navbar-brand">Awesome</a>
<ul class="uk-navbar-nav">
<li data-url="blogs"><a href="/"><i class="uk-icon-home"></i> 日志</a></li>
<li><a target="_blank" href="http://www.baidu.com"><i class="uk-icon-book"></i> 百度</a></li>
<li><a target="_blank" href="https://www.bilibili.com"><i class="uk-icon-code"></i> B站</a></li>
</ul>
<div class="uk-navbar-flip">
<ul class="uk-navbar-nav">
{% if __user__ %}
<li class="uk-parent" data-uk-dropdown>
<a href="#0"><i class="uk-icon-user"></i> {{ __user__.name }}</a>
<div class="uk-dropdown uk-dropdown-navbar">
<ul class="uk-nav uk-nav-navbar">
<li><a href="/signout"><i class="uk-icon-sign-out"></i> 登出</a></li>
</ul>
</div>
</li>
{% else %}
<li><a href="/signin"><i class="uk-icon-sign-in"></i> 登陆</a></li>
<li><a href="/register"><i class="uk-icon-edit"></i> 注册</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="uk-container uk-container-center">
<div class="uk-grid">
<!-- content -->
{% block content %}
{% endblock %}
<!-- // content -->
</div>
</div>
<div class="uk-margin-large-top" style="background-color:#eee; border-top:1px solid #ccc;">
<div class="uk-container uk-container-center uk-text-center">
<div class="uk-panel uk-margin-top uk-margin-bottom">
<p>
<a target="_blank" href="http://weibo.com/liaoxuefeng" class="uk-icon-button uk-icon-weibo"></a>
<a target="_blank" href="https://github.com/michaelliao" class="uk-icon-button uk-icon-github"></a>
<a target="_blank" href="http://www.linkedin.com/in/liaoxuefeng" class="uk-icon-button uk-icon-linkedin-square"></a>
<a target="_blank" href="https://twitter.com/liaoxuefeng" class="uk-icon-button uk-icon-twitter"></a>
</p>
<p>Powered by <a href="http://awesome.liaoxuefeng.com">Awesome Python Webapp</a>. Copyright © 2014. [<a href="/manage/" target="_blank">Manage</a>]</p>
<p><a href="http://www.liaoxuefeng.com/" target="_blank">www.liaoxuefeng.com</a>. All rights reserved.</p>
<a target="_blank" href="http://www.w3.org/TR/html5/"><i class="uk-icon-html5" style="font-size:64px; color: #444;"></i></a>
</div>
</div>
</div>
</body>
</html>
六、 关于数据库
其中主要用到的就是ORM,关于ORM可以看我的上一篇博客。
import asyncio, logging
import aiomysql
def log(sql, args=()):
logging.info('SQL: %s' % sql)
#创建连接池,这个用于app.py里的启动数据库
async def create_pool( **kw):
logging.info('create database connection pool...')
global __pool
__pool = await aiomysql.create_pool(
host=kw.get('host', 'localhost'),
port=kw.get('port', 3306),
user=kw['user'],
password=kw['password'],
db=kw['db'],
charset=kw.get('charset', 'utf8'),
autocommit=kw.get('autocommit', True),
maxsize=kw.get('maxsize', 10),
minsize=kw.get('minsize', 1),
)
#创建select函数,接收一个sql语句参数,和一个关键字列表,函数里面会执行数据库的selec语句,后面用于Model里面执行find操作
async def select(sql, args, size=None):
log(sql, args)
global __pool
async with __pool.get() as conn:
async with conn.cursor(aiomysql.DictCursor) as cur:
await cur.execute(sql.replace('?', '%s'), args or ())
if size:
rs = await cur.fetchmany(size)
else:
rs = await cur.fetchall()
logging.info('rows returned: %s' % len(rs))
return rs
#创建execute函数,l类似于上面的select函数,但是由于不需要返回数据库里的具体内容,只是返回数据库中被影响的函数,用于model里面执行insert,update等操作
async def execute(sql, args, autocommit=True):
log(sql)
async with __pool.get() as conn:
if not autocommit:
await conn.begin()
try:
async with conn.cursor(aiomysql.DictCursor) as cur:
await cur.execute(sql.replace('?', '%s'), args)
affected = cur.rowcount
if not autocommit:
await conn.commit()
except BaseException as e:
if not autocommit:
await conn.rollback()
raise
return affected
#创建函数,用于后面insert操作里面的占位符
def create_args_string(num):
L = []
for n in range(num):
L.append('?')
return ', '.join(L)
#创建Field类,用于metaclass和实例里面属性类型
class Field(object):
def __init__(self, name, column_type, primary_key, default):
self.name = name
self.column_type = column_type
self.primary_key = primary_key
self.default = default
def __str__(self):
return '<%s, %s:%s>' % (self.__class__.__name__, self.column_type, self.name)
class StringField(Field):
def __init__(self, name=None, primary_key=False, default=None, ddl='varchar(100)'):
super().__init__(name, ddl, primary_key, default)
class BooleanField(Field):
def __init__(self, name=None, default=False):
super().__init__(name, 'boolean', False, default)
class IntegerField(Field):
def __init__(self, name=None, primary_key=False, default=0):
super().__init__(name, 'bigint', primary_key, default)
class FloatField(Field):
def __init__(self, name=None, primary_key=False, default=0.0):
super().__init__(name, 'real', primary_key, default)
class TextField(Field):
def __init__(self, name=None, default=None):
super().__init__(name, 'text', False, default)
#创建ModelMetaclass,用于创建实例
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
#不对model进行任何处理
if name=='Model':
return type.__new__(cls, name, bases, attrs)
tableName = attrs.get('__table__', None) or name
logging.info('found model: %s (table: %s)' % (name, tableName))
#建立mappings字典和fileds字典
mappings = dict()
fields = []
primaryKey = None
for k, v in attrs.items():
#可以看出,mappings储存实例里所有符合Field类型的属性字典。field储存所有非主键的属性列表
if isinstance(v, Field):
logging.info(' found mapping: %s ==> %s' % (k, v))
mappings[k] = v
if v.primary_key:
# 找到主键:
if primaryKey:
raise Exception ('Duplicate primary key for field: %s' % k)
primaryKey = k
else:
fields.append(k)
if not primaryKey:
raise Exception ('Primary key not found.')
#删除类属性
for k in mappings.keys():
attrs.pop(k)
- [ ] #老实讲我不知道这里将%s替换为`%s`有什么意义
escaped_fields = list(map(lambda f: '`%s`' % f, fields))
#赋予s实例所有以下属性,这些属性一般与model里面的类方法有关
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = tableName
attrs['__primary_key__'] = primaryKey # 主键属性名
attrs['__fields__'] = fields # 除主键外的属性名
attrs['__select__'] = 'select `%s`, %s from `%s`' % (primaryKey, ', '.join(escaped_fields), tableName)
attrs['__insert__'] = 'insert into `%s` (%s, `%s`) values (%s)' % (tableName, ', '.join(escaped_fields), primaryKey, create_args_string(len(escaped_fields) + 1))
attrs['__update__'] = 'update `%s` set %s where `%s`=?' % (tableName, ', '.join(map(lambda f: '`%s`=?' % (mappings.get(f).name or f), fields)), primaryKey)
attrs['__delete__'] = 'delete from `%s` where `%s`=?' % (tableName, primaryKey)
return type.__new__(cls, name, bases, attrs)
#创建实例的模板,给实例增加各种方法,save,update,remove,find_all,这些类方法会调用上面提到的select等函数方法,将上面提到的实例的属性值作为参数输入到select函数中,经过其处理为可以执行的sql语句,然后对数据库进行操作
class Model(dict, metaclass=ModelMetaclass):
def __init__(self, **kw):
super(Model, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
def getValue(self, key):
return getattr(self, key, None)
def getValueOrDefault(self, key):
value = getattr(self, key, None)
if value is None:
field = self.__mappings__[key]
if field.default is not None:
value = field.default() if callable(field.default) else field.default
logging.debug('using default value for %s: %s' % (key, str(value)))
setattr(self, key, value)
return val
@classmethod
async def findAll(cls, where=None, args=None, **kw):
' find objects by where clause. '
sql = [cls.__select__]
if where:
sql.append('where')
sql.append(where)
if args is None:
args = []
orderBy = kw.get('orderBy', None)
if orderBy:
sql.append('order by')
sql.append(orderBy)
limit = kw.get('limit', None)
if limit is not None:
sql.append('limit')
if isinstance(limit, int):
sql.append('?')
args.append(limit)
elif isinstance(limit, tuple) and len(limit) == 2:
sql.append('?, ?')
args.extend(limit)
else:
raise ValueError('Invalid limit value: %s' % str(limit))
rs = await select(' '.join(sql), args)
return [cls(**r) for r in rs]
@classmethod
async def findNumber(cls, selectField, where=None, args=None):
' find number by select and where. '
sql = ['select %s _num_ from `%s`' % (selectField, cls.__table__)]
if where:
sql.append('where')
sql.append(where)
rs = await select(' '.join(sql), args, 1)
if len(rs) == 0:
return None
return rs[0]['_num_']
@classmethod
async def find(cls, pk):
' find object by primary key. '
rs = await select('%s where `%s`=?' % (cls.__select__, cls.__primary_key__), [pk], 1)
if len(rs) == 0:
return None
return cls(**rs[0])
async def save(self):
args = list(map(self.getValueOrDefault, self.__fields__))
args.append(self.getValueOrDefault(self.__primary_key__))
rows = await execute(self.__insert__, args)
if rows != 1:
logging.warn('failed to insert record: affected rows: %s' % rows)
async def update(self):
args = list(map(self.getValue, self.__fields__))
args.append(self.getValue(self.__primary_key__))
rows = await execute(self.__update__, args)
if rows != 1:
logging.warn('failed to update by primary key: affected rows: %s' % rows)
async def remove(self):
args = [self.getValue(self.__primary_key__)]
rows = await execute(self.__delete__, args)
if rows != 1:
logging.warning('failed to remove by primary key: affected rows: %s' % rows)`
然后定义三个实例,通过数据库客户端执行脚本后写入数据库
import time, uuid
from orm import Model, StringField, BooleanField, FloatField, TextField
def next_id():
return '%015d%s000' % (int(time.time() * 1000), uuid.uuid4().hex)
class User(Model):
__table__ = 'users'
id = StringField(primary_key=True, default=next_id, ddl='varchar(50)')
email = StringField(ddl='varchar(50)')
passwd = StringField(ddl='varchar(50)')
admin = BooleanField()
name = StringField(ddl='varchar(50)')
image = StringField(ddl='varchar(500)')
created_at = FloatField(default=time.time)
class Blog(Model):
__table__ = 'blogs'
id = StringField(primary_key=True, default=next_id, ddl='varchar(50)')
user_id = StringField(ddl='varchar(50)')
user_name = StringField(ddl='varchar(50)')
user_image = StringField(ddl='varchar(500)')
name = StringField(ddl='varchar(50)')
summary = StringField(ddl='varchar(200)')
content = TextField()
created_at = FloatField(default=time.time)
class Comment(Model):
__table__ = 'comments'
id = StringField(primary_key=True, default=next_id, ddl='varchar(50)')
blog_id = StringField(ddl='varchar(50)')
user_id = StringField(ddl='varchar(50)')
user_name = StringField(ddl='varchar(50)')
user_image = StringField(ddl='varchar(500)')
content = TextField()
created_at = FloatField(default=time.time)
下面的代码就可以通过ORM对数据库进行操作。这里必须要说明一点,上面三个model写入数据库建立表格并不是通过Python,而是直接在数据库里通过脚本实现的。这里三个model只对下面从Python进行数据库操作有用。
import orm
from models import User, Blog, Comment
import asyncio
from aiohttp import web
#创建一个写入协程函数
async def test():
#连接数据库
await orm.create_pool(user='root', password='', db='awesome')
#创建一个User对象,因为User是以Model为原型,经过metaclass处理的类,因此拥有model的类方法,metaclass赋予的类属性。传入一个dict,因为User也是dict的子类,所以就会有User[id]='Test1'这样的字典属性。执行这个语句的时候,类属性经过metaclass之后被删除干净,但同时也被赋予其他的类属性例如__fields__。因此再调用model里面的类方法时,需要的args参数里的就是实例属性
u = User(name='Test1', email='test1@example.com', passwd='1234567890', image='about:blank')
#执行save方法,save方法中具体提到了args的参数来源就是传入的字典
await u.save()
#执行协程
loop = asyncio.get_event_loop()
loop.run_until_complete(test())