Original link: https://editor.leonh.space/2022/openapi/
In the past, I wrote an article “Apiary API Specification File + Fake Interface at One Time “. Simply put, Apiary is a tool that helps us generate API files and fake interfaces, so that others can start the front-end before the real back-end API is completed. The file that defines Apiary is a format called API Blueprint, which is an extended format based on Markdown, that is to say, you will still have to write the file after going around in a circle .
The problem is that engineers hate three things: writing comments, writing tests, and writing files.
However:
- Comments can be omitted, because good code is self-explanatory
- Tests can not be written, because they can be left to QA to write
- The file must be written, because your front-end sister colleagues will be crazy
So someone invented a tool to automatically generate files from the code, so the code is there, the file is also there, the interface is also there, and even better than Apiary, it is true. interface .
Usually, the file specification used by such tools is the OpenAPI specification. Before getting started with the tool, let’s take a quick look at OpenAPI.
OpenAPI
OpenAPI is a file used to describe API information, including API endpoints, parameters, input and output formats, descriptions, authentication, etc. It is essentially a JSON or YAML file, and the schema in the file is defined by OpenAPI.
Here is an example of an OpenAPI JSON file:
{ " openapi ": " 3.0.0 ", " info ": { " title ": " TestAPI ", " description ": " Bare en liten test ", " version ": " 1.0 " }, " servers ": [ { " url ": " https://api.example.com/v1 " } ], " paths ": { " /users/{userId} ": { " get ": { " summary ": " Returns a user by ID ", " parameters ": [ { " name ": " userId ", " in ": " path ", " description ": " The ID of the user to return ", " required ": true , " style ": " simple ", " explode ": false , " schema ": { " type ": " integer " } } ], " responses ": { " 200 ": { " description ": " Successful response ", " content ": { " application/json ": { " schema ": { " $ref ": " #/components/schemas/inline_response_200 " } } } }, " 400 ": { " description ": " The specified user ID is invalid (eg not a number) " }, " 404 ": { " description ": " A user with the specified ID was not found " } } } } }, " components ": { " schemas ": { " inline_response_200 ": { " type ": " object ", " properties ": { " id ": { " type ": " integer " }, " name ": { " type ": " string " } } } } } }
Some of the schemas inside are expected to be literal, and the rest do not need to be fully understood. JSON is not a human-readable file but a machine-readable file. There are tools that will help us produce human-readable files. 🙂
Source: Sammo Hung
There are a number of tools that can read OpenAPI JSON/YAML files and produce feature-rich web documents, including:
Among these tools, the most common one should be our own Swagger UI (OpenAPI was part of the Swagger product line before it became an open specification). Taking the above example as an example, the following web document will be produced after processing by Swagger UI :
Source: TestAPI
We can see that the web documentation tells us the endpoint address of the API, what parameters to type, the format of the response, what the error response is, and you can try it out. These are all available in the tools mentioned above, and they are similar in form. .
However, the root of all these OpenAPI JSON / YAML must be generated by some means, otherwise it cannot solve the need of not writing files but not without files , so someone developed a tool to automatically generate OpenAPI JSON / YAML from code , such as Python’s FastAPI.
OpenAPI and FastAPI
Source: FastAPI
FastAPI is a Python web framework that has these features:
- High performance and feature rich. Usually, the more functions, the larger the volume and the worse the performance, and FastAPI strikes a good balance between performance and functions.
- Integrate the data verification function of pydantic .
- There is no binding ODM / ORM, and you can freely use any ODM to operate a file database such as MongoDB, or any ORM to operate a relational database such as SQLite.
- Integrate OpenAPI, Swagger UI, REDOC.
- Ecologically healthy, GitHub currently has nearly 40,000 stars, second only to the old Django (60,000 stars) and Flask (nearly 60,000 stars).
FastAPI is minimalist to use
FastAPI is a decorator-style framework. We add the decorator provided by FastAPI to the function to make the function an API endpoint:
from fastapi import FastAPI app: FastAPI = FastAPI () @app. get ( path =' /items/ {item_id} ') def read_item ( item_id : int) -> dict: return {' item_id ': item_id}
In the Yangchun API example above, we did a few things:
-
@app.get()
decorator defines this functionread_item()
to accept HTTP GET requests - The exposed API endpoint is
/items/{item_id}
-
item_id
should be defined as an integer in the function signature
Before talking about OpenAPI, let’s integrate pydantic’s data validation features to make the example closer to practicality.
FastAPI + pydantic
Following the above example, we make the real body of the item:
from fastapi import FastAPI from pydantic import BaseModel class Item ( BaseModel ): id : int name: str quantity: int app: FastAPI = FastAPI () @app. get ( path =' /items/ {item_id} ') def read_item ( item_id : int) -> dict: item = get_single_item_by_id (item_id) return item
Compared with the previous example, there are several more features here:
- Use the
Item
class to define the fields and types in theitem
object -
read_item()
returnsitem
directly
If more FastAPI to OpenAPI integration features are to be expressed, there are more icing on the cake.
FastAPI + OpenAPI
We add more elements to complete the OpenAPI documentation:
from fastapi import FastAPI from pydantic import BaseModel class Item ( BaseModel ): id : int name: str quantity: int app: FastAPI = FastAPI ( servers =[{' url ': ' http://localhost:5000 '}]) @app. get ( path =' /items/ {item_id} ', operation_id =' read_item ', response_model =Item, ) def read_item ( item_id : int) -> Item: """Read item by id.""" item = get_single_item_by_id (item_id) return item @app. delete ( path =' /items/ {item_id} ', operation_id =' delete_item ') def delete_item ( item_id : int) -> None : """Delete item by id.""" delete_single_item_by_id (item_id) return None
Compared with the previous example, here are a few more features:
- The
servers
parameter was added when constructing the FastAPI instance to indicate the API site address in the OpenAPI document. - Function decorator adds
operation_id='read_item'
parameter. - A docstring has been added to the function.
- Added another endpoint for deletion.
operationId
is the unique value used by OpenAPI to identify API endpoints and operations. In the RESTful API style, an API endpoint /items/{item_id}
may accept actions such as GET, DELETE, and PUT at the same time, and each action corresponds to To different internal processing functions, as shown in the example, but these internal functions are not known to the outside world, so it is necessary to use operationId
as a unique identification value.
After some tossing, we can finally take a look at the output OpenAPI JSON file and web document:
{ " openapi ": " 3.0.2 ", " info ": { " title ": " FastAPI ", " version ": " 0.1.0 " }, " servers ": [ { " url ": " http://localhost:5000 " } ], " paths ": { " /items/{item_id} ": { " get ": { " summary ": " Read Item ", " description ": " Read item by id. ", " operationId ": " read_item ", " parameters ": [ { " required ": true , " schema ": { " title ": " Item Id ", " type ": " integer " }, " name ": " item_id ", " in ": " path " } ], " responses ": { " 200 ": { " description ": " Successful Response ", " content ": { " application/json ": { " schema ": { " $ref ": " #/components/schemas/Item " } } } }, " 422 ": { " description ": " Validation Error ", " content ": { " application/json ": { " schema ": { " $ref ": " #/components/schemas/HTTPValidationError " } } } } } }, " delete ": { " summary ": " Delete Item ", " description ": " Delete item by id. ", " operationId ": " delete_item ", " parameters ": [ { " required ": true , " schema ": { " title ": " Item Id ", " type ": " integer " }, " name ": " item_id ", " in ": " path " } ], " responses ": { " 200 ": { " description ": " Successful Response ", " content ": { " application/json ": { " schema ": {} } } }, " 422 ": { " description ": " Validation Error ", " content ": { " application/json ": { " schema ": { " $ref ": " #/components/schemas/HTTPValidationError " } } } } } } } }, " components ": { " schemas ": { " HTTPValidationError ": { " title ": " HTTPValidationError ", " type ": " object ", " properties ": { " detail ": { " title ": " Detail ", " type ": " array ", " items ": { " $ref ": " #/components/schemas/ValidationError " } } } }, " Item ": { " title ": " Item ", " required ": [ " id ", " name ", " quantity " ], " type ": " object ", " properties ": { " id ": { " title ": " Id ", " type ": " integer " }, " name ": { " title ": " Name ", " type ": " string " }, " quantity ": { " title ": " Quantity ", " type ": " integer " } } }, " ValidationError ": { " title ": " ValidationError ", " required ": [ " loc ", " msg ", " type " ], " type ": " object ", " properties ": { " loc ": { " title ": " Location ", " type ": " array ", " items ": { " type ": " string " } }, " msg ": { " title ": " Message ", " type ": " string " }, " type ": { " title ": " Error Type ", " type ": " string " } } } } } }
Similarly, we do not seek to understand OpenAPI JSON physically, we use the Swagger UI web document that FastAPI automatically generates for us to help us understand the usage of this API:
In the overview view above, you can see the corresponding parts of the code:
- The Servers block comes from the
server
parameter in the code - “GET
/items/{item_id}
” and “DELETE/items/{item_id}
” correspond to the decorators and functions in the code respectively
Expand the “GET /items/{item_id}
” endpoint:
can be seen:
- The function’s docstring becomes the endpoint’s descriptive text
-
item_id
is listed as a parameter, and the type must be an integer, as declared in the program - The JSON format of the successful response is consistent with the
Item
model we defined
In the FastAPI framework, Python’s type hints feature and pydantic’s type verification feature are widely used. The type declaration in the program is not only used in the OpenAPI document, but also in the actual operation in the endpoint function, pydantic will automatically help We check whether the received parameter types match. When a parameter declared as an integer is received, but other types are received, pydantic will directly return an error. This feature saves us a lot of type verification work, compared to It is a better and safer strategy to block bad data ahead of time without checking or storing it before checking .
Returning to the topic of OpenAPI, of course, there is not only FastAPI that integrates natively with OpenAPI. APIFlask , developed by Mr. Hui Li, one of the Flask maintainers, is also natively integrated with OpenAPI. Even if there is no natively integrated framework, most of them have Third-party packages can help produce OpenAPI files.
Before this, we talked about how the backend and OpenAPI are, let’s talk about the application of OpenAPI in the front end.
OpenAPI on the front end
In addition to creating beautiful web documents, OpenAPI JSON / YAML itself is also a machine-readable file, which defines the front-end information such as server, path, HTTP method, operation ID, parameter, schema, etc. of the back-end API, which is of course There are also suites that can help us use OpenAPI JSON / YAML in front-end projects. This suite is Swagger Client . Like Swagger UI, Swagger Client is also a member of the Swagger family ~ ancestral ~ authentic ~ main family ~ direct line ~.
Swagger Client usage
A very simple usage example:
import SwaggerClient from " swagger-client "; async function getOasClient () { return await SwaggerClient ({ url: " http://localhost:5000/openapi.json ", }); } async function getItem ( id ) { const oasClient = await getOasClient (); const response = await oasClient . execute ({ operationId: " read_item ", parameters: { item_id: id }, }); return response .body }
Is it simpler to write with fetch()
? It seems to be about the same, because:
- No need to check HTTP method and endpoint path
- But manually check the Operation ID from OpenAPI JSON/YAML
- The parameters, types, and return formats to be passed should still be checked against the web documentation.
It’s useless to feel that you can save 30 years of struggle… let’s end this round.
For the front-end part, please refer to “Really, using OpenAPI to open up the two veins of the front-end and the back-end”
Summarize
So far, we have seen two API information exchange formats, API Blueprint and OpenAPI. Of course, foreigners have always been making wheels, and there are other genres such as RAML, AsyncAPI, AML / AMF, etc. At present, OpenAPI is already the most common. Standard, other schools are more or less compatible with OpenAPI, but the only and biggest exception is GraphQL. Compared with RESTful, OpenAPI based on resources with a fixed schema, GraphQL is a kind of Query Language , there is no fixed schema, but dynamic responses based on queries. It is difficult to convert between the two standards without discounts.
This article only mentions a small part of OpenAPI tools, OpenAPI JSON / YAML can do a lot, API testing, monitoring, fake API, security detection, etc. Here are two links to OpenAPI related tools:
This article is reprinted from: https://editor.leonh.space/2022/openapi/
This site is for inclusion only, and the copyright belongs to the original author.