廖雪峰Python教程day1-day9总结

源码见廖雪峰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 &copy; 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())


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值