[FastAPI] Premiers pas avec FastAPI, un framework Web ASGI créé par Python

FastAPI Le framework Web de Python, qui est un microframework comme Flask. Ses atouts comprennent des performances élevées, une facilité d'écriture, une conception fortement axée sur les opérations de production et des fonctions modernes.

FastAPI est écrit sur l'épaule de Starlette, et le traitement asynchrone est facile à gérer. En particulier, il présente les caractéristiques suivantes.

Franchement, c'est très similaire à responder. (Parce que l'heure à laquelle il est sorti est proche et que le répondeur est également basé sur Starlette) Cependant, les deux derniers sont conçus pour être beaucoup plus faciles à utiliser avec l'API Fast. Du point de vue suivant, je pense que l'API Fast est uniquement destinée aux opérations de production. (Personnellement, je pense que le répondeur est plus facile à utiliser si vous voulez écrire librement.)

J'ai également comparé les performances avec certains frameworks Python et j'ai constaté que l'API Fast semblait bien fonctionner. (Référence: Comparaison des performances du framework Web Python (Django, Flask, responder, FastAPI, japronto))

Objectif de cet article

Je pense que le tutoriel officiel est approprié si vous voulez apprécier l'API Fast. C'est très facile à comprendre car le contenu est substantiel. Cependant, d'un autre côté, il est relativement difficile de s'y référer pour commencer. Par conséquent, je voudrais réorganiser et introduire le contenu afin que l'API Fast puisse être utilisée avec le minimum nécessaire.

En outre, cet article est rédigé en supposant ce qui suit.

--Comprendre la notation de base de certains microframework en python

Les exemples de code correspondant au contenu présenté ici sont résumés dans ici. Veuillez l'utiliser lorsque vous souhaitez toucher uniquement Swagger.

table des matières

intro install FastAPI Installez fastapi et son serveur ASGI, uvicorn.

$ pip install fastapi uvicorn

intro code Créons une API qui renvoie `{" text ":" hello world! "}` `Dans json quand GET.

intro.py


from fastapi import FastAPI

app = FastAPI()

@app.get('/') #Spécification de la méthode et du point de terminaison
async def hello():
    return {"text": "hello world!"}

Je pense que c'est celui qui peut être écrit de manière concise dans le microframework Python. run server Le serveur démarrera comme suit. (--Recharger est pratique pendant le développement car le serveur est mis à jour à chaque fois que le fichier est modifié.) Intro: app partie est `nom de fichier: nom d'instance FastAPI () est. Veuillez remplacer selon le cas.

$ uvicorn intro:app --reload

Vérifiez le document généré automatiquement (interface utilisateur Swagger)

Accédez à http://127.0.0.1:8000/docs. Cela ouvrira l'interface utilisateur de Swagger. Vous pouvez accéder à l'API ici. f895d438c0b57a8272939ee4e3521af3.gif

Vous pourrez également vérifier les schémas de demande et de réponse en utilisant la méthode décrite ci-dessous. L'une des grandes forces de FastAPI est que ce document est généré automatiquement. Si vous développez normalement, le document sera généré sans autorisation.

Traitement de la demande

Les éléments suivants sont traités.

GET method

Obtenir le paramètre de chemin et le paramètre de requête

Vous pouvez obtenir un paramètre simplement en mettant le nom du paramètre dans l'argument. Une fois que

--Le nom du paramètre déclaré comme / {param} au point de terminaison est ** paramètre de chemin ** --Autre que cela, il pointe vers le ** paramètre de requête **

Essaye de comprendre. De plus, l'ordre des arguments n'a pas d'importance. Ensuite, selon que la valeur par défaut est déclarée ou non, le traitement lorsque le paramètre inclus dans l'argument n'est pas inclus au moment de GET change.

Et, la fonctionnalité de Fast API est d'ajouter ** type hint ** de python comme argument comme suit.

@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}"}

En faisant cela, lors de l'obtention du paramètre, l'API Fast prendra en compte l'indication de type python,

