项目描述
FastAPI-icontract是一个FastAPI扩展,用于设计-by-contract,它利用icontract允许你在FastAPI端点中指定和执行代码合约。
根据您的设置,FastAPI-icontract将
添加合约到API的好处
在FastAPI开发中执行代码合约为API级别的更系统化设计提供了新的途径
合约是规范的重要组成部分。
与人类语言不同,用代码编写的合约是明确的。
合约是自动可验证的。
您的客户可以放心,您实际上运行了它们。FastAPI-icontract将指定生产中运行的合约以及仅在测试期间验证的合约。
合约提供了更深入的测试。
如果您需要测试相互连接的微服务网格,请开启所有合约并针对客户的数据进行测试,而不是使用您自己的有限的单元测试数据。
代码中指定的合约允许进行自动的客户端验证。
因此,您可以提前正式地向客户端发出信号,告知您期望什么(使用先决条件),而客户端可以验证从您那里期望得到什么(使用后置条件)。
合约不仅仅是用于输入验证。
尽管您也可以使用合约进行输入验证,但FastAPI已经允许您指定您想要如何验证输入(请参阅链接)。另一方面,当您想要指定端点之间的关联时,合约才能真正发挥其优势。
合约允许自动生成测试。
例如,Schemathesis这样的基于属性的测试工具可以自动生成测试数据,并验证您的API是否按预期工作。后置条件是定义要测试的属性的一种简单方法。
我们正在与Schemathesis的作者讨论如何将其与基于合约生成数据的工具(如icontract-hypothesis)集成。
合约为分析开辟了一个更广泛的生态系统。
当您用合约装饰端点时,您可以直接使用分析工具,如CrossHair来分析您的代码并查找错误。
(但这仅适用于真正无状态的、纯函数式的端点。)
预告
完整文档可在以下地址找到: fastapi-icontract.readthedocs.io。
以下示例旨在邀请您进一步探索此扩展。
from typing import Optional, List, Any
from fastapi import FastAPI
from pydantic import BaseModel
import asyncstdlib as a
from fastapi_icontract import (
require, snapshot, ensure,
wrap_openapi_with_contracts,
set_up_route_for_docs_with_contracts_plugin
)
app = FastAPI()
@app.get("/has_author", response_model=bool)
async def has_author(identifier: str):
"""Check if the author exists in the database."""
...
@app.get("/categories", response_model=List[str])
async def get_categories():
"""Retrieve the list of available categories."""
...
class Book(BaseModel):
identifier: str
author: str
@app.get("/has_category", response_model=bool)
async def has_category(identifier: str):
"""Check if the author exists in the database."""
...
@app.get("/books_in_category", response_model=List[Book])
@require(
has_category, status_code=404, description="The category must exist."
)
@ensure(
lambda result: a.all(a.await_each(has_author(book.author) for book in result)),
description="One ore more authors of the resulting books do not exist."
)
async def books_in_category(category: str) -> Any:
"""Retrieve the books of the given category from the database."""
...
@app.get("/has_book", response_model=bool)
async def has_book(book_id: str) -> Any:
"""Check whether the book exists."""
...
@app.get("/book_count", response_model=int)
async def book_count() -> Any:
"""Count the available books."""
...
@app.post("/upsert_book")
@snapshot(lambda book: has_book(book.identifier), name="has_book")
@snapshot(lambda: book_count(), name="book_count")
@ensure(lambda book: has_book(book.identifier))
@ensure(
lambda book, OLD: a.apply(
lambda a_book_count: (
OLD.book_count + 1 == a_book_count if not OLD.has_book
else OLD.book_count == a_book_count),
book_count()))
async def add_book(book: Book) -> None:
...
# Include contracts in /openapi.json
wrap_openapi_with_contracts(app=app)
# Include swagger-ui-plugin-contracts in /doc
set_up_route_for_docs_with_contracts_plugin(app=app)
版本控制
我们遵循语义版本控制。版本X.Y.Z表示
X是主版本(不向后兼容),
Y是次要版本(向后兼容),
Z是补丁版本(向后兼容的 bug 修复)。
下载文件
下载适用于您平台的应用程序。如果您不确定选择哪个,请了解更多关于安装包的信息。
源代码分发