Python基础-项目-day5 Web框架

前言

学习笔记来源-廖雪峰老师
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432339008728d0ddbe19ee594980be3f0644a9371894000

编写一个URL的处理函数

coreweb.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
Python基础- Web 框架 coreweb 处理 URL 操作
'''

import asyncio, os, inspect, logging, functools
from urllib import parse
from aiohttp import web
from apis import APIError

def get(path):
    '''
    URL 的 get 装饰方法
    '''
    def decorator(func):

        # 把一个函数映射为URL处理函数
        @functools.wraps(func)
        def wrapper(*args, **kw):
            return func(*args, **kw)
        wrapper.__method__ = "GET"
        wrapper.__route__ = path
        return wrapper

    return decorator

def post(path):
    '''
    URL 的 post 装饰方法
    '''
    def decorator(func):

        @functools.wraps(func)
        def wrapper(*args, **kw):
            return func(*args, **kw)
        wrapper.__method__ = "POST"
        wrapper.__route__ = path
        return wrapper
    return decorator

def get_required_kw_args(fn):
    args = []
    params = inspect.sinature(fn).parameters

    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY and param.default == inspect.Parameter.empty:
            args.append(name)
    return tuple(args)

def get_name_kw_args(fn):
    args = []
    params = inspect.sinature(fn).parameters

    for name, param in param.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY:
            args.append(name)
    return tuple(args)

def has_name_kw_args(fn):
    params = inspect.sinature(fn).parameters

    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY:
            return True

def ha_var_kw_arg(fn):
    params = inspect(fn).parameters

    for name, param in params.items():
        if param.kind == inspect.Parameter.VAR_KEYWORD:
            return True

def has_request_arg(fn):
    sig = inspect.sinature(fn)
    param = sig.parameters
    found = False

    for name, param in param.items():
        if name == "request":
            found = True
            continue

        if found and (param.kind != inspect.Parameter.VAR_POSITIONAL and param.kind != inspect.Parameter.KEYWORD_ONLY and param.kind != inspect.Parameter.VAR_KEYWORD):
            raise valueError("请求的参数必须the last named parameter in function:%s%s" % (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 = named_kw_args(fn)
        self._required_kw_args = _required_kw_args(fn)

    # 异步
    @asyncio.coroutine
    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()
                # JSON 格式
                if ct.startswith('application/json'):
                    params = yield from request.json()

                    if not isinstance(params, dict):
                        return web.HTTPBadRequest("JSON body must be object(dict)")
                    kw = params
                elif ct.startswith("application/x-www-form-urlencoded") or ct.startswith('multipart/form-data'):
                    params = yield from request.post()
                    kw = dict(**params)
                else:
                    return web.HTTPBadRequest("不支持的 Content-Type: %s" % request.content_type)

            if kw is None:
                kw = dict(**request.match_info)

            else:
                if not self._has_var_kw_arg and self._named_kw_args:
                    # 移除所有未被命名的 kw
                    copy = dict()

                    for name in self._named_kw_args:
                        if name in kw:
                            copy[name] = kw[name]

                    kw = copy

                    # 检查 kw
                    for k, v in request.match_info.items():

                        if k in kw:
                            logging.warning("arg 和 kw arg重复命名了 %s" % k)
                        kw[k] = v

                    if self._required_kw_args:
                        kw["request"] = request

                    # 检查 required kw
                    if self._required_kw_args:
                        for name in self.required_kw_args:
                            if not name in kw:
                                return web.HTTPBadRequest("Missing arg %s" % name)
                    logging.info("call with args : %s" % str(kw))

                    try:
                        r = yield from self._func(**kw)
                        return r
                    except APIError 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))

def add_route(app, fn):
    method = getattr(fn, "__method__", None)
    path = getattr(fn, "__route__", None)

    if path is None or method is None:
        raise ValueError('@get or @post 未定义 in %s.' % str(fn))

    if not asyncio.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn):
        fn = asyncio.coroutine(n)
    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))

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)

包含的依赖文件 apis.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
JSON API definition.
'''
import json, logging, inspect, functools

class APIError(Exception):
    '''
    the base APIError which contains error(required), data(optional) and message(optional).
    '''
    def __init__(self, error, data='', message=''):
        super(APIError, self).__init__(message)
        self.error = error
        self.data = data
        elf.message = message

class APIValueError(APIError):
    '''
    Indicate the input value has error or invalid. The data specifies the error field of input form.
    '''
    def __init__(self, field, message=''):
        super(APIValueError, self).__init__('value:invalid', field, message)

class APIResourceNotFoundError(APIError):
    '''
    Indicate the resource was not found. The data specifies the resource name.
    '''
    def __init__(self, field, message=''):
        super(APIResourceNotFoundError, self).__init__('value:notfound', field, message)

