Skip to main content

立即创建具有自动类型转换、JSON RPC 和 Swagger UI 的 HTTP API。只需添加方法!

项目描述

标识

构建状态 覆盖状态 支持 Python 版本 3.7+

即时 API

立即创建具有自动类型转换、JSON RPC 和 Swagger UI 的 HTTP API。所有无聊的事情都为您完成,因此您可以专注于有趣的逻辑,同时拥有一个很棒的 AP​​I。只需添加方法!

安装

pip install instant-api

或者也安装相应的 Python 客户端:

pip install 'instant-api[client]'

基本用法

只需编写一些 Python 函数或方法并装饰它们即可。参数和返回值需要类型注释,以便您可以将它们转换为 JSON 或从 JSON 转换。您可以将数据类用于复杂值。

from dataclasses import dataclass
from flask import Flask
from instant_api import InstantAPI

app = Flask(__name__)

@dataclass
class Point:
    x: int
    y: int

@InstantAPI(app)
class Methods:
    def translate(self, p: Point, dx: int, dy: int) -> Point:
        """Move a point by dx and dy."""
        return Point(p.x + dx, p.y + dy)

    def scale(self, p: Point, factor: int) -> Point:
        """Scale a point away from the origin by factor."""
        return Point(p.x * factor, p.y * factor)

if __name__ == '__main__':
    app.run()

访问http://127.0.0.1:5000/apidocs/获取完整的 Swagger GUI,以交互方式试用 API:

招摇概述

与 API 交谈instant_client

如果您需要 Python 客户端,我强烈推荐配套库Instant_client。它在客户端处理数据转换,并与开发人员工具配合得很好。基本用法如下:

from server import Methods, Point  # the classes we defined above
from instant_client import InstantClient

# The type hint is a lie, but your linter/IDE doesn't know that!
methods: Methods = InstantClient("http://127.0.0.1:5000/api/", Methods()).methods

assert methods.scale(Point(1, 2), factor=3) == Point(3, 6)

这看起来很像Methods.scale()直接调用它,这是重点(不是双关语),但实际上它确实向服务器发送了一个 HTTP 请求。

使用方法路径而不是 JSON-RPC

API 自动提供两种风格,客户可以选择他们喜欢的通信方式:

  1. 中央 JSON-RPC 端点,完全遵循 JSON-RPC 协议规范,最容易与标准客户端库一起使用。
  2. 方法路径,这使得人类手动编写请求(尤其是在 Swagger GUI 中)更容易一些,并且更多地使用 HTTP 的特性。

要向方法路径发出请求,请在 URL 末尾包含方法名称,并在 JSON 正文中发送参数对象。这是这样一个调用的样子:

import requests

response = requests.post(
    'http://127.0.0.1:5000/api/scale',
    json={
        'p': {'x': 1, 'y': 2}, 
        'factor': 3,
    },
)

assert response.json()['result'] == {'x': 3, 'y': 6}

响应将是一个完整的 JSON-RPC 响应,就像您发出了一个完整的 JSON-RPC 请求一样。特别是,它将有一个result或一个error键。

HTTP 状态码

中央 JSON-RPC 端点将始终(除非请求未经过身份验证,请参见下文)返回代码 HTTP 状态代码 200(OK),即使出现错误,正如标准客户端所期望的那样。

由于方法路径不完全是 JSON-RPC,因此它们可能会在出现错误时返回不同的代码。特别是一个无效的请求将导致 400,而方法内部未处理的错误将导致 500。

如果你在方法内部提出一个InstantError,你可以给它一个http_code,例如raise InstantError(..., http_code=404)仅当方法由方法路径而不是 JSON-RPC 端点调用时,这将成为 HTTP 状态代码。

全局 API 配置

该类InstantAPI需要一个 Flask 应用程序并具有以下可选的仅关键字参数:

  • path是一个字符串(默认值'/api/'),它将为 JSON RPC 添加到应用程序的端点。每个方法也会有一个基于函数名称的路径,例如/api/scale/api/translate- 请参阅使用方法路径而不是 JSON-RPC。指定不同的字符串以更改所有这些路径。
  • swagger_kwargs是要传递给flasgger.Swagger应用程序调用的构造函数的关键字参数的字典(默认为空)。例如,您可以通过将字典传递给config来自定义 Swagger UI :
api = InstantAPI(app, swagger_kwargs={"config": {"specs_route": "/my_apidocs/", ...}})

处理错误

当服务器遇到错误时,响应将包含一个error键(而不是一个result)和一个包含codedata和的对象message。例如,如果为方法提供了无效参数,则错误的详细信息(aTypeError或 marshmallow ValidationError)将包含在响应中。错误代码将是-32602。响应 JSON 如下所示:

{
  "error": {
    "code": -32602,
    "data": {
      "p": {
        "y": [
          "Not a valid integer."
        ]
      }
    },
    "message": "marshmallow.exceptions.ValidationError: {'p': {'y': ['Not a valid integer.']}}"
  },
  "id": 0,
  "jsonrpc": "2.0"
}

您可以在JSON-RPC 协议规范中找到更多详细信息,包括一些典型错误的标准错误代码。

要返回您自己的自定义错误信息,InstantError请在您的方法中引发一个,例如:

from instant_api import InstantAPI, InstantError

