使用flask restful时可以通过reqparse.RequestParser来方便的获取请求参数以及对参数合法性做校验.下面完整的示例

# vies/ticket.py
import six
from flask_restful import Resource, reqparse, marshal
from opsflow.services import format_service
from opsflow.services.ticket_service.ticket_base_service import TicketBaseService


ticket_list_parser = reqparse.RequestParser()
ticket_list_parser.add_argument('per_page', type=int, location='args')
ticket_list_parser.add_argument('page', type=int, location='args')
ticket_list_parser.add_argument('title', type=six.text_type, location='args')  #因为title可能会包含中文,所以使用six也可以不指定type


class TicketListApi(Resource):
    """
    工单列表
    """
    def get(self):
        request_data_dict = ticket_list_parser.parse_args()
        for key in list(request_data_dict.keys()):
            if request_data_dict.get(key) is None:
                del (request_data_dict[key])
        page = request_data_dict.get('page', 1)
        per_page = request_data_dict.get('per_page', 10)
        title = request_data_dict.get('title', '')
        ticket_objs, msg = TicketBaseService.get_ticket_list(page, per_page, title)
        if ticket_fields is not False:
            response_data = dict(value=[marshal(ticket_obj, ticket_fields) for ticket_obj in ticket_objs.items], total=ticket_objs.total, page=ticket_objs.page, per_page=ticket_objs.per_page)
            return format_service.response(200, '', response_data)
        else:
            return format_service.response(500, msg, '')

说明:

add_argument中通过指定参数名、参数类型、参数获取方式来获取参数对象并支持做合法性校验

第一个参数是需要获取的参数的名称

参数type: 参数指的类型, 如果参数中可能包含中文需要使用six.text_type. 或直接不指定type

参数location: 获取参数的方式,可选的有args(url中获取)、json(json类型的)、form(表单方式提交)

参数required:是否必要,默认非必要提供

参数help:针对必要的参数,如果请求时没有提供,则会返回help中相应的信息


注意:

RequestParser可以通过在resource class外部指定,或者在class内部通过__init__的方式指定。但是如果在__init__中指定, 则无法区分get\post等method使用不同的参数规则,__init__用法如下

class TicketListApi(Resource):
    """
    工单列表
    """
    def __init__(self):
        self.ticket_list_parser = reqparse.RequestParser()
        self.ticket_list_parser.add_argument('per_page', type=int, location='args')
        
    def get(self):
        request_data_dict = self.ticket_list_parser.parse_args()
        for key in list(request_data_dict.keys()):
            if request_data_dict.get(key) is None:
                del (request_data_dict[key])
        page = request_data_dict.get('page', 1)
        per_page = request_data_dict.get('per_page', 10)
        title = request_data_dict.get('title', '')
        ticket_objs, msg = TicketBaseService.get_ticket_list(page, per_page, title)
        if ticket_fields is not False:
            response_data = dict(value=[marshal(ticket_obj, ticket_fields) for ticket_obj in ticket_objs.items], total=ticket_objs.total, page=ticket_objs.page, per_page=ticket_objs.per_page)
            return format_service.response(200, '', response_data)
        else:
            return format_service.response(500, msg, '')



考虑搭建python内部仓库的原因主要有两点:

  1. 公司生产区无法访问官方仓库,无法安装python包

  2. 使用python开发众多系统会有一些通用的功能,如统一登录、统一权限等等。可以将这些功能整合到一个内部通用的python包来分发,避免重复工作,升级管理也比较方便

在搭建内部仓库时,首先要考虑的是选型,在深入调研了pypiserver、devpi后,我选择了devpi.原因如下:

  1. devpi支持本地缓存(只需在部署仓库的服务器上开启外网访问,需要安装包的服务器只需要与仓库连通)

  2. devpi支持从其他python源下载包后缓存到本地,而不从官方仓库下载(国内访问官方参考比较慢)--这个是与devpi的开发人员沟通后才搞定的。pypiserver只支持重定向(仓库中不存在时让需要安装包的服务器从其他源下载,这种对于服务器无法访问外网的情况就无法使用)。有兴趣的可以看下  how to change default mirror url pypi.python.org/simple/ with devpi-server 


