Lab1 - Introduction to FastAPI & Development Setup
Overview
In this lab, you’ll set up your environment and create foundational API endpoints. Follow these steps to become proficient in building APIs using FastAPI.
If you have any questions or need further assistance, feel free to reach out. May the code be with you! 🌌
Goals
By the end of this lab, you should be able to:
- Set up a virtual environment for Python projects.
- Install FastAPI and Uvicorn.
- Create a basic FastAPI application.
- Implement path parameters in your API endpoints.
- Explore Pydantic models for data validation.
- Develop CRUD operations for resource management.
- Customize your FastAPI documentation.
Prerequisites
Ensure you have Python and a terminal or command line interface available on your computer.
You’ll be working through practical exercises, so consider how each step can be applied to real-world projects.
Exercises
1. Environment Setup
- Create and Activate a Virtual Environment
- Open your terminal or command line interface.
- Create a virtual environment with the following command:
python3 -m venv env
Activate the virtual environment:
On Linux and macOS:
source env/bin/activate
On Windows:
env\Scripts\activate
- Install FastAPI and Uvicorn using pip:
pip install fastapi[standard]
pip install uvicorn[standard]
Verify the installation by checking the FastAPI version:
fastapi --version
Save the installed packages to a requirements.txt file so others can replicate your setup:
pip freeze > requirements.txt
2. Write and Test Your First FastAPI Application
- Create a new file and name it
app.py
. This will be your main application file. - At the top of your
app.py
file, import theFastAPI
class to initialize your application. - Initialize a FastAPI application by creating an instance of the
FastAPI
class. This instance will be used to define your API endpoints. - Use the
@app.get("/")
decorator to define a function that handles GET requests to the root path (/
). - Inside the function, return a dictionary that includes a key-value pair where the key is
"message"
and the value is"Hello World"
. This will automatically be converted to a JSON response. - Open your terminal and navigate to the directory containing your
app.py
file. - Use the following command to start the FastAPI application with Uvicorn:
uvicorn app:app --reload
- In this command,
app:app
specifies the module name (app
) and the FastAPI instance (app
). - Open a web browser and navigate to
http://127.0.0.1:8000
. - You should see a JSON response displaying
{"message": "Hello World"}
. - Visit
http://127.0.0.1:8000/docs
to view the automatically generated documentation for your API.
3. Adding Path Parameters
- In your
app.py
file, add two new endpoints that utilize path parameters. - Create a function named
read_message
that accepts amessage
string parameter and returns it. - Create another function named
read_number
that accepts anumber
integer parameter and returns it. - For each endpoint, define a function that takes the path parameter as an argument. Ensure the function processes this parameter and includes it in the response.
- Use type hints (e.g.,
str
for text,int
for numbers) to specify the expected data type of each path parameter. - Use Python’s
enum
module to define an Enum class. - Define your Enum class,
PeopleName
, which inherits from bothstr
andEnum
. This allows your Enum members to behave like strings. - Inside the class, define each family member as a class variable, setting its value to the respective string : “Marc,” “Marie,” and “Josette.”
- Create an endpoint that accepts a path parameter of type
PeopleName
. - Use the
@app.get()
decorator to define the endpoint path, incorporating the Enum as a path parameter. - Define the function
get_person
to handle the request, usingPeopleName
as the type for the path parameter. - Inside the function, use
if
statements to compare the parameter against Enum members and return corresponding messages.
4. Data Validation with Pydantic Models
- Simple Model
- In your
app.py
file, define a Pydantic model namedUser
to structure incoming data.The model should include the following fields:name
: a string representing the user’s name.email
: an email string, using Pydantic’sBaseModel
andEmailStr
types for validation.age
: an integer representing the user’s age.
- Use the
@app.post("/users/")
decorator to create a POST endpoint. - Implement a function named
create_user
that accepts aUser
model instance as the request body. - Return a JSON response confirming the creation of the user, including the validated user data.
- Nested Models
- In your
app.py
file, define aBaseModel
class namedAddress
with fields forstreet
,city
,state
, andzip_code
. - Create another
BaseModel
class namedUserWithAddress
that includes fields forname
(string),email
(string),age
(integer), and anaddress
field of typeAddress
to nest the address model within the user model. - Use the
@app.post("/users-with-address/")
decorator to define a POST endpoint. - Implement a function named
create_user_with_address
that accepts aUserWithAddress
model instance as the request body. - Return a JSON response confirming the creation of the user with the nested address data.
- Using Default Values
- In your
app.py
file, create a Pydantic model namedItem
with fields:name
(string),price
(float), andis_available
(boolean) with a default value ofTrue
. - Use the
@app.post("/items/")
decorator to define a POST endpoint. - Implement a function named
create_item
that accepts anItem
model instance. - Return a JSON response confirming the creation of the item, including the default
is_available
field if not provided.
- Validating Data with Constraints
- In your
app.py
file, create a Pydantic model namedProduct
with fields:name
(useconstr
to specify a minimum and maximum length),price
(float), andquantity
(integer). - Use the
@app.post("/products/")
decorator to define a POST endpoint. - Implement a function named
create_product
that accepts aProduct
model instance. - Return a JSON response confirming the creation of the product, ensuring that the name adheres to the specified length constraints.
- Using Lists and Optional Fields
- In your
app.py
file, create a Pydantic model namedOrder
with fields:item_name
(string),quantity
(integer), and an optionalnotes
(string) field. - Create another model named
Cart
with fields:user_id
(integer) anditems
(list ofOrder
instances). - Use the
@app.post("/carts/")
decorator to define a POST endpoint. - Implement a function named
create_cart
that accepts aCart
model instance. - Return a JSON response confirming the creation of the cart with the list of orders.
- Complex Data Types
- In your
app.py
file, create a Pydantic model namedConfiguration
with fields:setting_name
(string) andvalue
(string). - Create another model named
System
with fields:name
(string) andconfigurations
(dictionary with string keys andConfiguration
values). - Use the
@app.post("/systems/")
decorator to define a POST endpoint. - Implement a function named
create_system
that accepts aSystem
model instance. - Return a JSON response confirming the creation of the system with its configurations.
5. Simulate a Database and Implement CRUD Operations for Cars
Simulate a Database:
- In your
app.py
file, create a dictionary namedcars_db
to simulate a database for storing car data. - Ensure that each car entry is indexed by a unique identifier (e.g., an integer ID).
- In your
Define a Pydantic Model for Cars:
- Create a Pydantic model named
Car
with the following fields:brand
: a string representing the car’s brand.model
: a string representing the car’s model.date
: a datetime object representing the car’s manufacture date.price
: a float representing the car’s price.
- Create a Pydantic model named
Implement CRUD Operations:
- Create (POST Endpoint):
- Use the
@app.post("/cars/")
decorator to define a POST endpoint. - Implement a function named
add_car
that:- Accepts a
Car
model instance. - Assigns a unique ID to each new car by calculating the length of
cars_db
plus one. - Stores the car in
cars_db
using the assigned ID. - Returns a JSON response confirming the addition of the car.
- Accepts a
- Use the
- Read (GET Endpoints):
- Get All Cars:
- Use the
@app.get("/cars/")
decorator withresponse_model=List[Car]
to define an endpoint that returns all cars. - Implement a function named
get_all_cars
that returns a list of all values incars_db
.
- Use the
- Get a Specific Car by ID:
- Use the
@app.get("/cars/{car_id}")
decorator to define an endpoint that retrieves a car by its ID. - Implement a function named
get_car
that:- Accepts
car_id
as a path parameter. - Checks if the car exists in
cars_db
. - Returns the car if found, or raises an HTTP 404 exception if not found.
- Accepts
- Use the
- Get All Cars:
- Update (PUT Endpoint):
- Use the
@app.put("/cars/{car_id}")
decorator to define an endpoint for updating car details. - Implement a function named
update_car_price
that:- Accepts
car_id
and aCar
model instance. - Checks if the car exists in
cars_db
. - Updates the price of the car (e.g., increases it by 10%).
- Returns a JSON response confirming the update, or raises an HTTP 404 exception if not found.
- Accepts
- Use the
- Delete (DELETE Endpoint):
- Use the
@app.delete("/cars/{car_id}")
decorator to define an endpoint for deleting a car. - Implement a function named
delete_car
that:- Accepts
car_id
as a path parameter. - Checks if the car exists in
cars_db
. - Deletes the car if found and returns a confirmation message, or raises an HTTP 404 exception if not found.
- Accepts
- Use the
- Create (POST Endpoint):
6. Customizing FastAPI API Documentation
1. Customize API Metadata
- Set Title, Description, and Version:
- When initializing your FastAPI instance, you can define a custom title, description, and version for your API. This information will be displayed prominently on your API’s documentation page.
from fastapi import FastAPI = FastAPI( app ="Recipe and Movie Collection API", # Custom API title title="An API for managing recipes and movie collections. Manage, retrieve, and share your favorite items!", # Custom description description="1.0.0", # Version of your API version )
- Add Tags with Descriptions:
- Use the
openapi_tags
parameter to categorize endpoints and add descriptions. This helps users understand different sections of your API.
= [ tags_metadata {"name": "Introduction", "description": "Basic introduction endpoints to get started.", }, {"name": "Recipe Management", "description": "Endpoints for managing recipes including adding, retrieving, and deleting recipes.", }, {"name": "Movie Collection", "description": "Endpoints for managing movie collections, including adding, retrieving, and deleting movies.", }, ] = FastAPI( app =tags_metadata # Apply tags metadata to FastAPI instance openapi_tags )
- Use the
2. Enhance Endpoint Documentation
- Add Detailed Docstrings:
- For each endpoint, provide comprehensive docstrings that explain the endpoint’s purpose, input parameters, and expected responses. This enhances the auto-generated documentation and helps users understand how to interact with your API.
@app.get("/", tags=["Introduction"]) async def index(): """ Returns a welcome message to introduce users to the API. **Response:** - `200`: A welcome message string. """ return {"message": "Welcome to the Recipe and Movie Collection API!"}
@app.post("/recipes/", tags=["Recipe Management"]) async def add_recipe(recipe: Recipe): """ Add a new recipe to the collection. **Request Body:** - `title`: (string) The title of the recipe. - `ingredients`: (list) The ingredients required. - `instructions`: (string) The steps to prepare the recipe. **Response:** - `200`: Success message and the new recipe. """ # Recipe handling logic here pass
3. Further Customization
- Add Contact, License, and Terms of Service Information:
- Provide additional context about your API by including contact details, licensing information, and terms of service. This is especially useful for public or enterprise APIs.
= FastAPI( app ="Recipe and Movie Collection API", title="An API for managing recipes and movie collections.", description="1.0.0", version={ contact"name": "API Support Team", "email": "support@example.com", "url": "https://example.com/support" },={ license_info"name": "MIT License", "url": "https://opensource.org/licenses/MIT", },="https://example.com/terms/" terms_of_service )
7. Putting It All Together
- Setting Up the Project:
- Create a Python file where you will define your FastAPI application (e.g.,
main.py
). - Set up a virtual environment and install FastAPI and Uvicorn if you haven’t already. This is essential for running your application.
- Define Data Models:
- Create Pydantic Models for data validation:
- Define a
Recipe
model with fields such astitle
,ingredients
,instructions
, andcook_time
. - Define a
Movie
model with fields liketitle
,director
,year
, andgenre
.
- Define a
- Simulate a Database:
- Use Python dictionaries to simulate a database for storing recipes and movies:
- Create a dictionary named
recipes_db
for storing recipes. - Create another dictionary named
movies_db
for storing movies. - Ensure that each entry is indexed by a unique identifier.
- Create a dictionary named
- Implement CRUD Operations:
- Create (POST Endpoint):
- Define a POST endpoint to add a new recipe. Validate the input data using the
Recipe
model, assign a unique ID, and store the recipe inrecipes_db
. - Similarly, create a POST endpoint for adding a new movie using the
Movie
model, storing it inmovies_db
.
- Define a POST endpoint to add a new recipe. Validate the input data using the
- Read (GET Endpoints):
- Implement a GET endpoint to retrieve all recipes from
recipes_db
. Return a list of all stored recipes. - Create another GET endpoint to fetch a specific recipe by its ID, returning the data if found, or an appropriate error if not.
- Implement similar GET endpoints for movies, allowing retrieval of all movies or a specific movie by its ID.
- Implement a GET endpoint to retrieve all recipes from
- Update (PUT Endpoint):
- Define a PUT endpoint to update an existing recipe. Check if the recipe exists in
recipes_db
. If it does, update its details and return a success message; if not, return an error. - Implement a PUT endpoint for updating movie details, using a similar approach with
movies_db
.
- Define a PUT endpoint to update an existing recipe. Check if the recipe exists in
- Delete (DELETE Endpoint):
- Create a DELETE endpoint to remove a recipe by its ID. Check for its existence in
recipes_db
, delete it if found, and return a confirmation message; otherwise, return an error. - Similarly, implement a DELETE endpoint for movies, allowing deletion by ID with appropriate checks.
- Create a DELETE endpoint to remove a recipe by its ID. Check for its existence in
- Enhance API Documentation:
- Use Detailed Docstrings:
- For each endpoint, provide a clear and concise docstring that describes the endpoint’s purpose, parameters, expected responses, and possible errors. This enhances the auto-generated documentation at
/docs
.
- For each endpoint, provide a clear and concise docstring that describes the endpoint’s purpose, parameters, expected responses, and possible errors. This enhances the auto-generated documentation at
- Run the Application:
- Start the FastAPI Server:
- Use Uvicorn to run your application locally. This will enable you to test the CRUD operations and access the documentation.
By following these instructions, you will have a fully functional FastAPI application capable of managing a collection of recipes and movies, complete with CRUD operations and well-documented endpoints.