廖雪峰webAPP实战——Day1-8总结 \ 剖析Day5

廖雪峰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排版,使整体更美观,下面可以看一下差距。廖雪峰webAPP实战——Day1-8总结  \  剖析Day5这个是排版后的,下面是排版前的。廖雪峰webAPP实战——Day1-8总结  \  剖析Day5是不是差距有点大呢,前者是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) 的话就好理解。
举个例子就能明白:
廖雪峰webAPP实战——Day1-8总结  \  剖析Day5
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>

然后就可以显示用户信息了。
廖雪峰webAPP实战——Day1-8总结  \  剖析Day5

总结

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

参考

day5
Day5
还是Day5
asyncio
这个是add_routes

thanks!!!

上一篇:每天5个Python小技巧(day5)


下一篇:冲刺博客Day5