Home » Using FastAPI and Pydantic

Using FastAPI and Pydantic

To rapidly develop an API in python with data models that are validated easily and autogenerated OpenAPI / Swagger / ReDoc documentation, FastAPI its an excellent option.

VENV Creation and Installation

python3 -m venv asicsapi cd asicsapi/ source bin/activate git init touch .gitignore pip3 install -r requirements.txt pip install fastapi uvicorn[standard] databases[sqlite] python-multipart touch main.py
.gitignore File contents
# Virtualenv .Python [Bb]in [Ii]nclude [Ll]ib [Ll]ib64 [Ll]ocal [Ss]cripts pyvenv.cfg .venv pip-selfcheck.json # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/

Creating an FastApi app instance and add HTTP Routes – Main.py

Create an instance of FastAPI service. Add a basic route for Home page

from fastapi import FastAPI app = FastAPI() # Home Route @app.get("/") async def root(): return {"message": "Hello World"} # Items Route @app.get("/items") async def items(): return {"message": "127.0.0.0:8000/items"}
Code language: Python (python)

Run the server

uvicorn main:app --reload
Code language: CSS (css)

View the API at

Path and Query Parameters for HTTP Get Requests

## Add the following lines # Item_id passed as Path Parameter of Type Integer @app.get("/items/{item_id}") async def read_item(item_id: int): return {"item_id": item_id} # Restricted set of users allowed as Path Parameters - using Enum from enum import Enum class Users(str, Enum): vivek = "Vivek" gupta = "Gupta" epidemiologist = "Epidemiologist" @app.get("/users/{user_name}") async def users(user_name: Users): return {"user": user_name} # Both Path Parameter (item_id) and Query Parametrs: # needy, a required str. # skip, an int with a default value of 0. # limit, an optional int from typing import Optional @app.get("/items2/{item_id}") async def read_user_item( item_id: str, needy: str, skip: int = 0, limit: Optional[int] = None): item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} return item
Code language: Python (python)

JSON Request Body in HTTP PUT routes

Parametrs accepted by the service in Request Body can be defined as Pydantic models and then the model is added to the route inside the function definition. By default, JSON request body is accepted.

# JSON Request Body from pydantic import BaseModel class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = None @app.put("/add_items/{item_id}") async def create_item(item_id: int, item: Item): return {"item_id": item_id, **item.dict()}

Here the Item model has been defined with two mandatory and two optional parameters. Then in the app.put route, one path parameter has been given and in it’s function, the Item model is being added as the expected Request body data. We return the item_ID, as well as the concatenated JSON of request body.

Try going to http://127.0.0.1:8000/docs#/default/create_item_add_items__item_id__put

HTTP PUT using Form Data

The way HTML forms (<form></form>) sends the data to the server normally uses a “special” encoding for that data, it’s different from JSON. Data from forms is normally encoded using the “media type” application/x-www-form-urlencoded. But when the form includes files, it is encoded as multipart/form-data. When you need to receive Form fields instead of JSON, you can use Form in the function definition.

from fastapi import Form @app.post("/login_form/") async def login2(username: str = Form(...), password: str = Form(...)): return {"username": username, "logged_in": True }
Code language: JavaScript (javascript)

Upload Files

from fastapi import UploadFile, File @app.post("/uploadfile/") async def create_upload_file(file: UploadFile = File(...)): return {"filename": file.filename, "content_type": file.content_type}
Code language: JavaScript (javascript)

Please see discussion on trying to measure the File Size when using UploadFile here.

Pydantic Model Validations

from pydantic import BaseModel, ValidationError, validator, Field from pydantic import NegativeInt, conint, conlist, constr, StrictInt, PositiveInt from enum import Enum, IntEnum from datetime import date, datetime class AsicsOptions(IntEnum): Never = 1 Sometimes = 2 Often = 3 Always = 4 class AsicsForm(BaseModel): q1: AsicsOptions q2: conint(gt=0, lt=5) q3: constr(regex=r'^[1-4]$') q4: conint(gt=0, lt=5) username: str time_submit: datetime @validator('username') def username_alphanumeric(cls, v): assert v.isalnum(), 'must be alphanumeric' return v

Please see this page for some more examples