;jeP$5;@#etPRn@J>K0o*c){DcRXNybNf8fNU-5xB zl3E&(O^q}T`iP*1>ty47@x9caudk8FXNgcp@FE4>vh-!klxMsbG|c77D#9n1d*Pag zY^|WBaYU=Qrj?>;8O@HwANrE)1MzX+g;)S3ZYb6Mj3lOMkj)oL-^xNhyMQL!d`W`+zc&NkI5eWiC1F@kGCS8@#MoB_N+^`fB zxkDL74YNd;13wjNEcrlKlyIIxAzhCEe-%~{IUue?AQ}P;D1}-fpn@2Je*<=^z7Yu$ zC5n+V(ul8uTD}TmRXJXd0-vgh;&%#(x(JR!;6}(a|6_e&;rd!|l{ig;_BE) QCZF4rP0^Zn~k~vag;bM2lZQZoI}KSc@3xiIKX53%|G 8R~++1<`K(yLCLxz{y8*&U?iCy7E|f7 zr(_3`^{9BtiBlfq*bQjk`KhJ?eiWOwijIqLYIEjq;=Ab+)mDG2Ha5;UDaZ~fx0P(I z*OBL`Mceg)Ze $!U^XD5>PcIE!HyQ)j_r$mmTaxO(v`HDHJ$}KuqG{9s^z^LUrP` zVE7zo!*tim2Is^^R1W;hL>h6cHBVXyr}p{|-VTLEW1Z@(7SdF_w9aT}6u}D#cu_Q| zOWWCgzaxcp=dN8SmOZ8Hc!82rw!MH`+%S2!cy0J-HGx|G2aF7~EK@p)ypZ3OB}|iW ziO-TKAqY BD#D~|Y|F503vo2L@ VZws__v~_FYq?U{(gSG4Ae*ghb0(}4g delta 3941 zcmbVPU2Ggz6+U-ncfGrw_0QT~|2vzwZo1f{iD{FDBsQt+IBgmysRKzC)MdRh>#RNA zb?=N5Y@DoUBA`6*qx*n_)KXPEfRHQ%s1F6CB7qP{ec%O&c|{Z+Q6GaqeCO_; p8k}?)ka*oO93p=HvNa&W)!MiTGD9{N~#GBSe3rJ%165KZnPA^LyiWtB#=6cCy1{ zN9aD$JwaKEibQrB*-^TW*96sN1lcjN ;%~zdfY_|Hr~7V1=Q)L zdnRR*;C7Rp)Z8RFNzLu4g55)QuV(i)*uCKPk)6`qRD;_GZa>)rnmf?orobH}dq{JK z8r*(xhshq%+!1mHw7WrYN68-3+%a;7C}|%cXAqMj3voe9nY4OWoDgd&5W8Yty=lBI zvg+SP(dt6T0%Ps=Pe=}^b#v};FBlfbaR7vjgc%b_{Nnn@BmT)=^%wJOwHp#L36e1a z(|g?wLV3C3N!F~9L5u>^t*ne;;29t!Br-<@lgIeuw6)_Y;PHM9;uG!QAIUIC_=4tb zqVwN*i|&}TW74WYK4^$jHDBeaJ0aQ$(Q25rRwKMw+hAQ-*tpCo$5`7CI|j*HTDXlc zW((Rtn9ALn9i@BJ k!VLe$8`$+A;^22OQg`~5dGq?h=_ipTXbS94})LO z>A-+f&`;HR4&K3AEeF_RD*^ZEjzL?C9602l8K+uOKoR`VL&0x_9r+3qrwM~dQ&n6m zKSLqEjn?8Mze}QeCrn~HN^cNdyMa*RADp{kx?UD^9ocv&%;usT#X>pInP@AllIu(! zV3G#OMv;R;WwY$dgLn;M^Is`A8-A(sP*~+tEdSDkR>&8Nh@N8y!r52H+UzuhYGK8B z=n$sp76(O4^x#aSgqA1>b$PH?wMS>gsJalHdVb$&NEW1%_njP{l$__wQh8;TjYdHT zIqHE)c~pJSHK=|c-TgE>l%HYp9Fymn?6Vln `9(D#pE;-?Z3ILhN5 KdZW}e^%ADOuX24?+O ywsEEcyAeotO5!l<3ow>f^p=`lopM zC=>3lnK-(z>&a<#EVYoTtyEs(S?z_Zc0+wTwQ+J^+<9Wpc3L&chpd99QE%r^<%8la zb*2C6WhX|f@fJ2gbH;zRo$CAsPGP3iU;?jb$O4FRUtf4YJ>TG4Dk&G#NBw7NNnPOO z=(B9p>bW0`$5-(W)zyLZgXxPE+i}yC H53s?(mE8FjRO6URM2Scp?GZu>uTY(!CJPtN#r5i&s?N$lPn45Rn1|H+u7m z*B7z8$Ws@X=&CX$_ng4kSd&jPQ`bkcNP Dg5|3WFj*FpkPQo8o5dVHCDt^jwhDY)utJp)5ddXfJw<+$zcTz-ot1*n8-joz z@!^4Q5-5Yi_!FCK QAMn#1%`rhPF8-@fTcCuplk+mXv#+c7;zFK?xT zuv=O$`5FSLe;gPyABeOHr%xPhmI?s5apY(qB+fBcQ!Rgd{2s(lyCu&rRMt0U{;u5g zk#Vb5fVwVpKm0tz0<%>1gRsB3fm3ow{W(4G+8WzA%!Jz@V3#)RF@p<5a@(kZRp3XG zn@pMo+*pV10Bu~tXI=M0hxVj?^VG9r7gGImM&}6_8|-MJ6|y zaB&Ci1vl?`>yCf3Vh3$`Z>wA=Rf1TdA}gDIsqCPZAS#(unY3b6$0>u1Gvos1-o~R* zo+Xl|6|qeGEK>}``eG5GC4v|n<+aDmNbNj-4N8Gg@q*B*S1AV}zqIbi9yM|3Q&nJE zlD$kunCKYlBga=s5UDj%4YMS}9m%*uG6Il%56d|wSD2h*vc!a!+1f%Yy+QQ!dd1#! pof+;mI=I%P0BEcKx L!S_!KyWu;=NaQ*k}{{Tk_))W8$ diff --git a/awesome-python3-webapp/www/app.py b/awesome-python3-webapp/www/app.py index 10d22e7..f9f8d5d 100644 --- a/awesome-python3-webapp/www/app.py +++ b/awesome-python3-webapp/www/app.py @@ -1,10 +1,9 @@ import logging -import asyncio -import json -import os -import time -from aiohttp import web +import asyncio, os, json, time from datetime import datetime + +from aiohttp import web +from urllib import parse from jinja2 import Environment, FileSystemLoader import orm @@ -12,13 +11,9 @@ logging.basicConfig(level=logging.INFO) -# def index(request): -# return web.Response(body='你好'.encode(encoding='utf-8'), -# headers={'Content-Type': 'text/html;charset=utf-8'}) - def init_jinja2(app, **kw): - logging.info('init jinja2....') + 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', '%}'), @@ -26,7 +21,6 @@ def init_jinja2(app, **kw): variable_end_string=kw.get('variable_end_string', '}}'), auto_reload=kw.get('auto_reload', True)) path = kw.get('path', None) - # logging.info('set jinja2 template path: %s' % path) if path is None: path = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'templates') @@ -36,41 +30,57 @@ def init_jinja2(app, **kw): if filters is not None: for name, f in filters.items(): env.filters[name] = f - app['__templates__'] = env + app['__templating__'] = env +# -------------------------工厂函数------------------------------------ +# 在每个响应之前打印日志 +async def logger_factory(app, handler): -@asyncio.coroutine -def logger_factory(app, handler): - def logger(request): - logging.info('Request: %s %s' % (request.method, request.path)) - # await asyncio.sleep(0.3) - return handler(request) + async def logger(request): + logging.info('Response: %s %s' % (request.method, request.path)) + return await handler(request) return logger -@asyncio.coroutine -def data_factory(app, handler): - def parse_data(request): +async def data_factory(app, handler): + + async def parse_data(request): + logging.info('data_factory...') if request.method == 'POST': - if request.content_type.startswith('application/json'): - request.__data__ = request.json() - logging.info('request json: %s' % str(request.__data__)) - elif request.content_type.startswith( - 'application/x-www-form-urlencoded'): - request.__data__ = request.post() - logging.info('request form: %s' % str(request.__data__)) - return handler(request) + if not request.content_type: + return web.HTTPBadRequest(text='Missing Content-Type.') + content_type = request.content_type.lower() + if content_type.startswith('application/json'): + request.__data__ = await request.json() + if not isinstance(request.__data__, dict): + return web.HTTPBadRequest(text='JSON body must be object.') + logging.info('request json: %s' % request.__data__) + elif content_type.startswith( + ('application/x-www-form-urlencoded', 'multipart/form-data')): + params = await request.json() + request.__data__ = dict(**params) + logging.info('request form: %s' % request.__data__) + else: + return web.HTTPBadRequest(text='Unsupported Content-Type: %s' % + content_type) + elif request.method == 'GET': + qs = request.query_string + request.__data__ = {k: v[0] for k, v in parse.parse_qs(qs, True).items()} + logging.info('request query: %s' % request.__data__) + else: + request.__data__ = dict() + return await handler(request) return parse_data +# 把任何返回值封装成浏览器可正确显示的Response对象 +async def response_factory(app, handler): -@asyncio.coroutine -def response_factory(app, handler): - def response(request): + async def response(request): logging.info('Response handler...') - r = handler(request) - logging.info(type(r)) + r = await handler(request) + logging.info('Method request -- done : %s' % request.method) if isinstance(r, web.StreamResponse): return r if isinstance(r, bytes): @@ -92,18 +102,20 @@ def response(request): resp.content_type = 'application/json;charset=utf-8' return resp else: + # 如果用jinja2渲染,绑定已验证过的用户 + r['__user__'] = request.__user__ 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, int) and 100 <= r < 600: + return web.Response(status=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: + status, message = r + if isinstance(status, int) and 100 <= status < 600: + return web.Response(status=status, text=str(message)) + # default resp = web.Response(body=str(r).encode('utf-8')) resp.content_type = 'text/plain;charset=utf-8' return resp @@ -125,24 +137,17 @@ def datetime_filter(t): return u'%s年%s月%s日' % (dt.year, dt.month, dt.day) -@asyncio.coroutine -def init(loop): - orm.create_pool(loop=loop, - host='localhost', - post=3306, - user='sa', - password='P@ssw0rd', - db='awesome') - app = web.Application(loop=loop, - middlewares=[ - logger_factory, response_factory - ]) +async def init(loop): + await orm.create_pool(loop=loop, + user='sa', + password='P@ssw0rd', + db='awesome') + app = web.Application(loop=loop, middlewares=[logger_factory, response_factory]) init_jinja2(app, filters=dict(datetime=datetime_filter)) add_routes(app, 'handlers') add_static(app) - # app.router.add_route("GET", "/", index) - srv = yield from loop.create_server(app.make_handler(), '127.0.0.1', 9003) - logging.info('Server started at http://127.0.0.1:9003') + srv = await loop.create_server(app.make_handler(), '127.0.0.1', 9000) + logging.info('server started at http://127.0.0.1:9000...') return srv diff --git a/awesome-python3-webapp/www/coroweb.py b/awesome-python3-webapp/www/coroweb.py index 4487ebf..d874994 100644 --- a/awesome-python3-webapp/www/coroweb.py +++ b/awesome-python3-webapp/www/coroweb.py @@ -10,6 +10,10 @@ def get(path): + ''' + Define decorator @get('/path') + ''' + def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): @@ -23,6 +27,10 @@ def wrapper(*args, **kw): def post(path): + ''' + Define decorator @post('/path') + ''' + def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): @@ -71,7 +79,6 @@ def has_request_arg(fn): sig = inspect.signature(fn) params = sig.parameters found = False - for name, param in params.items(): if name == 'request': found = True @@ -84,71 +91,9 @@ def has_request_arg(fn): % (fn.__name__, str(sig))) return found -# 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) -# -# 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 = 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 = request.post() -# kw = dict(**params) -# else: -# return web.HTTPBadRequest('Unsupported Content-Type:%s' % -# request.content_type) -# if request.method == 'GET': -# qs = request.quert_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: -# copy = dict() -# for name in self._named_kw_args: -# if name in kw: -# copy[name] = name -# kw = copy -# 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 -# if self._required_kw_args: -# for name in self._required_kw_args: -# if name not in kw: -# return web.HTTPBadRequest('Missing argument:%s' % name) -# logging.info('Call with args :%s ' % str(kw)) -# try: -# r = self._func(**kw) -# return r -# except Exception as e: -# return dict(error=e.error, data=e.data, message=e.message) - class RequestHandler(object): + logging.info('RequestHandler Start....') def __init__(self, app, fn): self._app = app self._func = fn @@ -157,8 +102,11 @@ def __init__(self, app, 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) + logging.info('RequestHandler init .... ') + logging.info('object %s' % object) async def __call__(self, request): + logging.info('RequestHandler Method request: %s' % request.method) kw = None if self._has_var_kw_arg or self._has_named_kw_args or self._required_kw_args: if request.method == 'POST': @@ -205,44 +153,47 @@ async def __call__(self, request): # check required kw: if self._required_kw_args: for name in self._required_kw_args: - if not name in kw: + if name not 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: + except Exception as e: return dict(error=e.error, data=e.data, message=e.message) 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)) + logging.info('add static %s => %s' % ('/static/', path)) def add_route(app, fn): method = getattr(fn, '__method__', None) path = getattr(fn, '__route__', None) + logging.info('path: %s,method: %s' % (path,method)) 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.signature(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()))) + # logging.info('method: %s,fn: %s' % (method,fn)) app.router.add_route(method, path, RequestHandler(app, fn)) -def add_routes(app, moudle_name): - n = moudle_name.rfind('.') - # logging.info(' n %s ' % str(n)) +def add_routes(app, module_name): + n = module_name.rfind('.') if n == (-1): - mod = __import__(moudle_name, globals(), locals()) + mod = __import__(module_name, globals(), locals()) else: - name = moudle_name[n + 1:] + name = module_name[n + 1:] mod = getattr( - __import__(moudle_name[:n], globals(), locals(), [name]), name) + __import__(module_name[:n], globals(), locals(), [name]), name) for attr in dir(mod): if attr.startswith('_'): continue diff --git a/awesome-python3-webapp/www/handlers.py b/awesome-python3-webapp/www/handlers.py index 3ba13dd..f3f6833 100644 --- a/awesome-python3-webapp/www/handlers.py +++ b/awesome-python3-webapp/www/handlers.py @@ -1,18 +1,25 @@ -import re -import time -import json -import logging -import hashlib -import base64 -import asyncio +import re, time, json, logging, hashlib, base64, asyncio from coroweb import get, post from models import User, Comment, Blog, next_id +# @get('/') +# async def index(request): +# logging.info('users') +# users = await User.findAll() +# return {'__template__': 'test.html', 'users': users} +# +# +# @get('/test') +# async def index(request): +# logging.info('test') +# users = await User.findAll() +# return {'__template__': 'test.html', 'users': users} + +# 测试 @get('/') -def index(request): - logging.info('test User.findAll()') - users = User.findAll() +async def index(request): + users = await User.findAll() return {'__template__': 'test.html', 'users': users} diff --git a/awesome-python3-webapp/www/orm.py b/awesome-python3-webapp/www/orm.py index c5a54ff..8b33dd5 100644 --- a/awesome-python3-webapp/www/orm.py +++ b/awesome-python3-webapp/www/orm.py @@ -9,54 +9,60 @@ logging.basicConfig(level=logging.INFO) -@asyncio.coroutine -def create_pool(loop, **kw): +async def create_pool(loop, **kw): logging.info('create database connection pool...') global __pool - __pool = yield from 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), - loop=loop) + __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), + loop=loop) def log(sql, args=()): logging.info('SQL: %s' % sql) -@asyncio.coroutine -def select(sql, args, size=None): +async def select(sql, args, size=None): log(sql, args) - global __pool - with (yield from __pool) as conn: - cur = yield from conn.cursor(aiomysql.DictCursor) - yield from cur.execute(sql.replace('?', '%s'), args or ()) - if size: - rs = yield from cur.fetchmany(size) - else: - rs = yield from cur.fetchall() - yield from cur.close() - logging.info('rows returned %s' % len(rs)) - return rs - - -def execute(sql, args): - log(sql) - with (yield from __pool) as conn: + async with __pool.get() as conn: + # 等待连接对象返回DictCursor可以通过dict的方式获取数据库对象,需要通过游标对象执行SQL + async with conn.cursor(aiomysql.DictCursor) as cur: + await cur.execute( + sql.replace('?', '%s'), + args) #将sql中的'?'替换为'%s',因为mysql语句中的占位符为%s + #如果传入size' + if size: + resultset = await cur.fetchmany(size) # 从数据库获取指定的行数 + else: + resultset = await cur.fetchall() # 返回所有的结果集 + logging.info('rows returned: %s' % len(resultset)) + return resultset + + +async def execute(sql, args, autocommit=True): + log(sql, args) + async with __pool.get() as conn: + if not autocommit: # 若数据库的事务为非自动提交的,则调用协程启动连接 + await conn.begin() try: - cur = yield from conn.cursor() - yield from cur.execute(sql.replace('?', '%s'), args) - affected = cur.rowcount - yield from cur.close() - except Exception as e: - raise + async with conn.cursor( + aiomysql. + DictCursor) as cur: # 打开一个DictCursor,它与普通游标的不同在于,以dict形式返回结果 + 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 e return affected @@ -163,7 +169,7 @@ def __getattr__(self, key): try: return self[key] except KeyError: - raise AttributeError(r'Model object has no attribute %s' % key) + raise AttributeError(r"'Model' object has no attribute '%s'" % key) def __setattr__(self, key, value): self[key] = value @@ -184,27 +190,29 @@ def getValueOrDefault(self, key): return value @classmethod - @asyncio.coroutine - def find(cls, pk): + async def find(cls, pk): logging.info('find object by primary key') - rs = yield from select('%s where `%s`=?', - (cls.__select__, cls.__primary_key__), [pk], 1) + rs = await select('%s where `%s`=?' % + (cls.__select__, cls.__primary_key__), [pk], 1) if len(rs) == 0: return None return cls(**rs[0]) - @asyncio.coroutine - def save(self): + @classmethod + async def save(self): args = list(map(self.getValueOrDefault, self.__fields__)) args.append(self.getValueOrDefault(self.__primary_key__)) logging.info(args) - rows = yield from execute(self.__insert__, args) + rows = await execute(self.__insert__, args) if rows != 1: logging.warn('failed to insert record: affected rows: %s' % rows) - @asyncio.coroutine - def findAll(cls, where=None, args=None, **kw): + @classmethod + async def findAll(cls, where=None, args=None, **kw): + ' find objects by where clause. ' sql = [cls.__select__] + # logging.info(cls.__select__) + logging.info(select(' '.join(sql), args)) if where: sql.append('where') sql.append(where) @@ -225,5 +233,5 @@ def findAll(cls, where=None, args=None, **kw): args.extend(limit) else: raise ValueError('Invalid limit value: %s' % str(limit)) - rs = select(' '.join(sql), args) + rs = await select(' '.join(sql), args) return [cls(**r) for r in rs] diff --git a/awesome-python3-webapp/www/templates/test.html b/awesome-python3-webapp/www/templates/test.html index f23da43..939356a 100644 --- a/awesome-python3-webapp/www/templates/test.html +++ b/awesome-python3-webapp/www/templates/test.html @@ -6,9 +6,7 @@ - All users
{% for u in users %} -{{ u.name }} / {{ u.email }}
- {% endfor %} +My name is chenshi2