FastAPI: the ultimate web framework for Python
Here's an article to help you start with the essentials of FastAPI, a very powerful yet simple framework. It's a clear trend in the industry.
What is FastAPI ?
What are the main features of FastAPI ?
- Fast performance : FastAPI is built with high-performance libraries such as Pydantic and Uvicorn to achieve incredible speeds and handle high traffic loads.
- Simple and easy-to-use
- Easy validation and serialization: FastAPI uses Python's type hints to automatically validate API requests and responses, as well as to automatically serialize and deserialize data to and from JSON.
- Built-in documentation: FastAPI automatically generates interactive API documentation.
- ASGI support: FastAPI is built on top of ASGI (Asynchronous Server Gateway Interface), allowing it to leverage the power of asynchronous programming for even faster performance.
- Dependency Injection: FastAPI makes use of Python's dependency injection system to automatically inject dependencies into API endpoints and other components, making it easy to build complex applications.
Since there are lots of articles related to FastAPI and since the framework has many interesting features (it is impossible to cover everything in a single post), I will focus primarily in:
- How to create an api to do CRUD operations for airport codes (using PostgreSQL and SQLAlchemy)
- Data validation using Pydantic models.
- Automatic generation of API docs
- Dependency Injection
- FastAPI async capabilities (including SQLAlchemy)
- Build an app with FastAPI and Streamlit
. . .
Airport codes with FastAPI
Let's create a simple application to insert, update and delete airport codes into a PostgreSQL database.
You can find the source code on this repository. Once you have clone the repo, go to that directory and:
python3 -m venv venv
source venv/bin/activate
pip3 install -r requirements.txt
This will leave your environment ready for deployment. You will also need PostgreSQL installed (please check the README file ).
Let's start with the SQLAlchemy Airport model:
Nothing fancy, just a few properties for the Airport model. We can define here the name of the DB table as well as the key column(s).
Now the Pydantic model: this will be used as a Response model for the return type for the APIs. This is what I'm telling to my API users: "listen, I'm giving this model in return".
In very simple terms I'm declaring that iata_code is string and it's mandatory, but the icao_code is not (look at the str | None definition). FastAPI will use this return type to:
- Validate the returned data.
- If the data is invalid (e.g. you are missing a field), it means your app code is broken, not returning what it should, and it will return a server error instead of returning incorrect data. This way you and your clients can be certain that they will receive the data and the data shape expected.
- Add a JSON schema for the response, in the OpenAPI path operation.
- This will be used by the automatic docs.
- It will also be used by automatic client code generation tools.
- Limit and filter the output data to what is defined in the return type.
- This is particularly important for security.
Modular App structure
Database.py
model.airport.Base.metadata.create_all(bind=engine)
Main.py
several things here to mark.
- The app variable (in line 15) will be an instance of the class FastAPI. the This will be the main point of interaction to create all your API.
- This app variable is the same one referred by uvicorn in the command, when you will start the app:
uvicorn main:app --reload
- The FastAPI docs include a get_db() function that allows a route to use the same session through a request and then close it when the request is finished. Then the get_db() creates a new session for the next request.
- Creating a path operation: "path" here refers to the last part of the URL starting from the first / . And "operation" here refers to one of the HTTP methods:
- POST : to create data.
- GET: to read data.
- PUT: to update data.
- DELETE: to delete data.
- and the more exotic ones ( OPTIONS , HEAD, PATCH , TRACE).
- All the data validation is performed under the hood by Pydantic - so you get all the benefits from it.
- In line 31 we can see a "path operation decorator":
@app.get("/airports/", response_model=List[AirportBase])
- the path /airports
- using a get operation
- In line 32 the python function get_airports:
def get_airports(db: Session = Depends(get_db)) -> List[AirportBase]:
return db.query(Airport).all()
Path parameters
@app.get('/airport/{code}',
responses={404: {'description': 'Airport not found'}})
def get_airport(code: str = Path(description='IATA Airport Code'), db: Session = Depends(get_db)) -> AirportBase:
- Here we're declaring a path parameter, the value of the parameter code will be passed to the function as the argument code . We're also telling the function what type of parameter is ( str in this case)
- At the same time the value will be validated. Let's suppose I have defined code as an int : as soon as we go to our browser and type http://localhost:8000/airport/yul we will receive an error.
Query parameters
@app.post('/airport', status_code=201)
async def create_airport(iata_code: Annotated[str, Query(description='IATA Airport code', max_length=3)],
name: Annotated[str, Query(description='Airport name', max_length=250)],
city: Annotated[str, Query(description='City name', max_length=30)],
icao_code: Annotated[str | None, Query(description='ICAO Airport code', max_length=4)] = None,
- Validate the data making sure the max length is 3 characters.
- Show a clear error for the client when the data is not valid.
- Document the parameter in the OpenAPI schema path operation (so it will show up in the automatic doc UI)
POSTGRES_PASSWORD='postgres'
POSTGRES_USER='postgres'
POSTGRES_DB='ms-airports'
POSTGRES_HOST='localhost'
. . .
Starting the app
uvicorn main:app --reload
You can see in the main module we have CRUD operations defined:
- getting a list of airports
- getting a particular airport by its code
- creating a new airport code
- deleting an existing airport code.
- getting a list of airports
- getting a particular airport by its code
- creating a new airport code
- deleting an existing airport code.
Let's check the built-in swagger documentation: I am completely blown away when I see the documentation page at http://localhost:8000/docs :
Let's create an airport code using the POST method, and then let's look for that same airport code using the GET method:
The path or query parameters, header and response have our custom description or schema:
. . .
Dockerize
POSTGRES_PASSWORD='postgres'
POSTGRES_USER='postgres'
POSTGRES_DB='ms-airports'
POSTGRES_HOST='db'
docker-compose build
docker-compose up
. . .
Recap
With FastAPI, by using short, intuitive and standard Python type declarations, you get:
- Editor support: error checks, autocompletion, etc.
- Data "parsing"
- Data validation
- API annotation and automatic documentation
And you only have to declare them once.
That's probably the main visible advantage of FastAPI compared to alternative frameworks (apart from the raw performance)
There are much more features in FastAPI, including the security, Routers, CORS middleware and the async side of FastAPI, I invite you to read the references below.
In any case I hope you liked this post. Again you can find the source code on this repository
References:
- https://fastapi.tiangolo.com/
- Build APIs with FastAPI
- Async with FastAPI
- Building a Data API with FastAPI and SQLAlchemy
- Build an async python service
- Understanding Python Async with FastAPI
- Why FastAPI is the future of Python Web Development
Comments
Post a Comment