tenir. Si vous vérifiez réellement Swagger, vous pouvez vérifier les informations de type de paramètre comme indiqué ci-dessous. 5136078ab0a27e2f274d116438395bc2.png

validation En plus de ce qui précède, vous pouvez effectuer des opérations avancées en utilisant la requête et le chemin suivants. La requête concerne le paramètre de requête et le chemin le paramètre de chemin.

from fastapi import Query, Path

Utilisez comme suit. Vous pouvez utiliser essentiellement les mêmes arguments pour la requête et le chemin,

--Spécifiez la valeur par défaut du premier argument. Passez ... '' si vous ne voulez pas de valeur par défaut (obligatoire) --alias: spécifiez le nom du paramètre. Il est utilisé lorsque vous souhaitez séparer le nom de l'argument et le nom du paramètre. Quand il viole la convention de dénomination Python --Autre: vous pouvez spécifier la longueur des caractères, l'expression régulière et la plage de valeurs pour limiter la valeur reçue.

@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}

Vous pouvez également vérifier les restrictions de Swagger. Puisque vous pouvez accéder à l'API, essayez de modifier les valeurs et vérifiez si la validation est effectuée correctement. POST method

Obtenir le corps de la demande

Forme basique

Explique comment recevoir des données de publication. Tout d'abord, en gros, après avoir hérité de pydantic.BaseModel```, préparez une classe séparée avec des indices de type pour les attributs, et ajoutez des indices de type avec des arguments comme type de corps de requête. C'est bon.

from pydantic import BaseModel
from typing import Optional, List

class Data(BaseModel):
    """Classe avec indications de type pour les données de demande"""
    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}"}

Ici, le code ci-dessus suppose que le json suivant sera publié.

requestBody


{
    "string": "string",
    "default_none": 0,
    "lists": [1, 2]
}

S'il n'y a pas assez de champs, le code d'état 422 sera renvoyé. (S'il y a un champ supplémentaire, il semble fonctionner normalement) De plus, après avoir effectué le traitement jusqu'à ce point, la structure de données du corps de requête attendu peut être confirmée à partir de l'interface utilisateur Swagger. c21c87c01835cab42629eb3e88e30201.png

embed request body Un peu différent de l'exemple précédent, je vais expliquer la notation de la structure de données suivante.

requestBody


{
    "data": {
        "string": "string",
        "default_none": 0,
        "lists": [1, 2]
    }
}

Pour une telle structure, utilisez la même classe Data qu'avant. Seule la structure peut être modifiée en utilisant fastapi.Body ''. fastapi.Body``` est un compagnon de pydantic.Query '' introduit dans la validation de la méthode GET. De même, le premier argument est la valeur par défaut. Il utilise un argument appelé embed qui n'a pas été trouvé dans pydantic.Query```. La structure peut être modifiée avec les modifications mineures suivantes.

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 Ensuite, je vais expliquer comment gérer la structure des listes imbriquées et des dictionnaires comme suit. La structure de subData est sous la forme d'un corps de requête incorporé, mais je vais introduire une manière différente de l'écrire.

{
    "subData": {
        "strings": "string",
        "integer": 0
    },
    "subDataList": [
        {"strings": "string0", "integer": 0},
        {"strings": "string1", "integer": 1},
        {"strings": "string2", "integer": 2}
    ]
}

Les déclarations de type imbriquées sont souvent très grossières avec des indices de type python. (Si vous voulez avoir une idée approximative, il suffit de taper la sous-liste de données suivante comme List [Any] '' ou List [Dict [str, Any] ``) D'autre part, FastAPI (ou pydantic) peut gérer des structures imbriquées complexes. Vous pouvez définir fidèlement les sous-classes le long de la structure imbriquée et ajouter des indices de type comme indiqué ci-dessous.

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 Ce que vous pouvez faire et ce que vous pouvez faire avec la méthode GET est presque identique. La différence est qu'il utilise pydantic.Field au lieu de `` fastapi.Query etc. Mais les arguments sont les mêmes. Je viens d'introduire pydantic.Field '' à chaque classe utilisée dans le corps de la requête imbriquée. Vous pouvez également l'utiliser avec `` fastapi.Query``` etc., mais il utilise l'exemple d'argument. Les données passées à cet argument seront la valeur par défaut lors de la frappe de l'API de 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}"}

