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 envActivate the virtual environment:
On Linux and macOS:
source env/bin/activateOn 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 --versionSave the installed packages to a requirements.txt file so others can replicate your setup:
pip freeze > requirements.txt2. 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.pyfile, import theFastAPIclass to initialize your application. - Initialize a FastAPI application by creating an instance of the
FastAPIclass. 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.pyfile. - Use the following command to start the FastAPI application with Uvicorn:
uvicorn app:app --reload- In this command,
app:appspecifies 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/docsto view the automatically generated documentation for your API.
3. Adding Path Parameters
- In your
app.pyfile, add two new endpoints that utilize path parameters. - Create a function named
read_messagethat accepts amessagestring parameter and returns it. - Create another function named
read_numberthat accepts anumberinteger 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.,
strfor text,intfor numbers) to specify the expected data type of each path parameter. - Use Python’s
enummodule to define an Enum class. - Define your Enum class,
PeopleName, which inherits from bothstrandEnum. 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_personto handle the request, usingPeopleNameas the type for the path parameter. - Inside the function, use
ifstatements to compare the parameter against Enum members and return corresponding messages.
4. Data Validation with Pydantic Models
- Simple Model
- In your
app.pyfile, define a Pydantic model namedUserto structure incoming data.The model should include the following fields:name: a string representing the user’s name.email: an email string, using Pydantic’sBaseModelandEmailStrtypes 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_userthat accepts aUsermodel 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.pyfile, define aBaseModelclass namedAddresswith fields forstreet,city,state, andzip_code. - Create another
BaseModelclass namedUserWithAddressthat includes fields forname(string),email(string),age(integer), and anaddressfield of typeAddressto 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_addressthat accepts aUserWithAddressmodel 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.pyfile, create a Pydantic model namedItemwith 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_itemthat accepts anItemmodel instance. - Return a JSON response confirming the creation of the item, including the default
is_availablefield if not provided.
- Validating Data with Constraints
- In your
app.pyfile, create a Pydantic model namedProductwith fields:name(useconstrto 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_productthat accepts aProductmodel 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.pyfile, create a Pydantic model namedOrderwith fields:item_name(string),quantity(integer), and an optionalnotes(string) field. - Create another model named
Cartwith fields:user_id(integer) anditems(list ofOrderinstances). - Use the
@app.post("/carts/")decorator to define a POST endpoint. - Implement a function named
create_cartthat accepts aCartmodel instance. - Return a JSON response confirming the creation of the cart with the list of orders.
- Complex Data Types
- In your
app.pyfile, create a Pydantic model namedConfigurationwith fields:setting_name(string) andvalue(string). - Create another model named
Systemwith fields:name(string) andconfigurations(dictionary with string keys andConfigurationvalues). - Use the
@app.post("/systems/")decorator to define a POST endpoint. - Implement a function named
create_systemthat accepts aSystemmodel 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.pyfile, create a dictionary namedcars_dbto 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
Carwith 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_carthat:- Accepts a
Carmodel instance. - Assigns a unique ID to each new car by calculating the length of
cars_dbplus one. - Stores the car in
cars_dbusing 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_carsthat 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_carthat:- Accepts
car_idas 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_pricethat:- Accepts
car_idand aCarmodel 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_carthat:- Accepts
car_idas 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 app = FastAPI( title="Recipe and Movie Collection API", # Custom API title description="An API for managing recipes and movie collections. Manage, retrieve, and share your favorite items!", # Custom description version="1.0.0", # Version of your API ) - Add Tags with Descriptions:
- Use the
openapi_tagsparameter 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.", }, ] app = FastAPI( openapi_tags=tags_metadata # Apply tags metadata to FastAPI instance ) - 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.
app = FastAPI( title="Recipe and Movie Collection API", description="An API for managing recipes and movie collections.", version="1.0.0", 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", }, terms_of_service="https://example.com/terms/" )
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
Recipemodel with fields such astitle,ingredients,instructions, andcook_time. - Define a
Moviemodel 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_dbfor storing recipes. - Create another dictionary named
movies_dbfor 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
Recipemodel, assign a unique ID, and store the recipe inrecipes_db. - Similarly, create a POST endpoint for adding a new movie using the
Moviemodel, 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.