@InstantAPI(app)
class Methods:
    def find_thing(self, thing_id: int) -> Thing:
        ...
        raise InstantError(
            code=123,
            message="Thing not found anywhere at all",
            data=["not here", "or here"],
        )

响应将是:

{
  "error": {
    "code": 123,
    "data": [
      "not here",
      "or here"
    ],
    "message": "Thing not found anywhere at all"
  },
  "id": 0,
  "jsonrpc": "2.0"
}

HTTP 状态代码取决于您使用的 API 的风格 - 请参阅本节

附加方法

可以使用函数、类或任意对象调用实例,InstantAPI以向 API 添加方法。对于函数和类,实例可以作为装饰器来调用它。

正如您所期望的,装饰单个函数将其添加为 API 方法。函数本身不应该是类的方法,因为没有办法提供第一个参数self

使用对象调用InstantAPI将搜索其所有属性并将名称不以下划线 ( _) 开头的所有函数(包括绑定方法)添加到 API。

装饰一个类将构造一个不带参数的类的实例,然后如上所述调用生成的对象。这意味着它将添加绑定方法,因此self忽略该参数。

所以给定api = InstantAPI(app),所有这些都是等价的:

@api
def foo(bar: Bar) -> Spam:
    ...

api(foo)

@api
class Methods:
    def foo(self, bar: Bar) -> Spam:
        ...

api(Methods)

api(Methods())

如果函数缺少任何参数或返回值的类型注释,则会引发异常。如果您不希望将方法添加到 API,请在其名称前加上下划线,例如def _foo(...).

在 Swagger UI 中自定义方法路径

直接设置属性

对于每种方法,flasgger.SwaggerView都会创建一个。swagger_view_attrs您可以通过在装饰器的参数中传递类属性字典来自定义视图。例如:

@api(swagger_view_attrs={"tags": ["Stuff"]})
def foo(...)

这将放入Swagger UIfooStuff部分。

请注意,以下是Python 3.9 之前的无效语法:

@InstantAPI(app)(swagger_view_attrs={"tags": ["Stuff"]})
def foo(...)

通过文档字符串设置摘要和描述

如果一个方法有一个文档字符串,它的第一行将是summary 方法路径的 OpenAPI 规范中的,在 Swagger UI 的概述中可见。description当路径在 UI 中展开时,剩余的行将变为可见。

自定义全局请求和方法处理

要直接控制如何处理请求,请创建一个子类InstantAPI并覆盖以下方法之一:

  • handle_request(self, method)是将原始烧瓶请求转换为响应的入口点。如果method为 None,则向通用 JSON-RPC 路径发出请求。否则method是请求路径末尾带有方法名称的字符串。
  • call_method(self, func, *args, **kwargs)使用给定的参数调用 API 方法func。这里的参数还没有根据函数类型注释反序列化。

super()除非您正在做一些非常奇怪的事情,否则请记住在某处调用父方法。

验证

要求对请求进行身份验证:

  1. 创建 的子类InstantAPI
  2. 覆盖方法def is_authenticated(self):
  3. 返回一个布尔值:True如果用户应该有权访问(基于全局 Flaskrequest对象),False是否应该被拒绝。
  4. 使用子类的实例来装饰方法。

未经身份验证的请求将收到带有非 JSON 正文的 403 响应。

依赖项

  • datafunctions(它反过来使用marshmallow)被两者使用instant_apiinstant_client透明地处理两端的 JSON 和 Python 类之间的转换。
  • Flasgger提供了 Swagger UI。
  • json-rpc处理协议。

因为其他库做了很多工作,所以instant_api它本身就是一个非常小的库,基本上包含在一个小文件中。您可能可以很容易地阅读源代码并根据您的需要进行调整。

为什么要使用这个库?

这个库从FastAPI中获得了明显的灵感。那么我为什么要写这个,你为什么要使用它呢?

  • 真的很棒instant_client,它让您感觉就像在本地调用方法(并且您的 IDE 会像您一样帮助您),即使它们是远程执行的。

  • 它更容易设置,因为您不必指定路径或 HTTP 方法。如果您将所有内容分组到一个类中,则只需对整个内容进行一次装饰即可。这几乎是尽可能少的样板代码。

  • JSON-RPC 非常酷。

    • 它是一种流行的标准协议,具有以多种语言编写的客户端库。
    • 它可以让您进行批量请求:发送一组请求,返回一组响应。
    • 当您不关心结果时,它支持通知。
  • 当您想使用 Flask(例如,使用其他 Flask 库),或者更一般地说,如果您想要一个 WSGI 应用程序而不必将其嵌入到 FastAPI 中,这非常棒。

    当我的用例出现时,我考虑过 FastAPI,但能够使用 Flask(特别是 Plotly Dash)是一项硬性要求。API 只是一个较大项目的一小部分,所以我不希望 FastAPI “负责”。

    我尝试查看 FastAPI 的源代码以提取我需要的位,例如从类型注释生成 Swagger 规范,但代码非常复杂,这不值得。所以我编写了我自己的版本,其中依赖项以一种很好的模块化方式完成了类似的艰苦工作。剩下的是一个小的、可读的库,它在很大程度上只是将其他东西连接在一起。这样,如果其他人的情况与我相同,他们的需求略有不同,他们现在可以调整源代码。

下载文件

下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。

内置分布

instant_api-0.2.0-py3-none-any.whl (12.4 kB 查看哈希)

已上传 py3