用Python优雅的封装Upsource HTTP API
背景
Upsource是一个非常出色的CodeReview工具,在和其他系统联动时需要通过API进行访问,于是使用python对其API进行封装,过程中一步一步思考如何使用python装饰器简化封装,达到十分简洁的封装。
本文主要介绍利用动态生成函数和装饰器两种方法简化API封装的实现。
Upsource HTTP API说明
1 | Upsource API is an RPC-style HTTP API. You can make calls using HTTP GET and POST. All data is sent and received as JSON. While the RPC methods don't enforce the use of a specific HTTP method, we recommend that you conform to HTTP semantics by using GET for retrieving data (e.g. getRevisionsList) and POST for modifying data (e.g. createReview). |
根据文档描述,每个接口的调用包含methodName和JSON格式的参数,于是我们开始用python进行封装。
简单实现
最简单的就是实现就是每个API加个函数
1 | class Upsource: |
上述实现每个API都需要实现一个函数做转换,有没有优化的空间?
下面我们介绍两种方法进行优化:
- 动态生成函数
- 装饰器
动态生成函数
核心思路
python使用locals()可以获取当前上下文exec()可以执行在字符串中的python代码,所以可以动态生成函数,然后利用setattr()将生成的函数
实现探索
我们可以利用下划线转驼峰,将参数名的转换做优化,配合locals()获取参数列表
下划线转驼峰函数
1 | def underline2hump(underline_str): |
1 | def get_branch_info(project_id, branch): |
于是,每个API函数可以优化成这样
1 | def get_branch_info(self, project_id, branch): |
函数体变成公共的了, 所以我们可以再写个公共函数,这样每个API都可以调用这个公共函数
1 | def request(self, data): |
每个API函数调用request, 获取上一层调用名,请求参数透传;
注意: 这里不能用locals(),应为locals只是针对调用栈的本层命名空间,暂时没找到方法获取上一层调用函数的参数列表
所以每个API函数应该都有如下函数体
1 | def api_name(self, params1, params2, ...): |
我们可以利用python的exec()将字符串代码执行,这样我们就可以动态生成形如上述格式的函数,并且函数名和参数可变
1 | REQUEST_MAP = { |
由此,我们只需要修改REQUEST_MAP就能实现API封装。
这个实现有个小缺点:由于函数是运行时生成的,所以编辑器没法索引定义,可能导致误报。
使用装饰器进行优化
核心思路
利用装饰器+inspect库获取函数参数名及参数值,进行参数预处理,然后进行调用
实现探索
1 | import inspect |
完整代码
动态生成
1 | #!/usr/local/bin/python3 |