热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

廖雪峰webAPP实战——Day18总结\剖析Day5

廖雪峰webAPP实战——Day1-8总结服务端和客户端init(loop)response_factoryRequestHandlerinit_jinja2add_routesa



廖雪峰webAPP实战——Day1-8总结

  • 服务端和客户端
    • init(loop)
    • response_factory
      • RequestHandler
    • init_jinja2
    • add_routes
      • add_route
    • add_static
  • 服务端和数据库
    • 注册
    • User类
      • self.getValueOrDefault()
      • save()
      • findAll
  • 总结
  • 参考
  • thanks!!!


大家可能都卡在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: .handler_wrapper at 0x000001C1B44A92F0>
async def response(request):
print('in app response factory requset: ',request)
#输出:in app response factory requset:
print('in response factory Response handler...')
r = await handler(request)
if isinstance(r, web.StreamResponse):
...
...
...
return respone

RequestHandler的功能就是将request里面的内容处里面字典的形式,目前只是处理post和get方法的请求,到day8的时候,请求request也只是而已,最多也就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:
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:
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
was callable!
add_routes attr in dir(mod): Comment
was callable!
add_routes attr in dir(mod): 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
was callable!
add_routes attr in dir(mod): hashlib
add_routes attr in dir(mod): index
was callable!
add_routes attr in dir(mod): json
add_routes attr in dir(mod): logging
add_routes attr in dir(mod): next_id
was callable!
add_routes attr in dir(mod): post
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 :
v.primary_key: True
k : email v :
k : passwd v :
k : admin v :
k : name v :
k : image v :
k : created_at v :
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': ,
'blog_id': ,
'user_id': ,
'user_name': ,
'user_image': ,
'content': ,
'created_at': }
__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):

然后发现在函数的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]:
[
,

]
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类了








All users
{% for u in users %}

{{ u.name }} / {{ u.email }}


{% endfor %}


然后就可以显示用户信息了。

在这里插入图片描述


总结

服务端、客户端、数据库三者的关系差不多都打通了,就可以天马行空了,自己就可以天就各种各样的功能。


参考

day5

Day5

还是Day5

asyncio

这个是add_routes


thanks!!!

推荐阅读
  • 本文详细介绍了 phpMyAdmin 的安装与配置方法,适用于多个版本的 phpMyAdmin。通过本教程,您将掌握从下载到部署的完整流程,并了解如何根据不同的环境进行必要的配置调整。 ... [详细]
  • Python自动化测试入门:Selenium环境搭建
    本文详细介绍如何在Python环境中安装和配置Selenium,包括开发工具PyCharm的安装、Python环境的设置以及Selenium包的安装方法。此外,还提供了编写和运行第一个自动化测试脚本的步骤。 ... [详细]
  • Spring Boot 中静态资源映射详解
    本文深入探讨了 Spring Boot 如何简化 Web 应用中的静态资源管理,包括默认的静态资源映射规则、WebJars 的使用以及静态首页的处理方法。通过本文,您将了解如何高效地管理和引用静态资源。 ... [详细]
  • 软件工程课堂测试2
    要做一个简单的保存网页界面,首先用jsp写出保存界面,本次界面比较简单,首先是三个提示语,后面是三个输入框,然 ... [详细]
  • 本文深入探讨了 Exchange Server 2010 中客户端访问的代理和重定向机制,特别是在跨站点环境中如何配置这些功能以确保用户能够顺利访问邮箱服务。通过详细解析不同场景下的应用,帮助管理员更好地理解和实施相关设置。 ... [详细]
  • 在尝试使用C# Windows Forms客户端通过SignalR连接到ASP.NET服务器时,遇到了内部服务器错误(500)。本文将详细探讨问题的原因及解决方案。 ... [详细]
  • Symfony是一个功能强大的PHP框架,以其依赖注入(DI)特性著称。许多流行的PHP框架如Drupal和Laravel的核心组件都基于Symfony构建。本文将详细介绍Symfony的安装方法及其基本使用。 ... [详细]
  • 本文详细介绍了如何在云服务器上配置Nginx、Tomcat、JDK和MySQL。涵盖从下载、安装到配置的完整步骤,帮助读者快速搭建Java Web开发环境。 ... [详细]
  • 本文探讨了在 SQL Server 中使用 JDBC 插入数据时遇到的问题。通过详细分析代码和数据库配置,提供了解决方案并解释了潜在的原因。 ... [详细]
  • 本文详细比较了CSS选择器和XPath在Selenium中通过页面结构定位元素的优劣,并提供了具体的代码示例,帮助读者理解两者在不同场景下的适用性。 ... [详细]
  • 访问一个网页的全过程
    准备:DHCPUDPIP和以太网启动主机,用一根以太网电缆连接到学校的以太网交换机,交换机又与学校的路由器相连.学校的这台路由器与一个ISP链接,此ISP(Intern ... [详细]
  • SpringMVC RestTemplate的几种请求调用(转)
    SpringMVCRestTemplate的几种请求调用(转),Go语言社区,Golang程序员人脉社 ... [详细]
  • Vue 开发与调试工具指南
    本文介绍了如何使用 Vue 调试工具,包括克隆仓库、安装依赖包、构建项目以及在 Chrome 浏览器中加载扩展的详细步骤。 ... [详细]
  • Django Token 认证详解与 HTTP 401、403 状态码的区别
    本文详细介绍了如何在 Django 中配置和使用 Token 认证,并解释了 HTTP 401 和 HTTP 403 状态码的区别。通过具体的代码示例,帮助开发者理解认证机制及权限控制。 ... [详细]
  • 本文介绍了如何通过在数据库表中增加一个字段来记录文章的访问次数,并提供了一个示例方法用于更新该字段值。 ... [详细]
author-avatar
Smitty
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有