廖雪峰webAPP实战——Day1-8总结
大家可能都卡在day5, 其中的web的框架真是让头疼,day4 还不容易搞明白了orm,没想到day5 还更加难,多了3个py文件,可以说是4个,其中app.py都差不多全改了。。。
服务端和客户端
下面是关于服务端和客户端的函数,也就是app.py和 coroweb.py的交互。
init(loop)
我们跟着代码跑一遍,程序开头是在app.py的 init(loop)函数。
#app.py
async def init(loop):
await orm.create_pool(loop=loop, host='127.0.0.1', port=3306, user='www-data', password='kx123456', db='awesome')
app = web.Application(middlewares=[logger_factory, response_factory])#去掉loop = loop,loop参数弃用
init_jinja2(app, filters=dict(datetime=datetime_filter))
add_routes(app, 'handlers')#handlers
add_static(app)
srv = await loop.create_server(app.make_handler(), '127.0.0.1', 9000)#app.make_handler()->web.AppRunner(app)
print('server started at http://127.0.0.1:9000...')
return srv
loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()
首先要创建事件循环,配合异步处理,异步还不熟的话可以重看廖雪峰的异步IO教程。然后把一个协程coroutine放进run_until_complete里面,其中init(loop)就是一个协程了,有async修饰。
在init函数里面:
- 1、创建连接池,就是数据库和服务端的交接处,可以控制连接数量等。
- 2、创建webAPP类,web.Application有很多个参数,其中loop已被弃用,所以我们不用再传入loop。middlware是一种拦截器,一个URL在被某个函数处理前,可以经过一系列的middleware的处理。这里面的函数都是统一的,一个app和一个handler,参数是怎么传进去的?后面会说到。
- 3、加载模板,jinja2作为模板引擎,在新框架中对jinja2模板进行初始化设置。
- 4、注册函数,add_routes,就是将handler函数作为app类中或者其子类的属性,和middlware里面的函数联合起来,刚刚说的middlware里面的函数的参数handler,就是要通过add_routes注册,才能获得。
- 5、添加静态文件add_static,静态文件就是css, js等模板,可以重整HTML排版,使整体更美观,下面可以看一下差距。这个是排版后的,下面是排版前的。是不是差距有点大呢,前者是copy廖雪峰day5的静态文件,后者是在uikit官网主页下载的,是3.x版本, 页面右上角下载。廖雪峰用的是2.x版本。
- 6、然后就是开启服务了, create_server, 其中的protocol_factory参数,廖雪峰教程中采用app.make_handler(),提示make_handler已经被弃用,但是依然可以运行。
srv = await loop.create_server(app.make_handler(), '127.0.0.1', 9000)#app.make_handler()->web.AppRunner(app)
response_factory
记下来就介绍init里面的各个函数,打通整个架构。在介绍response_factory之前先要了解coroweb.py里面的RequestHandler,因为response_factory的参数除了app还有一个handler,这个handler就是RequestHandler,是通过 app.router.add_route() 注册进去的,其中三个参数是method, path, RequestHandler(app, fn),method 常用的是get、post,path 就是url的通道,就是下面的get后面的参数,通过get包装后,一步一步地传到了add_route里面去。
def get(path):
def decorator(func):
print('in coro get')
@functools.wraps(func)
def wrapper(*args, **kw):
return func(*args, **kw)
wrapper.__method__ = 'GET'
wrapper.__route__ = path
return wrapper
return decorator
@get('/')
async def index(request):
users = await User.findAll()
return {
'__template__': 'test.html',
'users': users
}
最后一个handler参数,就是RequestHandler(app, fn),下面看一下该函数。
RequestHandler
#定义RequestHandler从视图函数中分析其需要接受的参数,从web.Request中获取必要的参数
#调用视图函数,然后把结果转换为web.Response对象,符合aiohttp框架要求
class RequestHandler(object):
def __init__(self, app, fn):
self._app = app
self._func = fn
self._required_kw_args = get_required_kw_args(fn)
self._named_kw_args = get_named_kw_args(fn)
self._has_request_arg = has_request_arg(fn)
self._has_named_kw_arg = has_named_kw_args(fn)
self._has_var_kw_arg = has_var_kw_arg(fn)
# 1.定义kw,用于保存参数
# 2.判断视图函数是否存在关键词参数,如果存在根据POST或者GET方法将request请求内容保存到kw
# 3.如果kw为空(说明request无请求内容),则将match_info列表里的资源映射给kw;若不为空,把命名关键词参数内容给kw
# 4.完善_has_request_arg和_required_kw_args属性
async def __call__(self, request):
print('in RequestHandler call__() request: ',request )
kw = None # 定义kw,用于保存request中参数
if self._has_named_kw_arg or self._has_var_kw_arg: # 若视图函数有命名关键词或关键词参数
if request.method == 'POST':
# 根据request参数中的content_type使用不同解析方法:
if request.content_type == None: # 如果content_type不存在,返回400错误
return web.HTTPBadRequest(text='Missing Content_Type.')
ct = request.content_type.lower() # 小写,便于检查
if ct.startwith('application/json'): # json格式数据
params = await request.json() # 仅解析body字段的json数据
if not isinstance(params, dict): # request.json()返回dict对象
return web.HTTPBadRequest(text='JSON body must be object.')
kw = params
# form表单请求的编码形式
elif ct.startwith('application/x-www-form-urlencoded') or ct.startswith('multipart/form-data'):
params = await request.post() # 返回post的内容中解析后的数据。dict-like对象。
kw = dict(**params) # 组成dict,统一kw格式
else:
return web.HTTPBadRequest(text='Unsupported Content-Type: %s' % request.content_type)
if request.method == 'GET':
qs = request.query_string # 返回URL查询语句,?后的键值。string形式。
print('in call__() qs = request.query_string : ' ,qs)
if qs:
kw = dict()
'''
解析url中?后面的键值对的内容
qs = 'first=f,s&second=s'
parse.parse_qs(qs, True).items()
>>> dict([('first', ['f,s']), ('second', ['s'])])
'''
for k, v in parse.parse_qs(qs, True).items(): # 返回查询变量和值的映射,dict对象。True表示不忽略空格。
kw[k] = v[0]
print('in get:kw: ',kw)
if kw is None: # 若request中无参数
# request.match_info返回dict对象。可变路由中的可变字段{variable}为参数名,传入request请求的path为值
# 若存在可变路由:/a/{name}/c,可匹配path为:/a/jack/c的request
# 则reqwuest.match_info返回{name = jack}
print('in call__() request.match_info ,**request.match_info: ',request.match_info,' ,,,,,,,,, ', **request.match_info)
kw = dict(**request.match_info)
else: # request有参数
if self._has_named_kw_arg and (not self._has_var_kw_arg): # 若视图函数只有命名关键词参数没有关键词参数
copy = dict()
# 只保留命名关键词参数
for name in self._named_kw_args:
if name in kw:
copy[name] = kw[name]
kw = copy # kw中只存在命名关键词参数
print('in call__() kw,copy : ',kw,copy)
# 将request.match_info中的参数传入kw
for k, v in request.match_info.items():
print('in getpost k: ',k,' v: ',v)
# 检查kw中的参数是否和match_info中的重复
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: # 视图函数存在request参数
kw['request'] = request
if self._required_kw_args: # 视图函数存在无默认值的命名关键词参数
for name in self._required_kw_args:
if not name in kw: # 若未传入必须参数值,报错。
return web.HTTPBadRequest('Missing argument: %s' % name)
# 至此,kw为视图函数fn真正能调用的参数
# request请求中的参数,终于传递给了视图函数
print('call with args: %s' % str(kw))
# try:
#print('in call__() **kw: ' ,**kw)
r = await self._func(**kw)
return r
函数有两个魔法方法,__ init__() , 和__call__(), 如果把init参数也就是(app, fn)传进去就会初始化函数,其中fn就是通过add_routes解析出来的存在于handler.py的index函数。得到的RequestHandler(app, fn)是一个实例化,由对他再使用()的话就会调用call方法,也就是RequestHandler(app, fn)(request),call()里面的内容就会执行。
回到response_factory,其中的r = await handler(request),就是执行了RequestHandler的call方法,参数request是客户端发过来的,在app内部处理后(已经注册了的话)再交给handler函数。
async def response_factory(app, handler):
print('in response_factory handler: ',handler)
#输出:in response_factory handler: <function AbstractRoute.__init__.<locals>.handler_wrapper at 0x000001C1B44A92F0>
async def response(request):
print('in app response factory requset: ',request)
#输出:in app response factory requset: <Request GET / >
print('in response factory Response handler...')
r = await handler(request)
if isinstance(r, web.StreamResponse):
...
...
...
return respone
RequestHandler的功能就是将request里面的内容处里面字典的形式,目前只是处理post和get方法的请求,到day8的时候,请求request也只是<Request GET / >而已,最多也就GET /blog/1,不过是404的返回,还没写好处理函数。
在RequestHandler的call的最后得到kw字典参数,作r = wait self._func(**kw) 处理self.__func就是传进RequestHandler的参数fn,也就是通过add_routes解析出来的存在于handlers.py 的index(request)函数,然后返回也是一个字典的形式,就是r。然后就回到了response_factory函数。
- RequestHandler小结,response_factory的handler是一个已经初始化的函数,原型是RequestHandler(app, fn),服务端接收到request时,就会启动middlware,里面的函数都会跑起来。然后就会触发到RequestHandler的call方法,处理request的各部分参数,然后通过字典的形式返回给response_factory。
现在我们回到response_factory,该函数接收到handler的返回后,对返回值r 进行处理。r其实就是下面的函数的返回值。
#handlers.py
@get('/')
def index(request):
print('in index request: ',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),
Blog(id='3', name='Learn Swift', summary=summary, created_at=time.time()-7200)
]
return {
'__template__': 'blogs.html',
'blogs': blogs
}
我们可以看到返回值是字典形式,所以在response_factory中的==if isinstance(r, dict):==会停下进入。
async def response_factory(app, handler):
async def response(request):
print('in app response factory requset: ',request)#打印 in app response factory requset: <Request GET / >
r = await handler(request)
...................
..................
if isinstance(r, dict):
print('Response handler...dict: ',r)
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'
print('resp: ',resp)
print('resp.__dict__: ',resp.__dict__)
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'
print('resp: ',resp)
return resp
................
...............
resp = web.Response(body=str(r).encode('utf-8'))
resp.content_type = 'text/plain;charset=utf-8'
print('resp: ', resp)#打印 resp: <Response OK not prepared>
return resp
return response
进入判断条件后就是从r里面提取信息,再对返回的response进行包装。其中print('Response handler…dict: ',r)的结果是:
Response handler...
dict:{
'__template__': 'blogs.html',
'blogs': [
{'id': '1', 'name': 'Test Blog', 'summary': 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', 'created_at': 1565066334.8002357},
{'id': '2', 'name': 'Something New', 'summary': 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', 'created_at': 1565062854.8002357},
{'id': '3', 'name': 'Learn Swift', 'summary': 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', 'created_at': 1565059254.8002357}
]
}
和index函数的返回是一样的,包装完之后就返回给浏览器了。
- response_factory小结:该函数就是调用RequestHandler来处理request请求,然后返回给response_factory再进行返回值包装,发送给浏览器。
init_jinja2
我们使用jinja2作为模板引擎,在新框架中对jinja2模板进行初始化设置。
def init_jinja2(app, **kw):
logging.info('init jinja2…')
# class Environment(**options)
# 配置options参数
options = dict(
# 自动转义xml/html的特殊字符
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 = kw.get('path', None)
if not path:
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')
# Environment类是jinja2的核心类,用来保存配置、全局对象以及模板文件的路径
# FileSystemLoader类加载path路径中的模板文件
env = Environment(loader = FileSystemLoader(path), **options)
# 过滤器集合
filters = kw.get('filters', None)
if filters:
for name, f in filters.items():
# filters是Environment类的属性:过滤器字典
env.filters[name] = f
# 所有的一切是为了给app添加__templating__字段
# 前面将jinja2的环境配置都赋值给env了,这里再把env存入app的dict中,这样app就知道要到哪儿去找模板,怎么解析模板。
app['__template__'] = env # app是一个dict-like对象
初始化jinja2需要以下几步:
-
1、对 Environment 类的参数 options 进行配置。
-
2、使用jinja提供的模板加载器加载模板文件,程序中选用FileSystemLoader加载器直接从模板文件夹加载模板。
-
3、有了加载器和options参数,传递给Environment类,添加过滤器,完成初始化。
add_routes
该函数的作用就是将handlers.py里面的函数都加入注册,注册进app类的内部属性,方便库内的调用,帮我们减少了很多麻烦。
想知道注册函数内部发生了什么就移步到这里。
在for循环之前都是在解析路径,找到handlers.py后就把里面的函数逐个进行注册。
def add_routes(app, module_name):
n = module_name.rfind('.') # 从右侧检索,返回索引。若无,返回-1。
# 导入整个模块
if n == -1:
# __import__ 作用同import语句,但__import__是一个函数,并且只接收字符串作为参数
# __import__('os',globals(),locals(),['path','pip'], 0) ,等价于from os import path, pip
mod = __import__(module_name, globals(), locals, [], 0)
else:
name = module_name[(n+1):]
# 只获取最终导入的模块,为后续调用dir()
mod = getattr(__import__(module_name[:n], globals(), locals, [name], 0), name)
for attr in dir(mod): # dir()迭代出mod模块中所有的类,实例及函数等对象,str形式
if attr.startswith('_'):
continue # 忽略'_'开头的对象,直接继续for循环
fn = getattr(mod, attr)
print('add_routes attr in dir(mod): ',attr)#
# 确保是函数
if callable(fn):
# 确保视图函数存在method和path
method = getattr(fn, '__method__', None)
path = getattr(fn, '__route__', None)
print(fn,' was callable!')
if method and path:
# 注册
add_route(app, fn)
我们看一下 print(‘add_routes attr in dir(mod): ‘,attr)、print(fn,’ was callable!’) 的结果:
add_routes attr in dir(mod): Blog
<class 'models.Blog'> was callable!
add_routes attr in dir(mod): Comment
<class 'models.Comment'> was callable!
add_routes attr in dir(mod): User
<class 'models.User'> was callable!
add_routes attr in dir(mod): __author__
add_routes attr in dir(mod): __builtins__
add_routes attr in dir(mod): __cached__
add_routes attr in dir(mod): __doc__
add_routes attr in dir(mod): __file__
add_routes attr in dir(mod): __loader__
add_routes attr in dir(mod): __name__
add_routes attr in dir(mod): __package__
add_routes attr in dir(mod): __spec__
add_routes attr in dir(mod): asyncio
add_routes attr in dir(mod): base64
add_routes attr in dir(mod): get
<function get at 0x000001C1B449D8C8> was callable!
add_routes attr in dir(mod): hashlib
add_routes attr in dir(mod): index
<function index at 0x000001C1B44A9378> was callable!
add_routes attr in dir(mod): json
add_routes attr in dir(mod): logging
add_routes attr in dir(mod): next_id
<function next_id at 0x000001C1B44A9400> was callable!
add_routes attr in dir(mod): post
<function post at 0x000001C1B449F598> was callable!
add_routes attr in dir(mod): re
add_routes attr in dir(mod): time
看到有7个是可以callable的,最后真正进行注册的只有index,因为==if method and path:==才能够注册,index被get装饰过,所以拥有method and path属性。
add_route
# 编写一个add_route函数,用来注册一个视图函数
def add_route(app, fn):
method = getattr(fn, '__method__', None)
path = getattr(fn, '__route__', None)
if method is None or path is None:
raise ValueError('@get or @post not defined in %s.' % fn.__name__)
# 判断URL处理函数是否协程并且是生成器
if not asyncio.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn):
# 将fn转变成协程
fn = asyncio.coroutine(fn)
logging.info('add route %s %s => %s(%s)' % (method, path, fn.__name__, ','.join(inspect.signature(fn).parameters.keys())))
# 在app中注册经RequestHandler类封装的视图函数
app.router.add_route(method, path, RequestHandler(app, fn))
logging提示的信息是:add route GET / => index(request)。
index函数进行注册。
add_static
该函数函数用于注册静态文件,提供文件路径即可进行注册。
def add_static(app):
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
app.router.add_static('/static/', path)
print('add_static path: ',path)
print('add static %s => %s' % ('/static/', path))
服务端和数据库
下面是关于服务端和数据库的函数,也就是app.py和 orm.py的交互情况。
注册
我们拿一个test.py 当作app.py 进行注册用户。
import orm
import asyncio
from models import User, Blog, Comment
async def test(loop):
await orm.create_pool(loop=loop, user='www-data', password='kx123456', db='awesome')
u = User(name='Test', email='test5@example.com',
passwd='123456789', image='about:blank')
await u.save()
loop = asyncio.get_event_loop()
loop.run_until_complete(test(loop))
loop.run_forever()
for x in test(loop):
pass
然后一步一步跟着代码走,首先是连接数据库,然后创建用户实例,再save()保存就行,然而User怎么来?丢参数怎么处理?怎么save()?先看一下类和一些函数的介绍。
User类
我们来看一下这个类:
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)
似乎把参数这么一丢进去,接收的了吗?
我们看到各个属性都是一个类。
我们还要回到他的父类Model:(省略了大部分)
class Model(dict, metaclass=ModelMetaclass):
def __init__(self, **kw):
super(Model, self).__init__(**kw)
print('kwkw: ',kw)
def __getattr__(self, key):
try:
print('in model getattr: ',key,self[key])
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
..............................
..............................
如果继承知识学得不牢的话,可能还是会看不懂,User继承于Model, Model 继承于dict , 为的就是方便取得各种值,用self[key]就可以取得对应的值,要注意的是要用super加载一遍父类的init,还有把**kw传进去。这样的话就可以用点号运算符,取出其中的属性,如果不存在的话,就会调用 getattr 魔法方法。然后在里面用self[key] 进行提取kw里面的值。
我们可以看到User类里面的变量都是一些类,基于Field的类,所以使用 user.id和user.name 等等的情况下(thml里面会用到),不就是一个类了吗?刚刚传入的kw对应不上啊?
所以这个时候就有了元类,Model还要有一个元类,去修改和增加继承于Model的类(User、Blog、Comment )的一些属性。下面是Model的元类。
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
if name=='Model':
return type.__new__(cls, name, bases, attrs)
#如果发现使用了该元类的类是Model,则不需要修改,只有User、Blog、Comment 这三个类需要修改诸多的性质
tableName = attrs.get('__table__', None) or name
print('attrs: ',attrs)
print('found model: %s (table: %s)' % (name, tableName))
mappings = dict()
fields = []
primaryKey = None
for k, v in attrs.items():
print('k :',k,'v :',v)
if isinstance(v, Field):
mappings[k] = v
if v.primary_key:
# 找到主键:!!!!!!!!!!!!!!!!!!!!!!主键就是ID!!!!!!!!!!!!!!!!!!!
print('v.primary_key:',v.primary_key)
if primaryKey:
raise StandardError('Duplicate primary key for field: %s' % k)
primaryKey = k
else:
fields.append(k)
print('fields: ',fields)#!!!!!!!!!!!!!!!!!!!!!!
if not primaryKey:
raise StandardError('Primary key not found.')
for k in mappings.keys():
attrs.pop(k)
print('attrs: ', attrs)#!!!!!!!!!!!!!!!!!!!!
escaped_fields = list(map(lambda f: '`%s`' % f, fields))
print('escaped_fields: ',escaped_fields)
attrs['__mappings__'] = mappings # 保存属性和类(ID、name等继承于Field的类)的映射关系
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)
print('__mappings__:',attrs['__mappings__'])
print('__fields__: ',attrs['__fields__'])
print('__select__: ',attrs['__select__'] )
print('__insert__: ',attrs['__insert__'] )
print('__update__: ',attrs['__update__'])
print('__delete__: ',attrs['__delete__'])
print('type.__new__(cls, name, bases, attrs): ',type.__new__(cls, name, bases, attrs))
return type.__new__(cls, name, bases, attrs)
运行了一下,就有以下输出。
found model: User (table: users)
k : __module__ v : models
k : __qualname__ v : User
k : __table__ v : users
k : id v : <StringField, varchar(50):None>
v.primary_key: True
k : email v : <StringField, varchar(50):None>
k : passwd v : <StringField, varchar(50):None>
k : admin v : <BooleanField, boolean:None>
k : name v : <StringField, varchar(50):None>
k : image v : <StringField, varchar(500):None>
k : created_at v : <FloatField, real:None>
fields: ['email', 'passwd', 'admin', 'name', 'image', 'created_at']
attrs: {'__module__': 'models', '__qualname__': 'User', '__table__': 'users'}
escaped_fields: ['`email`', '`passwd`', '`admin`', '`name`', '`image`', '`created_at`']
__fields__: ['email', 'passwd', 'admin', 'name', 'image', 'created_at']
__mappings__: {'id': <orm.StringField object at 0x00000224B00598D0>,
'blog_id': <orm.StringField object at 0x00000224B0059828>,
'user_id': <orm.StringField object at 0x00000224B0059860>,
'user_name': <orm.StringField object at 0x00000224B0059908>,
'user_image': <orm.StringField object at 0x00000224B0059940>,
'content': <orm.TextField object at 0x00000224B0059978>,
'created_at': <orm.FloatField object at 0x00000224B00599B0>}
__select__: select `id`, `email`, `passwd`, `admin`, `name`, `image`, `created_at` from `users`
__insert__: insert into `users` (`email`, `passwd`, `admin`, `name`, `image`, `created_at`, `id`) values (?, ?, ?, ?, ?, ?, ?)
__update__: update `users` set `email`=?, `passwd`=?, `admin`=?, `name`=?, `image`=?, `created_at`=? where `id`=?
__delete__: delete from `users` where `id`=?
type.__new__(cls, name, bases, attrs): <class 'models.User'>
然后发现在函数的for k, v in attrs.items(): 部分,把父类是Field的类收在mapping里面,把用户的属性的名称放在列表fields里面,然后再在attrs里面把id、name等类删掉,这样子,user.id和user.name等 就不会返回一个类了,他们是调用不了,因为已经删掉了这些属性,然后就会去调用__getattr__(), 之后就可以返回kw里面的值了!!!理解了这些,之后的一切都很简单了!!!
self.getValueOrDefault()
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 value
这个函数就是用来为save服务的,避免获取失败,就可以使用刚开始设置好的default值。我们看到上面的输出,mapping是一个字典,存放的是从attrs中删掉的属性,是七个类。把这些类拿出来,来获取他的default值。
save()
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.warning('failed to insert record: affected rows: %s' % rows)#warn
save就是用来把输入的信息保存到数据库,用map取出fields列表里面的属性对应的值。fields里面是不含ID的,ID放在__primary_key__里面,所以继续append进去。最后再用execute操控mysql保存进去。
findAll
@classmethod
async def findAll(cls, where=None, args=None, **kw):
' find objects by where clause. '
sql = [cls.__select__]#select
#print(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)
print('in findAll [type(cls(**r)) for r in rs]: ',[type(cls(**r)) for r in rs])
print('in findAll [cls(**r) for r in rs]: ',[cls(**r) for r in rs])
print('rs: ',rs)
return [cls(**r) for r in rs]
先看一下下面的输出,就可以知道findall的返回值是什么。
我的数据库存在两个用户:
in findAll [type(cls(**r)) for r in rs]:
[
<class 'models.User'>,
<class 'models.User'>
]
in findAll [cls(**r) for r in rs]:
[
{'id': '00156479459332916458e6d6c144f4f887214c26000cf41000', 'email': 'test1@example.com', 'passwd': '1234567890', 'admin': 0, 'name': 'Test', 'image': 'about:blank', 'created_at': 1564794593.32919},
{'id': '0015648105271120e5ba27b865f4001bc2d1884960050c2000', 'email': 'test@example.com', 'passwd': '12345678', 'admin': 0, 'name': 'Test', 'image': 'about:blank', 'created_at': 1564810527.11229},
]
[cls(**r) for r in rs] 是一个存放着N个类的列表,有N个用户。能明白 cls(**r) 的话就好理解。
举个例子就能明白:
cls是一个类,和函数一样样,有这个传参的用法(把**kw传进去)。
看到这里可能最开始的注册代码都忘了:
import orm
import asyncio
from models import User, Blog, Comment
async def test(loop):
await orm.create_pool(loop=loop, user='www-data', password='kx123456', db='awesome')
u = User(name='Test', email='test5@example.com',
passwd='123456789', image='about:blank')
await u.save()
loop = asyncio.get_event_loop()
loop.run_until_complete(test(loop))
loop.run_forever()
for x in test(loop):
pass
再看一下handler函数
@get('/')
async def index(request):
users = await User.findAll()
return {
'__template__': 'test.html',
'users': users
}
然后在html里面就可以使用user类了
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test users - Awesome Python Webapp</title>
</head>
<body>
<h1>All users</h1>
{% for u in users %}
<p>{{ u.name }} / {{ u.email }}</p>
{% endfor %}
</body>
</html>
然后就可以显示用户信息了。
总结
服务端、客户端、数据库三者的关系差不多都打通了,就可以天马行空了,自己就可以天就各种各样的功能。
参考
day5
Day5
还是Day5
asyncio
这个是add_routes