FastAPI
FastAPI is a modern, fast (high-performance), web framework that enables developers to build APIs with Python 3.6+ based on standard Python type hints. We are going to use a Python package called Pydantic, which enforces type hints at runtime. It provides user-friendly errors, allowing us to catch any invalid data.
The key features of FastAPI are:
- Fast: Very high performance, on par with NodeJS and Go.
- Fast to code: It allows for significant increases in development speed.
- Easy: Designed to be easy to use and learn. Less time reading docs.
- Short: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
- Robust: Get production-ready code. With automatic interactive documentation.
- Standards-based: It’s based on the open standards for APIs, OpenAPI and JSON Schema.
Swagger
Swagger is a set of open-source tools built around the OpenAPI Specification that can help to design, build, document, and consume REST APIs. The major Swagger tools include:
- Swagger Editor — browser-based editor where you can write OpenAPI specs
- Swagger UI — renders OpenAPI specs as interactive API documentation
- Swagger Codegen — generates server stubs and client libraries from an OpenAPI spec
SQLAlchemy
SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL.
It provides a full suite of well-known enterprise-level persistence patterns, designed for efficient and high-performing database access, adapted into a simple and Pythonic domain language.
Uvicorn
Uvicorn is a lightning-fast ASGI server implementation, using uvloop and httptools. It supports HTTP/1.1 and WebSockets. Support for HTTP/2 is planned.
The aim of this tutorial is to work with FastAPI that helps us to create a production environment-ready Python application along with Swagger UI without a hitch. We will learn to build Rest APIs using Python 3, FastAPI and SQLAlchemy, and share the API using Swagger UI.
Creating the Item and Store REST API
We are going to create REST API providing access to item and store resources. Here’s the API design for the same:
Following are the steps required to create the sample FastAPI-based API for an Item and Store management application:
- Setup and Installation
- Configure Database
- Create Database Models
- Create Schemas
- Application Entry Point
- Swagger UI
- Conclusion
Prerequisites
We require Python 3 with Pipenv and Git installed. Pipenv is a package and a virtual environment manager which uses PIP
under the hood. It provides more advanced features like version locking and dependency isolation between projects.
1. Setup and Installation
Once the prerequisites are in place we can begin creating our application.
a) Create a Sample Item Management Flask Application
To begin with our application, create a folder called python-sample-fastapi-application
in any directory on the disk for our project.
$ cd /path/to/my/workspace/
$ mkdir python-sample-fastapi-application
$ cd python-sample-fastapi-application
Navigate to the project folder.
b) Activate Virtual Environment
Once we are inside the project folder, execute the following commands to activate the VirtualEnv.
pipenv shell --python 3.8
The virtual environment will now be activated, which will provide the required project isolation and version locking.
c) Install Dependencies
Next, install all the required dependencies using Pipenv as shown.
pipenv install fastapi==0.68.1
pipenv install uvicorn==0.15.0
pipenv install sqlalchemy==1.4.23
After we execute the above commands, the required dependencies will be installed.
We can see now two files, which have been created inside our project folder, namely, Pipfile
and Pipfile.lock
.
Pipfile
contains all the names of the dependencies we just installed.Pipfile.lock
is intended to specify, based on the dependencies present inPipfile
, which specific version of those should be used, avoiding the risks of automatically upgrading dependencies that depend upon each other and breaking your project dependency tree.
Note: Here, we have installed all the dependencies with specific versions, which worked on my machine while writing this tutorial. If we don’t specify any version then the latest version of that dependency will be installed, which might not be compatible with other dependencies.
Now, let’s start with writing some code for our application.
2. Configure Database
To start with our application , let’s configure the database first. FastAPI supports multiple databases, such as :
- PostgreSQL
- MySQL
- SQLite
- Oracle
- Microsoft SQL Server, and so on.
For our application, we’ll use SQLite, because it uses a single file and Python has integrated support. FastAPI works with any database and any style of library to talk to the database. A common pattern is to use an “ORM”: an “object-relational mapping” library. ORM is a technique that lets us query and manipulate data from a database using an object-oriented paradigm. ORMs can be thought of as a translator converting our code from one form to another. With an ORM, we normally create a class that represents a table in a SQL database and each attribute of the class represents a column, with a name and a type. In this tutorial we will use SQLAlchemy ORM framework.
To integrate database with our application, create a file db.py
with the following content.
The above code does the following:
- First, we have imported packages which are required to create SQLAlchemy
engine
and database session to connect to the SQLite database. The database file will be created in the same directory with the namedata.db
. - Then, we created the SQLAlchemy engine using the database created above.
Note:connect_args={"check_same_thread":False}
is only required forSQLite
SessionLocal
class represents database session. The class itself is not a database session yet. But, once we create an instance of theSessionLocal
class, this instance will be the actual database session. To create theSessionLocal
class, we used the functionsessionmaker
fromsqlachemy.orm
.- Finally, we used the function
declarative_base()
that returns a class to createBase
class. Later, we will inherit from this class to create each of the database models or classes(the ORM models). - We also defined a function called
get_db()
, which can used to create independent database session for each request. We will use the same session throughout the request and then close it after the request is finished. In the functionget_db
,yield
is used to create a database session for each request. Close it after finishing the request.
3. Create Database Models
Next, we will create database models for our data storage and organization. For our application, we need to create two database models Item, Store and its repositories. We will be using db.py
, which we created earlier in (Step 2) to create our SQLAlchemy models. It provides a class called Base
that is a declarative base, which can be used to declare our models.
Create the sql_app
package and add two files named models.py
and repositories.py
. We will add all the database entities in models.py
and it’s corresponding repository in repositories.py
.
3.1 models.py
The models.py file should contain the following content:
The above code in models.py
does the following:
- We started off by creating the Item Model class in
line 6
. - In
line 7
, we declared the table nameitems
where this model will be mapped to. - From
line 9 to 13
, we defined the table columns along with their data types. We useColumn
from SQLAlchemy as the default value. Here,store_id
acts as Foreign key reference forStores
. - From
line 14
to15
, we added some helper methods to print the object at runtime. - In
line 17
, we declared the Store Model class and inline 18
we declared the table namestores
where this model will be mapped to. - From
line 19 to 20
, we defined the stores table columns along with their data types. - In
line 21
we define therelationship
provided by SQLAlchemy ORM. This will become, more or less, a “magic” attribute that will contain the values from other tables related to this one. - From
line 23
to24
, we added some helper methods to print the object at runtime.
3.2 repositories.py
The repositories.py file contains some reusable functions to interact with the data in the database. It has the following content:
The above code in repositories.py
does the following:
- We started off by creating the ItemRepo class in
line 7
and StoreRepo class inline 38
. - From
line 9 to 25
, we defined some helper methods, which we can use to perform CRUD operations onItem
database model. - From
line 40 to 63
, we defined some helper methods, which we can use to perform CRUD operations onStore
database model.
4. Create Schemas
Let’s add a file schemas.py inside the package sql_app
. This file will contain the Pydantic models for our SQLAlchemy models. These Pydantic models define more or less a schema (a valid data shape).
Based on the official documentation,
Pydantic is primarily a parsing library, not a validation library. Validation is a means to an end: building a model which conforms to the types and constraints provided. In other words, pydantic guarantees the types and constraints of the output model, not the input data.
All the data validations are performed under the hood by Pydantic.
The schemas.py file should contain the following content:
First, we need to import BaseModel
from pydantic
and then use it to create subclasses defining the schema, or data shapes, we want to receive. The above code in schemas.py
does the following:
- We started off by creating
ItemBase
Pydantic model(schema) inline 6
andStoreBase
Pydantic model(schema) inline 24
. These classes contain the common attributes, which we need while creating or reading data. When a model attribute has a default value or is not required, then we can make that attribute optional. Here, we have usedNone
as the default value for description inline 9
. - Then we added
ItemCreate
andStoreCreate
classes, which inherit fromItemBase
andStoreBase
, respectively. Thus, they will have all the attributes of the Parent class, plus any additional data (attributes) needed for creation. - Finally, we created Pydantic models (schemas)
Item
andStore
that will be used to read the data from the database and returning it from the API. In the Pydantic models for reading,Item
andStore
, we added an internalConfig
class. ThisConfig
class is used to provide configurations to Pydantic. In theConfig
class, we set the attributeorm_mode = True
.
5. Application Entry Point
Now, let us create our application entry point. In the root directory of the project, create a file named main.py
with the following content:
And, now in the file main.py
let's integrate and use all the other parts we created in the above steps. The above code within main.py
does the following:
5.1 Create FastAPI Instance
In line 13
we defined a variable app
, which will be an “instance” of the class FastAPI
. This will be the main point of interaction for our APIs.
5.2 Create the Database Tables
In line 17
we create all the tables in the database during the application startup using the SQLAlchemy models defined in step 3.
5.3 Add Exception Handler
From line 19
to 22
, we defined a global exception Handler for our application.
5.4 Add REST Endpoints
From line 19
to 133
contains various REST endpoints available to consumers on resource Item and Store.
Let’s check some sample REST endpoints we have defined in our application.
In line 24
we can see that we have defined an endpoint operation decorator to create an Item. This API will be used by the consumers to create an Item with given details. The @app.post("/items")
tells FastAPI that the function right below is in charge of handling requests that go to the path /items
using a post
operation. This is a decorator related to an endpoint operation, or an endpoint operation decorator.
From line 25
to 34
we defined the endpoint operation function or the function that goes below the endpoint operation decorator. This function will be called by FastAPI whenever it receives a request to the specified URL (/items
) using a POST
operation. In this case, it is an async
function. async
and await
is used to support concurrency and improve performance. For this endpoint we expect the client to send the request as request body.
A request body is data sent by the client to our API. And response body is the data that our API sends back to the client. To declare a request body, we will use Pydantic models defined in step 3, with all their power and benefits.
We also have used normal functions for other endpoints, as we can see in line 37
instead of using async def.
Note: If you don’t know the difference between normal
functions and async
functions and when to use them, check out Concurrency and async/await in the FastAPI documentation.
In line 50
we have used path parameters with type. The value of the path parameter item_id
will be passed to our function as the argument item_id
.
Here, we just explored the capability of using both normal
and async
functions in FastAPI.
Similarly, we have defined other REST endpoints for our application.
5.5 Start the Application
Till now we have written all the code required for our application to run. Now, if we try to run the application using the python
command, it won’t run. To run it, we need a server program.
FastAPI is the framework that we have used to build our API, and Uvicorn is the server that we will use to serve the requests. We have already installed Uvicorn. That will be our server.
Finally, in line 137
we configure the application to run at port=9000 using Uvicorn ASGI server.
Our application is ready now. We can start the application by executing the below command:
python main.py
We are done with all the coding part and it’s testing time. Once the application is started successfully, we can navigate to http://localhost:9000/docs
. The system will bring up a page that looks something like this:
6. Swagger UI
FastAPI provides automatically generated documentation interfaces for our APIs, which we can interact with through a web interface. We can see that when we navigate to /docs
.
Let’s test our application now to ensure everything is working fine.
Let’s start with creating a Store.
Let’s check the Store we created.
Let’s add an Item to the Store we created above.
Now if we look at the Stores again, it will contain the Item we created above.
Similarly, we can explore other REST APIs.
Because FastAPI is built on top of the OpenAPI standard, it also provides an alternative API documentation using ReDoc, which we can access at http://localhost:9000/redoc
:
The JSON Schemas of our Pydantic models, which we defined in (step 3 ) will be part of the OpenAPI generated for our application and will be shown in the interactive API documentation:
We can see that the attributes of Item
in the API documentation are exactly the ones that we declared for our Pydantic model.
These JSON Schemas will also be used in the API documentation inside each path operation that needs them:
Swagger UI also helps the developers with API testing, either in case of any issues reported or while adding new API’s to the existing application.
7. Conclusion
In this tutorial, we saw how easy it is to create a comprehensive REST API using FastAPI. FastAPI uses the best practices by default while providing the best developer experience as possible. Here, we learned how to:
- Use path parameters to get a unique URL path per item
- Receive JSON data in requests using Pydantic
- Use API best practices like validation, serialization, and documentation
- Integrate FastAPI with SQLAlchemy
- Using both normal and async ways of handling requests.
- Using Swagger in FastAPI
If you would like to refer to the full code, do check: