我已经用Python编写JSON-REST API多年了,在那段时间里,我发现工具有了很大的改进。为了向您展示如何使我受益,我将向您展示我如何发展。但是,如果您想跳到我今天使用的工具,请看一下Quart-Schema。
使用Flask / Quart用Python编写JSON-REST API一直很高兴。尚未将API结构(路线,数据等)传达给第三方以及我自己的未来。很难保证API能够像文档所述那样工作。这导致在生产中我不得不避免的问题,错误和滥用。
def():data = request.get_json()todo_id = create_todo(data [" task"],data [" due"])todo = {" id" :todo_id," task&#34 ;:数据[" task"]," due&#34 ;:数据[" due"]}返回jsonify(todo) ,201
效果很好,但没有达到我的目标。本人和第三方用户都必须阅读代码以了解API发送和返回的内容。
这也无助于知道何时发送了错误的数据。例如,到期应为日期,但如果不是,则create_todo函数将引发ValueError,导致API返回500响应。它应该返回400响应,以便第三方和我可以知道这是数据错误,而不是服务器或编程问题。
为了解决简单的Flask设置遇到的文档问题,我尝试了sphinx autohttp扩展,
def():"""创建TODO任务。 ..:quickref:创建一个待办事项。 :reqheader接受:application / json:< json字符串任务:任务描述:< json字符串到期:todo截止日期:> json int id:todo唯一标识符:> json字符串task:任务描述:> json字符串截止日期:待办事项截止日期:resheader内容类型:application / json:状态201:创建的帖子""" data = request.get_json()todo_id = create_todo(data [" task"],data [" due"])todo = {" id&#34 ;: todo_id,& #34; task&#34 ;: data [" task"]," due&#34 ;: data [" due"]}返回jsonify(todo),201
通过手动指定文档字符串中的数据结构,可以创建文档。
尽管这对文档有帮助,但它与简单的Flask示例有相同的问题,因为它不验证发送到路由的数据,也不验证返回的数据。它还对任何短毛绒都无济于事,例如,像data [" duo"]之类的简单错字只会在测试过程中产生错误,或者在生产中变得更糟。
但是,更大的问题是编写文档字符串文档,因为它很耗时,而且我(和我的同事)会忘记做无聊的工作。随着时间的流逝,这会导致文档错误,从而导致不信任和缺乏使用。
我最终停止使用此技术,并返回到更简单的示例,因为此文档最终导致了误导。还很清楚,获胜的文档标准是OpenAPI,而不是sphinx文档。
大约在这个时候,我从用Flask编写API转向了用Quart编写API。现在,本文中的片段将采用Quart,但唯一的区别是使用了async / await关键字,您可以删除它们以获得Flask等效项。
我写Quart的时候就想开始使用async await关键字和库。您可以从示例中删除异步,等待以获取Flask等效项。
我编写的API中发送和接收的数据格式为JSON。这意味着JSONSchema可用于描述结构,并用于验证任何数据与之匹配。这使我能够编写接受JSONSchema的装饰器,以验证请求和响应数据,
从functools import换行,从输入import Any,可调用,Dict导入,从quart导入中止,make_response,请求def(模式:Dict [str,Any])->可调用的:"""验证请求JSON。如果正文中没有JSON,或者JSON无法验证,则将触发400响应。 """验证程序= fastjsonschema。编译(schema)def(func:Callable)->可调用的:异步def(* args:任何,** kwargs:任何)->任意:数据=等待request.get_json()尝试:验证器(数据)除了fastjsonschema.JsonSchemaException:中止(400)其他:返回等待函数(* args,** kwargs)返回包装器返回装饰器def(模式:Dict [ Dict [str,Any]])->可调用的:"""验证响应JSON。架构由状态代码键控。 """验证程序= {状态:fastjsonschema。编译(schema)状态,schema中的schema.items()} def(func:Callable)->可调用的:异步def(* args:任何,** kwargs:任何)->任意:result = await func(* args,** kwargs)response = await make_response(result),如果validator中的response.status_code:validators [response.status_code](await response.get_json())return response return wrapper return decorator
{" type&#34 ;:" object&#34 ;," properties&#34 ;: {" due&#34 ;: {" type&#34 ;:&& 34; string&#34 ;、" format&#34 ;:" date"}," task&#34 ;: {" type&#34 ;:" string& #34;},},"必需":[" due"," task"]," additionalProperties&#34 ;: False,}){ 201:{" type&#34 ;:" object&#34 ;," properties&#34 ;: {" due&#34 ;: {" type&#34 ;: " string&#34 ;、" format&#34 ;:" date"}," id&#34 ;: {" type&#34 ;:&#34 ; number"}," task&#34 ;: {" type&#34 ;:" string"},}," required&#34 ;: [&# 34; due"," id"," task"]," additionalProperties&#34 ;: False,},})异步def():数据=等待请求.get_json()todo_id = create_todo(data [" task"],data [" due"])todo = {" id&#34 ;: todo_id," task&#34 ;: data [" task"]," due&#34 ;: data [" due"]}返回待办事项,201
然后,在开发或为第三方创建API的openapi规范时,可以将JSONSchemas用作文档。
这解决了验证和文档编制问题,同时确保文档保持正确。但是,编写代码与狮身人面像文档字符串一样痛苦(尽管现在无法忽略)。这也没有帮助linters了解我们在处理数据。
为了从整理工具中获得帮助,需要在他们理解的结构中定义数据。方便地,Python 3.7附带了数据类,我们可以使用它们来定义结构,并使IDE和linter可以向我发出任何问题的警报。通过更改装饰器以将数据类转换为JSONSchema,可以改为使用数据类,
类别:到期日:日期任务:str类别(TodoData):id:int异步def()-> Todo:数据=等待请求。get_json()todo_data = TodoData(** data)todo_id = create_todo(todo_data.task,todo_data.due)return Todo(id = todo_id,task = todo_data.task,due = todo_data.due), 201
与以前的任何示例相比,这实际上是令人愉悦的代码编写,并使其(借助IDE自动完成功能)更快。
我一直在尝试使用代码来使数据类装饰器工作一段时间,但这并不是最简单的。值得庆幸的是,我最近遇到了pydantic的数据类,这些数据类可验证参数并可以生成文档的架构。
Quart-Schema是Quart扩展,提供了验证修饰符和自动生成的文档。全文中的示例是,
从datetime从pydantic.dataclasses导入日期从quart导入dataclass从quart_schema导入Quart导入QuartSchema,validate_request,validate_responseapp = Quart(__ name __)QuartSchema(app)class:due:date task:str class(TodoData):id:int async def(数据:TodoData)->待办事项:待办事项=等待create_todo(data)返回待办事项,201
我认为,借助Quart-Schema,我终于以一种非常清晰且易于编写的形式实现了我的所有最初目标。这只有在过去几年中对async / await,类型提示和对Python的数据类改进后才可能实现。
我很想知道您对此的看法-我在Twitter @pdgjones上。