class APIPermissionError(APIError):
    '''
    Indicate the api has no permission.
    '''
    def __init__(self, message=''):
        super(APIPermissionError, self).__init__('permission:forbidden', 'permission', message)

其中handlers.py

# handlers.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'url handlers'

app.py 调用

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Python基础-Web App 骨架

import logging; logging.basicConfig(level = logging.INFO)

import asyncio, os, json, time
from datetime import datetime

import orm
from aiohttp import web
from coreweb import add_routes, add_static
from jinja2 import Environment, FileSystemLoader

def init_jinja2(app, **kw):
    logging.info("初始化 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 = kw.get("path", None)
    if path is None:
        # templates文件夹 放置 html 文件
        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
    logging.info("设置 jinja2 templates 地址为 %s" % path)

    env = Environment(loader = FileSystemLoader(path), **options)
    filters = kw.get("filters", None)

    if filters is not None:
        for name, f in  filters.items():
            env.filters[name] = f

    app['__templating__'] = env

def datetime_filter(t):
    # 时间差
    delta = int(time.time() - t)

    if delta < 60:
        return u'1分钟前'
    if delta < 3600:
        return u'%s分钟前' % (delta // 60)
    if delta < 86400:
        return u'%s小时前' % (delta // 3600)
    if delta < 604800:
        return u'%s天前' % (delta // 86400)

    dt = datetime.fromtimestamp(t)
    return u'%s年%s月%s日' % (dt.year, dt.month, dt.day)

@asyncio.coroutine
def logger_factory(app, handler):

    @asyncio.coroutine
    def logger(request):
        logging.info('Request: %s %s' % (request.method, request.path))
        return (yield from handler(request))
    return logger

@asyncio.coroutine
def data_factory(app, handler):

    @asyncio.coroutine
    def parse_data(request):
        if request.method == 'POST':
            if request.content_type.startswith('application/json'):
                request.__data__ = yield from request.json()
                logging.info('request json: %s' % str(request.__data__))
            elif request.content_type.startswith('application/x-www-form-urlencoded'):
                request.__data__ = yield from request.post()
                logging.info('request form: %s' % str(request.__data__))

        return (yield from handler(request))
    return parse_data

@asyncio.coroutine
def response_factory(app, handler):

    @asyncio.coroutine
    def response(request):
        logging.info('Response handler...')
        r = yield from handler(request)
        if isinstance(r, web.StreamResponse):
            return r

        if isinstance(r, bytes):
            resp = web.Response(body=r)
            resp.content_type = 'application/octet-stream'
            return resp

        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

        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

@asyncio.coroutine
def init(loop):
    yield from orm.create_pool(loop = loop, host = "127.0.0.1", port = 3306, user = "root", password = "", database = "test")

    #创建一个web服务器对象
    app = web.Application(loop=loop, middlewares=[
        logger_factory, response_factory
    ])

    #通过router的指定的方法可以把请求的链接和对应的处理函数关联在一起
    init_jinja2(app, filters=dict(datetime = datetime_filter))
    add_routes(app, "handlers")
    add_static(app)
    #运行web服务器,服务器启动后,有用户在浏览器访问,就可以做出对应的响应
    # 127.0.0.1 本机地址
    srv = yield from loop.create_server(app.make_handler(), "127.0.0.1", 9000)
    logging.info("服务端 http://127.0.0.1:9000....")
    return srv

# 固定写法
loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()

运行效果

D:\python3-webapp-Su\www>python app.py
INFO:root:建立数据库连接池
INFO:root:初始化 jinja2
INFO:root:设置 jinja2 templates 地址为 D:\python3-webapp-Su\www\templates
INFO:root:add_static /static/ -> D:\python3-webapp-Su\www\static
INFO:root:服务端 http://127.0.0.1:9000....
INFO:root:Request: GET /index
INFO:root:Response handler...
INFO:aiohttp.access:127.0.0.1 - - [30/Dec/2017:15:37:26 +0000] "GET /index HTTP/1.1" 404 172 "-" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299"
INFO:root:Request: GET /index
INFO:root:Response handler...
INFO:aiohttp.access:127.0.0.1 - - [30/Dec/2017:15:39:20 +0000] "GET /index HTTP/1.1" 404 172 "-" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299"
INFO:root:Request: GET /
INFO:root:Response handler...
INFO:aiohttp.access:127.0.0.1 - - [30/Dec/2017:15:39:25 +0000] "GET / HTTP/1.1" 404 172 "-" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299"

小结

Web框架实现对前面的 web 和 mysql 的基本调用

提交git

git add .
git commit -m "Python基础-项目-day5 Web框架"
git push -u origin master
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

法迪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值