Traitement de la réponse

Vous pouvez également définir une classe comme celle définie dans le corps de la requête pour la réponse et effectuer la validation.

Forme basique

Si vous le transmettez à response_model, par défaut

Ici, si vous écrivez comme suit, integer est supprimé du dictionnaire de retour, et aux est complété pour retourner json. (Un exemple très simple est donné, mais s'il est imbriqué ou qu'une validation un peu compliquée est requise, vous pouvez utiliser la notation pour les indices de type comme mentionné dans "Traitement de la demande" tel quel)

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}

À ce stade, vous pouvez vérifier le schéma des données de réponse de Swagger. bb16c30d6110d5ec387b8e8edca89fc8.png

Forme dérivée

Il existe plusieurs options pour utiliser response_model.

#Réponse si pas dans le dictionnaire_La valeur par défaut des attributs du modèle"Je ne peux pas le mettre"
@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_du modèle"strings", "aux"Ignorer-> "text"Seulement retour
@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_du modèle"text"Ne considérez que-> "text"Seulement retour
@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}

gestion des erreurs et gestion des codes d'état

Il existe trois étapes de gestion des codes d'état.

--Déclarer le code d'état par défaut: déclarer avec le décorateur

from fastapi import HTTPException
from starlette.responses import Response
from starlette.status import HTTP_201_CREATED

@app.get('/status', status_code=200) #spécification du code d'état par défaut
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 Vous pouvez utiliser le processus d'arrière-plan pour renvoyer uniquement la réponse avant la fin du traitement intensif. Ce processus est assez difficile pour les systèmes WSGI (Django, etc.). Cependant, ASGI basé sur Starlette rend ce processus très concis.

La procédure est

  1. Déclarez un argument de type fastapi.BackgroundTasks```
  2. Lancez une tâche avec `` `.add_task```

Il est difficile de prédire ce qui se passe, mais je pense que la description en elle-même est facile.

Comme exemple de traitement intensif, essayez d'exécuter un processus d'arrière-plan qui se met en veille pendant les secondes du paramètre de chemin reçu, puis s'imprime.

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_Renvoie une réponse sans attendre la fin de la bombe

Les résultats sont traités dans l'ordre suivant

  1. Date des en-têtes de réponse = 17:37:14
  2. sortie de la date à imprimer = 17:37:25

Il semble donc qu'il soit correctement traité en arrière-plan. スクリーンショット 2020-01-03 2.39.19.png

unittest TestClient de Starlette est excellent et vous pouvez facilement accéder à l'api pour unittest. Cette fois, je vais faire un test unitaire avec pytest selon le tutoriel.

install

$ pip install requests pytest

placement de répertoire

├── intro.py
└── tests
    ├── __init__.py
    └── test_intro.py

Maintenant, faisons le test unitaire suivant.

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 L'argument de vente est que vous pouvez facilement frapper GET et POST avec `starlette.testclient.TestClient et obtenir `ʻassert``` de la réponse comme indiqué ci-dessous.

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]",
    }

Exécuter pytest

$ 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 Vous avez les options suivantes: C'est une application simple, donc je ne pense pas qu'il y aura de problèmes avec l'infrastructure.

--Si vous pouvez installer et démarrer uvicorn, cela fonctionne comme local --Docker image (Officiel): Il semble que la performance a été réglée. Surtout, c'est officiel, il y a donc un sentiment de confiance.

Fondamentalement, si vous pouvez utiliser docker, la dernière méthode est meilleure, et si autre que cela (comme créer une API rapide avec PaaS), la première méthode est meilleure.

Concernant les spécificités, il n'y a pas de traitement spécifique à FastAPI, et c'est une procédure qui n'est pas liée à d'autres microframework, donc je vais l'omettre cette fois. référence:

