Package: fastapi-icontract
FastAPI-icontract is aFastAPIextension for design-by-contract which leveragesicontractto allow you to specify and enforce code contracts in yourFastAPI endpoints.Depending on how you set it up, FastAPI-icontract will:automaticallyenforcethe contracts during testing or in production,automatically add the contracts to yourOpenAPI specification, andrender theSwagger UIwith a specialized contracts plugin for nicer visualization.Benefits of Adding Contracts to Your APIEnforcing code contracts in your FastAPI development opens up new venues for approaches
to moresystematic designat the API level:Contracts are an importantpart of the specification.Unlike human language, contracts written in code are unambiguous.Contracts areautomatically verifiable.Your clients can rest assured that you actually run them.
FastAPI-icontract will specify precisely which contracts are run in production and
which were only verified during testing.Contracts providedeeper testing.If you have a mesh of microservices that you need to test in conjunction,
turn on all the contracts and test against yourclient’sdata instead of your own
limited unit test data.Contracts specified in code allow for automaticclient-sideverification.Thus you can signal formally to the client up-front what you expect
(using pre-conditions), while the client can verify what to expect back from you
(using post-conditions).Contracts arenot just for input validation.Though you can use contracts for input validation as well, FastAPI already allows you
to specify how you want yourinput verified.
Contracts, on the other hand, really shine when you want to specify relationsbetweenthe endpoints.Contracts allow forautomatic test generation.Tools for property-based testing such asSchemathesiscan automatically generate
test data and verify that your API works as expected.
Post-conditions are an easy way to define your properties to be tested.There is an ongoing discussion with the authors of theSchemathesishow to
integrate it with tools which generate data based on contracts such asicontract-hypothesis.Contracts open upa wider ecosystem for analysis.When you decorate the endpoints with contracts, you can immediately use analysis
tools such asCrossHairto analyze your code and find bugs.(Though this only makes sense for really stateless, purely functional endpoints.)TeaserThe full documentation is available at:fastapi-icontract.readthedocs.io.The following example is meant to invite you to further explore the extension.fromtypingimportOptional,List,AnyfromfastapiimportFastAPIfrompydanticimportBaseModelimportasyncstdlibasafromfastapi_icontractimport(require,snapshot,ensure,wrap_openapi_with_contracts,set_up_route_for_docs_with_contracts_plugin)app=FastAPI()@app.get("/has_author",response_model=bool)asyncdefhas_author(identifier:str):"""Check if the author exists in the database."""...@app.get("/categories",response_model=List[str])asyncdefget_categories():"""Retrieve the list of available categories."""...classBook(BaseModel):identifier:strauthor:str@app.get("/has_category",response_model=bool)asyncdefhas_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(lambdaresult:a.all(a.await_each(has_author(book.author)forbookinresult)),description="One ore more authors of the resulting books do not exist.")asyncdefbooks_in_category(category:str)->Any:"""Retrieve the books of the given category from the database."""...@app.get("/has_book",response_model=bool)asyncdefhas_book(book_id:str)->Any:"""Check whether the book exists."""...@app.get("/book_count",response_model=int)asyncdefbook_count()->Any:"""Count the available books."""...@app.post("/upsert_book")@snapshot(lambdabook:has_book(book.identifier),name="has_book")@snapshot(lambda:book_count(),name="book_count")@ensure(lambdabook:has_book(book.identifier))@ensure(lambdabook,OLD:a.apply(lambdaa_book_count:(OLD.book_count+1==a_book_countifnotOLD.has_bookelseOLD.book_count==a_book_count),book_count()))asyncdefadd_book(book:Book)->None:...# Include contracts in /openapi.jsonwrap_openapi_with_contracts(app=app)# Include swagger-ui-plugin-contracts in /docset_up_route_for_docs_with_contracts_plugin(app=app)VersioningWe followSemantic Versioning.
The version X.Y.Z indicates:X is the major version (backward-incompatible),Y is the minor version (backward-compatible), andZ is the patch version (backward-compatible bug fix).