FastAPI A Python web framework that is a micro-framework like Flask. Its strengths include high performance, ease of writing, a design that is strongly conscious of production operation, and modern functions.
FastAPI is written on the shoulder of Starlette, and asynchronous processing is easy to handle. In particular, it has the following features.
Frankly, it's very similar to responder. (Because the time when it came out is near, and the responder is also based on Starlette) However, the bottom two are designed to be much easier to use with the Fast API. From the following perspectives, I think the Fast API is only for production operation. (Personally, I think responder is easier to use if you want to write freely.)
--Documents are polite (linkage with DB, authentication, https conversion, etc. are also introduced) --Since the automatic document generation function is generous, cooperation with front-end developers is likely to improve. --There is even a docker image for production operation
I also compared the performance with some Python frameworks, and the Fast API certainly seemed to perform well. (Reference: Comparison of Python Web framework performance (Django, Flask, responder, FastAPI, japronto))
I think the official tutorial is appropriate if you want to appreciate the Fast API. It is very easy to understand because the contents are substantial. However, on the other hand, it is a little quantitatively heavy to refer to just to get started. Therefore, I would like to reorganize and introduce the contents so that the Fast API can be used with the minimum necessary.
In addition, this article is written assuming the following.
--Understand some basic notation of microframework in python --Understand the basic python type hint (mypy) notation
Code examples corresponding to the contents introduced here are summarized in here. Please use it when you want to touch only Swagger.
intro install FastAPI Install fastapi and its ASGI server, uvicorn.
$ pip install fastapi uvicorn
intro code
Let's create an API that returns `{" text ":" hello world! "}`
in json when GET.
intro.py
from fastapi import FastAPI
app = FastAPI()
@app.get('/') #Specifying method and endpoint
async def hello():
return {"text": "hello world!"}
I think it's the one that can be written concisely in Python's microframework.
run server
The server will start as follows. (--Reload is convenient during development because the server is updated every time the file is changed.) `intro: app``` part is
filename: instance name of FastAPI ()
` is. Please replace as appropriate.
$ uvicorn intro:app --reload
Go to http://127.0.0.1:8000/docs. This will open the Swagger UI. You can hit the API here.
You will also be able to check the request and response schemas using the methods described below. One of the great strengths of FastAPI is that this document is automatically generated. If you develop normally, the document will be generated without permission.
The following items are handled.
GET method
You can get the parameter just by putting the parameter name in the argument. Once
--The parameter name declared as ``` / {param}` `` at the endpoint is ** path parameter ** --Other than that, it points to ** query parameter **
Please understand. Also, the order of the arguments does not matter. Then, depending on whether the default value is declared or not, the processing when the parameter included in the argument is not included at the time of GET changes.
-** not required : If you declare a default value, the default value will be used if the parameter has not arrived.
- required **: On the other hand, if a parameter that does not declare a default value does not come, `{" detail ":" Not Found "}`
is returned.
And, the feature of FastAPI is to add python ** type hint ** to the argument as shown below.
@app.get('/get/{path}')
async def path_and_query_params(
path: str,
query: int,
default_none: Optional[str] = None):
return {"text": f"hello, {path}, {query} and {default_none}"}
By doing this, when getting the parameter, the Fast API will take into account the python type hint,
-** Convert : Enter the argument with the data converted to the specified type
- Validation : Returns `{" detail ":" Not Found "}`
if conversion to the specified type is not possible
- Automatic document generation **: Add type information to swagger UI
to hold. If you actually check Swagger, you can check the parameter type information as shown below.
validation In addition to the above, you can do some advanced things by using the following Query and Path. Query is for query parameter and Path is for path parameter.
from fastapi import Query, Path
Use as follows. You can use basically the same arguments for Query and Path,
--Specify the default value for the first argument. Pass `...`
if you want to have no default value (required)
--alias: Specify the parameter name. It is used when you want to separate the argument name and the parameter name. For when it violates the python naming convention
--Other: You can limit the value received by specifying the character length, regular expression, and range of values.
@app.get('/validation/{path}')
async def validation(
string: str = Query(None, min_length=2, max_length=5, regex=r'[a-c]+.'),
integer: int = Query(..., gt=1, le=3), # required
alias_query: str = Query('default', alias='alias-query'),
path: int = Path(10)):
return {"string": string, "integer": integer, "alias-query": alias_query, "path": path}
You can also check the restrictions from Swagger. Since you can hit the API, try changing the value in various ways and check if the validation is done correctly. POST method
I will explain how to receive post data. First, as shown below, after inheriting `` `pydantic.BaseModel```, prepare a separate class with type hints for attributes, and add type hints with arguments as the type of request body. It's fine.
from pydantic import BaseModel
from typing import Optional, List
class Data(BaseModel):
"""Class with type hints for request data"""
string: str
default_none: Optional[int] = None
lists: List[int]
@app.post('/post')
async def declare_request_body(data: Data):
return {"text": f"hello, {data.string}, {data.default_none}, {data.lists}"}
Here, the above code assumes that the following json will be posted.
requestBody
{
"string": "string",
"default_none": 0,
"lists": [1, 2]
}
If there are not enough fields, status code 422 will be returned. (If there is an extra field, it seems to work normally) Also, if you perform the processing up to this point, you can check the data structure of the expected request body from the Swagger UI.
embed request body A little different from the previous example, I will explain the notation for the following data structure.
requestBody
{
"data": {
"string": "string",
"default_none": 0,
"lists": [1, 2]
}
}
For such a structure, use the same Data class as before. Only the structure can be changed by using `fastapi.Body```.
fastapi.Body``` is a companion to ``
pydantic.Query introduced in the validation of the GET method. Similarly, the first argument is the default value. It uses an argument called embed that was not found in `` `pydantic.Query
etc. The structure can be changed with the following minor changes.
from fastapi import Body
@app.post('/post/embed')
async def declare_embedded_request_body(data: Data = Body(..., embed=True)):
return {"text": f"hello, {data.string}, {data.default_none}, {data.lists}"}
nested request body Next, I will explain how to handle the structure where lists and dictionaries are nested as follows. The structure of subData is in the form of embed request body, but I will introduce a different way of writing it.
{
"subData": {
"strings": "string",
"integer": 0
},
"subDataList": [
{"strings": "string0", "integer": 0},
{"strings": "string1", "integer": 1},
{"strings": "string2", "integer": 2}
]
}
Nested type declarations are often very crude with python type hints. (If you want to get a rough idea, it's enough to type the following subDataList such as `List [Any]`
or `List [Dict [str, Any]`
)
On the other hand, FastAPI (or pydantic) can handle complex nested structures.
You can faithfully define subclasses along the nesting structure and add type hints as shown below.
class subDict(BaseModel):
strings: str
integer: int
class NestedData(BaseModel):
subData: subDict
subDataList: List[subDict]
@app.post('/post/nested')
async def declare_nested_request_body(data: NestedData):
return {"text": f"hello, {data.subData}, {data.subDataList}"}
validation
What you can do and what you can do with the GET method is almost the same. The difference is that it uses `pydantic.Field``` instead of
fastapi.Query```. But the arguments are the same. I just introduced ``
pydantic.Field to each class used in the nested request body. You can also use it with `` `fastapi.Query
etc., but it uses the argument example. The data passed to this argument will be the default value when hitting the API from Swagger.
from pydantic import Field
class ValidatedSubData(BaseModel):
strings: str = Field(None, min_length=2, max_length=5, regex=r'[a-b]+.')
integer: int = Field(..., gt=1, le=3) # required
class ValidatedNestedData(BaseModel):
subData: ValidatedSubData = Field(..., example={"strings": "aaa", "integer": 2})
subDataList: List[ValidatedSubData] = Field(...)
@app.post('/validation')
async def validation(data: ValidatedNestedData):
return {"text": f"hello, {data.subData}, {data.subDataList}"}
You can also define a class like the one defined in request body for response and perform validation.
If you pass it to response_model, by default,
--For the returned dictionary, the key that does not have a name that matches attributes is discarded. --It is not included in the returned dictionary, but if there is a default value in attributes, that value is compensated.
Here, if you write as follows, integer
is discarded from the returning dictionary, aux
is supplemented, and json is returned. (A very simple example is given, but if it is nested or a little complicated validation is required, you can use the notation for type hints as mentioned in "Handling of request" as it is)
class ItemOut(BaseModel):
strings: str
aux: int = 1
text: str
@app.get('/', response_model=ItemOut)
async def response(strings: str, integer: int):
return {"text": "hello world!", "strings": strings, "integer": integer}
At this stage, you can check the schema of response data from Swagger.
There are several options for using response_model.
#Response if it does not exist in the dictionary_The default value for model attributes"I can't put it in"
@app.get('/unset', response_model=ItemOut, response_model_exclude_unset=True)
async def response_exclude_unset(strings: str, integer: int):
return {"text": "hello world!", "strings": strings, "integer": integer}
# response_of model"strings", "aux"Ignore-> "text"Only return
@app.get('/exclude', response_model=ItemOut, response_model_exclude={"strings", "aux"})
async def response_exclude(strings: str, integer: int):
return {"text": "hello world!", "strings": strings, "integer": integer}
# response_of model"text"Only consider-> "text"Only return
@app.get('/include', response_model=ItemOut, response_model_include={"text"})
async def response_include(strings: str, integer: int):
return {"text": "hello world!", "strings": strings, "integer": integer}
There are three stages of status code management.
--Declare default status code: Declare with decorator
--Error handling returns 400s: raise `fastapi.HTTPException``` in the right place --Flexibly change status code and return: Touch starlette directly --Added
starlette.responses.Response as an argument --You can change the output status code by rewriting `` `response.status_code
--Return the data you want to return as usual
from fastapi import HTTPException
from starlette.responses import Response
from starlette.status import HTTP_201_CREATED
@app.get('/status', status_code=200) #default status code specification
async def response_status_code(integer: int, response: Response):
if integer > 5:
# error handling
raise HTTPException(status_code=404, detail="this is error messages")
elif integer == 1:
# set manually
response.status_code = HTTP_201_CREATED
return {"text": "hello world, created!"}
else:
# default status code
return {"text": "hello world!"}
background process You can use the background process to return only the response before the heavy processing is complete. This process is quite difficult for WSGI (Django, etc.). However, Starlette-based ASGI makes this process very concise.
The procedure is
fastapi.BackgroundTasks
It's hard to predict what's going on, but I think the description itself is easy.
As an example of heavy processing, try running a background process that sleeps for the received path parameter seconds and then prints.
from fastapi import BackgroundTasks
from time import sleep
from datetime import datetime
def time_bomb(count: int):
sleep(count)
print(f'bomb!!! {datetime.utcnow()}')
@app.get('/{count}')
async def back(count: int, background_tasks: BackgroundTasks):
background_tasks.add_task(time_bomb, count)
return {"text": "finish"} # time_Returns a response without waiting for the bomb to finish
The results are processed in the following order
So it seems that it is being processed properly in the background.
unittest Starlette's TestClient is excellent and you can easily hit the api for unittest. This time, I will try unittest with pytest according to the tutorial.
install
$ pip install requests pytest
├── intro.py
└── tests
├── __init__.py
└── test_intro.py
Now, let's do the following unit test.
intro.py
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional, List
app = FastAPI()
@app.get('/')
async def hello():
return {"text": "hello world!"}
class Data(BaseModel):
string: str
default_none: Optional[int] = None
lists: List[int]
@app.post('/post')
async def declare_request_body(data: Data):
return {"text": f"hello, {data.string}, {data.default_none}, {data.lists}"}
unittest
The selling point is that you can easily hit GET and POST with `starlette.testclient.TestClient`
as shown below, and you can make assert
of the response.
test_intro.py
from starlette.testclient import TestClient
from intro import app
# get and assign app to create test client
client = TestClient(app)
def test_read_hello():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"text": "hello world!"}
def test_read_declare_request_body():
response = client.post(
"/post",
json={
"string": "foo",
"lists": [1, 2],
}
)
assert response.status_code == 200
assert response.json() == {
"text": "hello, foo, None, [1, 2]",
}
$ pytest
========================= test session starts =========================
platform darwin -- Python 3.6.8, pytest-5.3.2, py-1.8.1, pluggy-0.13.1
rootdir: ***/***/***
collected 1 items
tests/test_intro.py . [100%]
========================== 1 passed in 0.44s ==========================
deployment You have the following options: It's a simple application, so I don't think there will be any problems with the infrastructure.
--If you can pip install and start uvicorn, it works like local --Docker image (Official): It seems that the performance has been tuned. Above all, it is official, so there is a sense of trust.
Basically, if you can use docker, the latter method is better, and if other than that (such as making a quick API with PaaS), the former method is better.
Regarding the specifics, there is no processing specific to FastAPI, and it is a procedure that is not related to other microframework, so I will omit it this time. reference:
-Official documentation (deployment) -Heroku example
Here is a reference for other frequently-used settings and context-sensitive matters that need not be written as a tutorial.
-Fixed CORS (Cross-Origin Resource Sharing) problem: This is a problem that occurs when the frontend is on a different server from the backend. -Authentication: Contains examples of OAuth2 and HTTP basic authentication. Documents are automatically generated for authentication
This is the end of the minimum tutorial. You should now be able to develop a complete API server-> deployment.
If you want to handle database linkage, html rendering, websocket, GraphQL, etc. in addition to the contents dealt with this time, I think that it is enough to refer only to the related chapters.
Anyway, it is convenient that Swagger is generated automatically, so I would like you to try it while moving your hands!
Finally, although it has little to do with the content of this article, I would like to introduce you to the most interesting chapters in the official Fast API documentation. The development process and the points that differentiate it from other frameworks are mentioned.
Refs -Official document
This is Miso, but I tried to generate Schema definition-> swagger with responder before, but the amount of description was completely different. (Since there is no description of FastAPI only for Swagger) here You can see how amazing FastAPI is. I think you can.
Recommended Posts