Skip to content

Latest commit

 

History

History
185 lines (142 loc) · 7.9 KB

File metadata and controls

185 lines (142 loc) · 7.9 KB

蓝鲸云 API 客户端

本项目基于 requests,提供了一种基于配置构建 SDK 的方案。

Features

  • 同时支持网关 API 与组件 API,使用体验一致
  • 支持丰富的请求参数,包括魔法参数 data,requests.request 全部参数(除 data)、路径参数
    • 魔法参数 data,对于 GET/HEAD/OPTIONS 请求,参数将转换为 QueryString,其它请求方法,则转换为 json 格式的 Body
    • 支持从 django settings,Cookies 获取认证数据
    • verify 默认值为 True,请求 HTTPS 接口更安全
  • 支持对 session 进行更新细粒度的控制,支持复用 session 的连接
  • 灵活的响应处理方式
    • 支持校验响应状态码,获取响应的 json 数据
    • 支持获取原始的 requests Response 对象,进行更细粒度的控制
  • 统一采用异常方案,出错时触发异常,如用户认证失败,请求状态码错误,请求网关超频,请求结果非 JSON 等
  • 详细的错误日志,触发异常时,将打印请求的 curl 语句
  • 支持数据懒加载,减小内存消耗
  • 对 IDE 开发友好,SDK 支持常见 IDE 智能提示及补全;
  • 兼容 Python2 及 Python3 的类型补全;

SDK 使用样例

1. 使用 Client

1.1 直接调用 Client,并解析响应

该方式下,SDK 会根据配置自动做请求和响应的转换和解析,隐藏文档已描述的通用细节(如参数位置,响应格式等),用户仅需关注具体字段传递,以减轻使用成本。

from demo.client import Client

# 需提供网关地址 endpoint,环境 stage 默认为 prod
client = Client(stage="prod", endpoint="http://bkapi.example.com/api/test/")
# 根据需求,提供应用认证、用户认证信息
client.update_bkapi_authorization(
    bk_app_code="xxx",
    bk_app_secret="yyy",
    bk_username="admin",
)

# 发起请求,检查 response.raise_for_status(),返回 response.json() 数据
result = client.api.test({"key": "value"})
print(result["ok"])

1.2 直接调用 Client,不解析响应

该方式下,SDK 不会有过多的封装,仅做参数传递,调用 requests 进行请求后返回响应,用户对请求流程有更细粒度的控制。

from demo.client import Client

# 需提供网关地址 endpoint,环境 stage 默认为 prod
client = Client(stage="prod", endpoint="http://bkapi.example.com/api/test/")
# 根据需求,提供应用认证、用户认证信息
client.update_bkapi_authorization(
    bk_app_code="xxx",
    bk_app_secret="yyy",
    bk_username="admin",
)

# 发起请求,但不对响应进行解析,直接返回 requests Response 对象,用户可对响应进行更细粒度的控制
response = client.api.test.request({"key": "value"})
result = response.json()
print(result["ok"])

2. 使用 shortcuts,简化 Client 创建

为降低使用成本,SDK 提供了更简单地创建 Client 的方式:get_client_by_request、get_client_by_username

2.1 使用 get_client_by_request

此方式需提供 django request 对象

get_client_by_request

from demo.shortcuts import get_client_by_request

