What's FastAPI?
Fast API is a modern Python web framework that provides cutting edge features:- type hints for validation and parsing request URL parameters
- pydantic models to parse and validate JSON body data
- auto-generated project API documentation via Swagger
- async request handlers
- code re-use via dependency injection
Lets find out how to create a simple one file FastAPI application and respond to HTTP GET and POST requests.
1. The simplest application.
Create project folder, change into it. Install dependencies in virtual env:mkdir fastapi-demo cd fastapi-demo python3 -m venv env source env/bin/activate python3 -m pip install fastapi uvicornCreate
main.py
application file in the fastapi-demo
folder:
from fastapi import FastAPI app = FastAPI() # FastAPI application instance @app.get("/") def root(): """ Path function or handler for HTTP GET / request. """ return "FastAPI demo"
When the GET request to /
URL is made, the FastAPI application calls the root()
function, because the value of app.get()
parameter matches the request URL.
Such function for handling HTTP requests is called a path function.
fastapi-demo
folder to start web server:
uvicorn --reload main:appThe FastAPI application
app
instance from the main.py
is listening HTTP requests on http://127.0.0.1:8000 so lets open the URL in a browser.end
Or make the HTTP request from command line:
curl http://127.0.0.1:8000
And lets try it from the Swagger project API documentation, one of FastAPI key features: open http://localhost:8000/docs/ in a browser, expand the GET / root
section and press the Try it out button to trigger the HTTP request.
2. Handling HTTP GET request parameters.
There are two ways to add parameters in the URL:Path
To handle URL like/users/user_id/
where user_id
is varied integer value, for example /users/12/
if user_id is 12, we should add the parameter name in curly brackets into the app.get()
url parameter and add the parameter with the same name into the path function:
@app.get("/users/{user_id}/") def user_details(user_id: int): return {'user_id': user_id}The optional type hint is used for the parameter type validation and parsing. Command line request:
curl http://localhost:8000/users/12/Swagger: open http://localhost:8000/docs, expand the User Details section, fill the integer
user_id
field and press the Try it out button.
Query string
To handle a parameter passed after?
character in the request URL, we add only the path function parameter with the same name as the parameter in the query string, for example the path function below gets the q
query string parameter:
@app.get("/search/") def search(q: str): return {'q': q}The parameter type is restricted to a primitive types like integer, string or boolean. Command line request:
curl http://localhost:8000/search/?q=abc567Also the parameter is required so making the
http://localhost:8000/search/
request without the parameter returns the missing parameter error. To fix this, we'll make the parameter optional, depending on the Python version:
q: Optional[str] # before Python 3.10 q: str | None = None # Python 3.10 or laterThe first option requires
Optional
type import from typing
module.
Both of them: URL contains URL path and query string parameters
In this case the parameters from the URL path are matched first, then other parameters are considered query string parameters:@app.get("/users/{user_id}/orders/") def user_orders(user_id: int, q: str): return {'user_id': user_id, 'q': q}Command line request:
curl http://localhost:8000/users/12/orders/?q=abc567
3. The path functions order matters.
When the application file contain several path functions, the first path function with the URL match is called. Let's figure out which function is called for/users/about/
URL in the application below:
@app.get("/users/{username}/") def user_details(username: str): return "user detail" @app.get("/users/about/"): def users_about(): return "Users list"The
user_details()
is called because it's the first and /users/{username}/
matches the URL as well.
So we should place the users_about()
path function first in the file to be called on the /users/about/
URL:
@app.get("/users/about/"): def users_about(): return "Users list" @app.get("/users/{username}/") def user_details(username: str): return "user detail"
4. Handling HTML form POST data.
FastAPI provides POST request support via@app.post(url)
decorator. Let's explore how to access the POST body parameters in case of HTML form is posted in a browser.
First install the package for FastAPI form data support:
python3 -m pip install python-multipartand restart the application. Then add to
main.py
the code below:
from fastapi import Form @app.post("/login/") def login(username: str = Form(), password: str = Form()): return {"logged": username}A parameter with
Form()
default value is processed as multipart form data POST parameter instead query parameter.
Let's test the login endpoint via Swagger or command line:
curl -F username=alex -F password=123 http://localhost:8000/login/Expected output:
{"logged":"alex"}
Handling POST data in JSON format.
Let's assume JSON is sent from a browser by JavaScript, in the POST request body. The code below adds endpoint that accepts JSON in format{"username": str, "password": str}
:
from pydantic import BaseModel class NewUser(BaseModel): username: str password: str @app.post("/register/") def register(new_user: NewUser): return {"registered": new_user.username}
We added a parameter of NewUser
class to a path function so the application gets it from the POST request body instead query string. Sub-class of pydantic.BaseModel
describes JSON format to convert the JSON string to an object.
{ "username": "alex", "tags": [ "user", "test" ], "location": { "country": "Ukraine", "city": "Kharkov", "address": "some street" } }the corresponding validation and parsing model:
from typing import Optional, List class Location(BaseModel): country: str city: str address: str class NewUser(BaseModel): username: str tags: Optional[List[str]] location: Location