Autres (correspondance au problème CORS, certification)

Voici une référence pour d'autres paramètres fréquemment utilisés et des questions contextuelles qui n'ont pas besoin d'être écrites sous forme de didacticiel.

Résumé

C'est la fin du tutoriel minimum. Vous devriez maintenant être en mesure de développer un déploiement complet de serveur> API.

En plus du contenu traité cette fois-ci, si vous souhaitez traiter de la liaison de base de données, du rendu html, du websocket, de GraphQL, etc., je pense qu'il suffit de se référer uniquement aux chapitres associés.

Quoi qu'il en soit, il est pratique que Swagger soit généré automatiquement, alors essayez-le en bougeant vos mains!

Enfin, bien que cela ait peu à voir avec le contenu de cet article, je voudrais présenter les chapitres les plus intéressants de la documentation officielle de l'API Fast. Le processus de développement et les points qui le différencient des autres cadres sont mentionnés.

Refs

Supplément

En ce qui concerne le miso avant, j'ai essayé de générer une définition de schéma -> swagger avec le répondeur avant, mais la quantité de description était complètement différente. (Comme il n'y a pas de description de FastAPI uniquement pour Swagger) ici, vous pouvez voir à quel point FastAPI est incroyable. Je pense que tu peux.

Recommended Posts

[FastAPI] Premiers pas avec FastAPI, un framework Web ASGI créé par Python
Premiers pas avec les applications Web Python
Premiers pas avec Python Web Scraping Practice
Premiers pas avec Python Web Scraping Practice
1.1 Premiers pas avec Python
Premiers pas avec Python
Premiers pas avec Python
Introduction à Tornado (1): Framework Web Python démarré avec Tornado
Introduction aux fonctions Python
Premiers pas avec Python Django (4)
Premiers pas avec Python Django (3)
Introduction à Python Django (6)
Premiers pas avec Python Django (5)
Premiers pas avec Python responder v2
Premiers pas avec Python pour les classes PHPer
Premiers pas avec Python Bases de Python
Premiers pas avec les algorithmes génétiques Python
Premiers pas avec Python 3.8 sous Windows
Premiers pas avec Python pour les fonctions PHPer
Premiers pas avec python3 # 1 Apprenez les connaissances de base
Premiers pas avec Python pour PHPer-Super Basics
Premiers pas avec Dynamo de Python boto
Créez un framework Web avec Python! (1)
Créez un framework Web avec Python! (2)
Démarrer avec Python avec 100 coups sur le traitement du langage
Contenu Web Python réalisé avec le serveur bon marché Lolipop
Premiers pas avec AWS IoT facilement en Python
Matériel à lire lors de la mise en route de Python
Paramètres pour démarrer avec MongoDB avec python
Django 1.11 a démarré avec Python3.6
Premiers pas avec python3 # 2 En savoir plus sur les types et les variables
Framework Web Django Python
Premiers pas avec apache2
Premiers pas avec Django 1
Application Web réalisée avec Python3.4 + Django (Construction de l'environnement Part.1)
Introduction à l'optimisation
Premiers pas avec Google App Engine pour Python et PHP
Premiers pas avec Numpy
Premiers pas avec Spark
Premiers pas avec Pydantic
Premiers pas avec Jython
Premiers pas avec Django 2
J'ai créé un environnement pour Masonite, un framework WEB Python similaire à Laravel, avec Docker!
Comparaison des performances du framework Web Python (Django, Flask, responder, FastAPI, japronto)
Premiers pas avec python3 # 3 Essayez des calculs avancés à l'aide de l'instruction d'importation
Initiation aux mathématiques Démarrage avec la programmation Python Challenge Notes personnelles - Problème 1-1
Traduire Premiers pas avec TensorFlow
Introduction à Tkinter 2: Button
Web scraping avec python + JupyterLab
Premiers pas avec Go Assembly
API Web EXE par Python
J'ai fait un blackjack avec du python!
Créer un œuf avec python
API Web avec Python + Falcon
Python lancé par des programmeurs C