下面是一些我认为比较重要的地方,详细文档可以直接看官方文档

  1. devpi 是分为server、client、web几个部分的,通过pip安装时会自动安装这三个库

  2. 一些配置信息可以通过client 来设置(如创建用户、设置密码、配置mirror_url等)

  3. 安装完成后可以通过如下命令来启用: devpi-server --port 4041 --serverdir /data/project/devpiserver --outside-url=http://pypi.xxxxx.com    可以通过supervisor启动多个端口,然后nginx做反向代理



以下一些命令:

  • 客户端连接服务端:

    devpi use http://pypi.xxx.co

  • 首次登陆:

    devpi login root --password ''   (首次启动密码为空)

  • 修改密码:

    devpi user -m root password=123

  • 退出登陆:

    devpi logoff

  • 创建新用户

    devpi user -c alice password=456  email=alice@example.com

  • 登陆新用户

    devpi login alice --password=456

  • 创建索引

    devpi index -c dev bases=root/pypi

  • 使用索引

    devpi use alice/dev 

  • 上传包

    devpi upload   (在setup.py所在目录下执行)

    devpi upload --with-docs  (支持sphinx创建的文档,需要docs目录和setup.py在同个目录下)

  • 设置当仓库中不存在包时 从豆瓣下载包缓存到本地(默认是从官方源下载的)

    devpi index alice/dev mirror_url="https://pypi.doubanio.com/simple/"

  • 修改devpi web的首页

    如果想要定制首页,可以修改 site-packages/devpi_web/templates/root.pt

下面是效果图:

image.pngimage.png

image.png


服务是centos6,默认python是2.6的。安装完virtualenvwrapper执行source /usr/bin/virtualenvwrapper.sh初始化时出现如下错误:

Traceback (most recent call last):
  File "/usr/lib64/python2.6/runpy.py", line 122, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/usr/lib64/python2.6/runpy.py", line 34, in _run_code
    exec code in run_globals
  File "/usr/lib/python2.6/site-packages/virtualenvwrapper/hook_loader.py", line 16, in <module>
    from stevedore import ExtensionManager
  File "/usr/lib/python2.6/site-packages/stevedore/__init__.py", line 23, in <module>
    LOG.addHandler(logging.NullHandler())
AttributeError: 'module' object has no attribute 'NullHandler'
virtualenvwrapper.sh: There was a problem running the initialization hooks.


原因python2.6的loggin模块没有NullHandler.解决方法如下:

修改

/usr/lib/python2.6/site-packages/stevedore/__init__.py

将最后一行的LOG.addHandler(logging.NullHandler())内容改为:

try:
    from logging import NullHandler
except ImportError:
    class NullHandler(logging.Handler):
        def emit(self, record):
            pass

LOG.addHandler(NullHandler())



因为这些天离职换工作,调休期间有点时间所以把本网站后端代码重写了,提高了站点的响应时间。在文章访问计数有些心得。分享如下:


文章详情访问地址为: http://www/loonapp.com/blog/*/。 简单点的做法是直接在view方法(django, 其他框架如tornado为handle)时从数据库中取出该页面的访问次数加1。该做法在并发较大时候多数据库压力较大。因此采用将文章访问次数存到redis,当数量为N的整数倍则更新数据库,同时redis中key加1。 这样减少了这数据库n-1/n的压力, 也避免了因为大并发导致的数据库+操作锁问题

def blog_view(request, blog_id):
    """
    文章查看
    :param request:
    :param blog_id:
    :return:
    """
    BlogService.update_view_count(int(blog_id))
    ......
# BlogService.py
@staticmethod
def update_view_count(blog_id):
    """
    更新文章访问次数,计算准确访问次数的时候需要把这个也加上
    :param blog_id:
    :return:
    """
    key_flag = 'blog_view_count_%d' % blog_id
    view_count = cache.get(key_flag)  # 使用django-redis-cache,settings中配置redis
    if view_count != None:
        cache.incr(key_flag)  # 加1
        if not (view_count % 10):   # 当发现访问次数是10的倍数, 就更新数据库中文章访问次数
            Blog.objects.filter(id=blog_id).update(view_count=view_count)

    else:  # 缓存中没有访问次数记录,则从数据库中获取
        view_count = Blog.objects.get(id=blog_id).view_count
        cache.set(key_flag, view_count+1, None)  # 永不过期