# 需提供 django request,client 可从 request 中获取当前用户名或从 Cookies 中获取用户登录态
# 并且,支持从 django settings 获取默认的 bk_app_code、bk_app_secret、endpoint,也可通过参数指定
client = get_client_by_request(request)
result = client.api.test({"key": "value"})
print(result["ok])

2.2 使用 get_client_by_username

get_client_by_username

from demo.shortcuts import get_client_by_username

# 支持从 django settings 获取默认的 bk_app_code、bk_app_secret、endpoint,也可通过参数指定
client = get_client_by_username("admin")
result = client.api.test({"key": "value"})
print(result["ok])

3. 复用 session

支持复用 session 连接,提高请求效率

from demo.shortcuts import get_client_by_username

client = get_client_by_username("admin")
with client:
    result = client.api.test({"key": "value"})
    print(result["ok])

模型

主要模型定义的类关系请参考 class_diagram.md

Session

requests.Session 的基础之上,增加特有业务逻辑封装:

  • 公共参数设置
  • 超时设置
  • 路径参数设置

同时扩展原有 hooks 机制,为后续进阶需求提供扩展。 session 管理状态及完成每一个接口调用。

Operation

借用 openapi 概念,表示一个路径和方法绑定的操作,相当于网关中的资源及 ESB 中的组件(对应的某个方法),作为配置及调用入口,封装以下属性:

  • 方法
  • 路径
  • 默认配置

Operation 不发起具体的请求,对应的请求会在调用时交给 Manager 完成。

Manager

管理 Session 和 Operation,作为整体的“门面”,可以扩展实现为一个 Client。 其本身不保存状态,而是提供封装,将状态转换成 Session 的基本属性。 其本身也不完成接口调用,而是管理调用流程,处理好请求创建,响应解析和异常处理等步骤。

OperationGroup

Operation 的分组,在 ESB 中,可以用系统名作为分组,而在网关中,即使不区分系统,但为了避免资源和 Client 自身属性的冲突,也使用默认分组管理。

Client

Manager 的子类,管理 OperationGroups 以及其定义的 Operations,针对具体的业务场景中,提供更符合业务流程的封装。

易用性设计

在现有的网关文档中,有较大的一类文档基于程序自动生成,其中虽有描述请求方法,认证方式,调用示例,但因为基于模板生成的原因,其内容较为死板和繁琐。如果由人去机械化地拼接接口的每个配置细节,因为这种方式是反人性的,会有较大的出错概率,由此会带来额外的不必要的咨询和反馈。因此希望 SDK 能够自动帮用户去处理这些机械化的配置,只需要关注核心的调用参数即可完成任务。此外,SDK 本身又可看做某个版本的离线文档,通过 SDK 合理的智能提示和补全,也可以免除用户的查找和排除成本。

接口定义

在接口的设计上,有以下目标:

  • 避免覆盖和重写 __init__ 方法;
  • 使用静态显式的定义方式;
  • 为了减轻内存压力,使用懒加载进行初始化;
  • 没有具体的业务逻辑;

因此接口定义使用类属性 + property 的方式进行:

class Group(OperationGroup):
    test = bind_property(Operation, name="test", method="GET", path="/test/")

如上示例,声明了一个 API 分组 Grouptest 是该分组下注册在网关的一个资源名称,其方法为 GET,资源路径为 /test/

class Client(APIGatewayClient):
    api = bind_property(Group)

上方示例基于 Group 这个 api 分组定义了一个网关客户端,使用 Client 可直接调用这个网关下的所有资源接口。 在接口定义中,bind_property 方法实现了懒加载属性的功能,在调用时自动初始化对应类型,同时基于类型注解实现了泛型,可以帮助 IDE 建立类型系统。

IDE 优化

智能补全

得益于 bind_property 实现的泛型,IDE 可以完成 Client -> Group -> Operation 整个调用链的智能补全。而 Operation 自身的补全方式,完全基于 Operation 定义,仅需维护本项目即可。

接口文档

为了优化 Python3 的调用逻辑,同时兼容 Python2 的使用,SDK 生成定义的同时,也会生成对应的存根文件(.pyi),将接口文档作为 docstring,在 IDE 中引用即可看到文档:

class Group(OperationGroup):
    @property
    def test(self) -> Operation:
        """test api"""

class Client(APIGatewayClient):
    @property
    def api(self) -> Group:
        """api resources"""

存根文件在运行时不会被执行,因此使用等价的 property 描述资源定义,通过 docstring 暴露文档(也是唯一方式)。