python 中使用装饰器来统一检查 flask 用户权限

最近在一个项目中,需要判断 restful 接口函数传入的时候,是否之前已经登录状态是某个特定用户,以及该用户有没有指定的权限。检查下来如果没有的话,立刻返回错误,中断功能。

遮掩的场景虽然也可以通过标准的调用函数来操作,但都不如用装饰器来得简单。都知道装饰器好用不好写,废话不说,先来看看这个场景怎么实现,还是有一定的通用性的。

def validate_current_is_admin(f):
    @functools.wraps(f)
    def decorated_function(*args, **kws):
        # 需要在登录状态调用, 检查是否为有admin权限的用户登录,
        # 如果不是,返回错误码;
        if g.user.user_name != 'admin':
            raise CustomFlaskErr(USER_MUST_HAS_ADMIN_PRIVILEGE, status_code=401)

        # 验证权限是否为 admin, 不是的话,返回401错误
        if g.user.role_id != Permission.ADMIN:
            raise CustomFlaskErr(USER_MUST_HAS_ADMIN_PRIVILEGE, status_code=401)

        return f(*args, **kws)

    return decorated_function

 

这是一个标准的装饰器的写法,如果你要写一个简单的装饰器,整个框架可以参考。

装饰器调用举例:

@app.route('/api/create_user', methods=['POST'])
@auth.login_required
@validate_current_is_admin
def create_user():

    # 获得参数
    user_name = request.json.get('user_name')
    password = request.json.get('password')
......

 

核心代码的业务逻辑也不复杂,根据 flask 的 g 对象中预存的用户 user 进行检查处理,flask 的这些定义非常灵活,flask.g 怎么使用可以查看 flask 的文档。

这里的user以及相关的属性属于具体业务逻辑,就不展开解释了,可以望文生义。

如果检查下来不符合的话,会调用自定义的 flask 错误,这部分内容可以查看之前写的 python flask 写 api 如何返回自定义错误

因为不对args和kws这些参数进行解析和处理,所处理的是 flask 全局对象。最后将参数都原路打包返回即可,没有问题的话交给使用装饰器的代码继续处理。

这个例子比较简单,主要还是熟悉装饰器的基本用法。

(首发简书

python flask 写 api 如何返回自定义错误

在 python 开发中,利用 flask 写 restful api 函数的时候,除了标准的400、500等这些返回码通过 abort() 返回以外,怎么另外返回自定义的错误代码和信息呢?

我们碰到的业务场景是对于api 输入参数的各类校验以及在业务逻辑执行的时候,都会返回统一的400代码,同时也会返回我们约定的描述详细错误的代码以及描述字符串,提供给调用方来处理,这样可以让其用户体验做得更好,同时详细错误代码和描述字符串也会自动打印在 log 日志中。

flask 的官方文档中告诉我们:

默认情况下,错误代码会显示一个黑白的错误页面。如果你要定制错误页面, 可以使用 errorhandler() 装饰器

在写 restful api 的时候,并没有页面可以返回,我们可以在 flask 提供的代码基础上稍加改造如下。

在你的初始化 flask app 的相关代码中加入下面两个函数:

@app.errorhandler(CustomFlaskErr)
def handle_flask_error(error):

    # response 的 json 内容为自定义错误代码和错误信息
    response = jsonify(error.to_dict())

    # response 返回 error 发生时定义的标准错误代码
    response.status_code = error.status_code

    return response

 

class CustomFlaskErr(Exception):

    # 默认的返回码
    status_code = 400

    # 自己定义了一个 return_code,作为更细颗粒度的错误代码
    def __init__(self, return_code=None, status_code=None, payload=None):
        Exception.__init__(self)
        self.return_code = return_code
        if status_code is not None:
            self.status_code = status_code
        self.payload = payload

    # 构造要返回的错误代码和错误信息的 dict
    def to_dict(self):
        rv = dict(self.payload or ())

        # 增加 dict key: return code
        rv['return_code'] = self.return_code

        # 增加 dict key: message, 具体内容由常量定义文件中通过 return_code 转化而来
        rv['message'] = J_MSG[self.return_code]

        # 日志打印
        logger.warning(J_MSG[self.return_code])

        return rv

 

CustomFlaskErr 是我们自己写的处理错误的类,然后通过 @app.errorhandler(CustomFlaskErr) 这个装饰器在 flask 中注册。

具体功能在注释里基本都写了,我们看一下怎么使用这个自定义错误处理器。

# 用户名输入为空
    if user_name is None:
        raise CustomFlaskErr(USER_NAME_ILLEGAL, status_code=400)

 

当需要处理某个错误的时候,rasie 刚才的 CustomFlaskErr,传递另外定义好的自己的错误代码,以及标准的返回代码;

上面说的常量定义文件可以参考如下:

USER_ALREADY_EXISTS = 20001  # 用户已经存在
J_MSG = {USER_ALREADY_EXISTS: 'user already exists'}

 

通过这样的机制,就做到了在具体 restful api 的业务逻辑代码中简单的进行各类自定义错误的处理,所有的错误处理是集中的,细颗粒度的错误代码和消息也是集中维护,便于扩展。

flask 官方文档和一些网上的资料都说比较简单,实践中摸索了这样的实现方式供参考。

(首发简书

学习 python 这一年

大约2015年4月到5月开始,正式学习 python,发现之前大概在2009年左右也学过,可能当时没有坚持下去。

记得去年开始学 python 的时候,1个月左右,给同事写了一个基于 csv 处理的小程序,当时就被 python 的简洁优雅所倾倒。

然后边学边写东西,本来为公司 zion 框架写一个 dsl,当时水平还不行,所以写成了一个 sql 的包装工具 r2,用来在前端业务逻辑调用后台数据库的时候简化 sql 语句写法以及做到和数据库无关。

真正让我自己 python 水平感觉有点突破的是几个事情:

1 写真正生产上的应用。需要考虑的事情非常多,比如一个简单的 r2,就让我学到了 log、conf、import、class 等很多基本知识的应用,以及单元测试、性能测试。

2 教别人 python。这是让自己的基础知识巩固最好的办法之一。要把别人教会,且处理各类奇怪的运行错误,很有挑战。这样的结果,是对诸如列表、字符串、元组、字典、函数、循环等基本概念非常清楚。不管多复杂的程序,90%的代码其实还是基本的操作,在这90%的代码设计和编写的时候可以减少错误,节约时间,自然有用。

3 看资料。晚上有大量的英文和中文的 python 资料,绝大多数都写的很好,受益匪浅。

在我的推进下,公司也在个别项目上开始使用 python,通过 flask 来构建 restful 接口,机器学习也基于 keras 和 tensorflow 开始了实际的应用。

我相信,进步会是跳跃式的,而这还是仅仅发生在一年间,python 在胶水语言的应用,连接 js、php、java 和 moble 开发上面,以及 python 在人工智能、机器学习上这几年强劲的表现,都让人激动不已。