Skip to content

Commit

Permalink
Merge pull request #46 from raminmohammadi/akhil
Browse files Browse the repository at this point in the history
Updated FastAPI Lab
  • Loading branch information
raminmohammadi authored Jul 22, 2024
2 parents db46761 + 155b3b8 commit 474809e
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 111 deletions.
172 changes: 105 additions & 67 deletions Labs/API_Labs/FastAPI_Labs/README.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,131 @@
## Install FASTAPI and Uvicorn

You will need to install fastapi and uvicorn to proceed ahead
---
- Video Explanation: [FastAPI lab](https://www.youtube.com/watch?v=KReburHqRIQ&list=PLcS4TrUUc53LeKBIyXAaERFKBJ3dvc9GZ&index=4)
- Blog: [FastAPI Lab-1](https://www.mlwithramin.com/blog/fastapi-lab1)

---

## Overview

In this Lab, we will learn how to expose ML models as APIs using [FastAPI](https://fastapi.tiangolo.com/) and [uvicorn](https://www.uvicorn.org/).
1. **FastAPI**: FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints.
2. **uvicorn**: Uvicorn is an [ASGI](https://youtu.be/vKjCkeJGbNk) web server implementation for Python. It is often used to serve FastAPI aplications.

1. `pip install fastapi`
2. `pip install uvicorn[standard]`
The workflow involves the following steps:
1. Training a Decision Tree Classifier on Iris Dataset.
2. Serving the trained model as an API using FastAPI and uvicorn.

## FASTAPI Syntax
The instance of FASTAPI class can be defined as `app = FASTAPI()` When you run a FastAPI application, you often pass this app instance to an ASGI server uvicorn. The server then uses the app instance to handle incoming web requests and send responses based on the routes and logic you’ve defined in your FastAPI application.
## Setting up the lab

1. Create a virtual environment(e.g. **fastapi_lab1_env**).
2. Activate the environment and install the required packages using `pip install -r requirements.txt`.

### Project structure

To run a FASTAPI application
```
uvicorn myapp:app --reload
mlops_labs
└── fastapi_lab1
├── assets/
├── fastapi_lab1_env/
├── model/
│ └── iris_model.pkl
├── src/
│ ├── __init__.py
│ ├── data.py
│ ├── main.py
│ ├── predict.py
│ └── train.py
├── README.md
└── requirements.txt
```

In this command, myapp is the name of the Python file containing your app instance (without the .py extension), and app is the name of the instance itself. The --reload flag tells uvicorn to restart the server whenever code changes are detected, which is useful during development and should not be used in production.
Note:
- **fastapi[all]** in **requirements.txt** will install optional additional dependencies for fastapi which contains **uvicorn** too.

## Running the Lab

1. First step is to train a Decision Tree Classifier(Although you have **`model/iris_model.pkl`** when you cloned from the repo, let's create a new model). To do this, move into **src/** folder with
```bash
cd src
```
2. To train the Decision Tree Classifier, run:
```bash
python train.py
```
3. To serve the trained model as an API, run:
```bash
uvicorn app:main --reload
```
4. Testing endpoints - to view the documentation of your api model you can use [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) (or) [http://localhost:8000/docs](http://localhost:8000/docs) after you run you run your FastAPI app.

![API page](assets/docs.png)

You can also test out the results of your endpoints by interacting with them. Click on the dropdown button of your endpoint -> Try it out -> Fill the Request body -> Click on Execute button.

![API response](assets/api_response.png)

All the functions which should be used as API should be prefixed by `@app.get("/followed_by_endpoint_name")` or `@app.post("/followed_by_endpoint_name")` This particular syntax is used to define route handlers(which function should handle an incoming request based on the URL and HTTP method), which are the functions responsible for responding to client requests to a given endpoint
- You can also use other tools like [Postman](https://www.postman.com/) for API testing.

1. Decorator (@): This symbol is used to define a decorator, which is a way to dynamically add functionality to functions or methods. In FastAPI, decorators are used to associate a function with a particular HTTP method and path.
2. App Instance (app): This represents an instance of the FastAPI class. It is the core of your application and maintains the list of defined routes, request handlers, and other configurations.
3. HTTP Method (get, post, etc.): The HTTP method specifies the type of HTTP request the route will respond to. For example, get is used for retrieving data, and post is used for sending data to the server. FastAPI provides a decorator for each standard HTTP method, such as @app.put, @app.delete, @app.patch, and @app.options, allowing you to define handlers for different types of client requests. For detailed info refer to this webiste by [Mdn](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods)
4. Path/Endpoint ("/endpoint_name"): This is the URL path where the API will be accessible. When a client makes a request to this path using the specified HTTP method, FastAPI will execute the associated function and return the response.
### FastAPI Syntax

Using `async` in FastAPI allows for non-blocking operations, enabling the server to handle other requests while waiting for I/O tasks, like database queries or model loading, to complete. This leads to improved concurrency and resource utilization, enhancing the application's ability to manage multiple simultaneous requests efficiently
- The instance of FASTAPI class can be defined as:
```bash
app = FastAPI()
```
- When you run a FastAPI application, you often pass this app instance to an ASGI server uvicorn. The server then uses the app instance to handle incoming web requests and send responses based on the routes and logic you’ve defined in your FastAPI application.
- To run a FastAPI application, run:
```
uvicorn main:app --reload
```
- In this command, **main** is the name of the Python file containing your app instance (without the .py extension), and **app** is the name of the instance itself. The **--reload** flag tells uvicorn to restart the server whenever code changes are detected, which is useful during development and should not be used in production.
- All the functions which should be used as API should be prefixed by **@app.get("/followed_by_endpoint_name")** or **@app.post("/followed_by_endpoint_name")**. This particular syntax is used to define route handlers (which function should handle an incoming request based on the URL and HTTP method), which are the functions responsible for responding to client requests to a given endpoint.
1. **Decorator (@)**: This symbol is used to define a decorator, which is a way to dynamically add functionality to functions or methods. In FastAPI, decorators are used to associate a function with a particular HTTP method and path.
2. **App Instance (app)**: This represents an instance of the FastAPI class. It is the core of your application and maintains the list of defined routes, request handlers, and other configurations.
3. **HTTP Method (get, post, etc.)**: The HTTP method specifies the type of HTTP request the route will respond to. For example, get is used for retrieving data, and post is used for sending data to the server. FastAPI provides a decorator for each standard HTTP method, such as @app.put, @app.delete, @app.patch, and @app.options, allowing you to define handlers for different types of client requests. For detailed info refer to this webiste by [Mdn](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods).
4. **Path/Endpoint ("/endpoint_name")**: This is the URL path where the API will be accessible. When a client makes a request to this path using the specified HTTP method, FastAPI will execute the associated function and return the response.
- Using **async** in FastAPI allows for non-blocking operations, enabling the server to handle other requests while waiting for I/O tasks, like database queries or model loading, to complete. This leads to improved concurrency and resource utilization, enhancing the application's ability to manage multiple simultaneous requests efficiently.
## Data Models in FASTAPI
```Python
### Data Models in FastAPI
##### 1. IrisData class
```python
class IrisData(BaseModel):
petal_length: float
sepal_length:float
petal_width:float
sepal_width:float
```
The `IrisData` class is a [Pydantic model](https://docs.pydantic.dev/latest/concepts/models/) which defines the expected structure of the data for a request body. When you use it as a type annotation for a route operation parameter, FastAPI will perform the following actions:
- Request Body Reading: FastAPI will read the request body as JSON.
- Data Conversion: It will convert the corresponding types, if necessary.
- Data Validation: It will validate the data. If the data is invalid, it will return a 422 Unprocessable Entity error response with details about what was incorrect.
The **IrisData** class is a [Pydantic model](https://docs.pydantic.dev/latest/concepts/models/) which defines the expected structure of the data for a request body. When you use it as a type annotation for a route operation parameter, FastAPI will perform the following actions:
- **Request Body Reading:** FastAPI will read the request body as JSON.
- **Data Conversion:** It will convert the corresponding types, if necessary.
- **Data Validation:** It will validate the data. If the data is invalid, it will return a 422 Unprocessable Entity error response with details about what was incorrect.
#### 2. IrisResponse class
```Python
```python
class IrisResponse(BaseModel):
response:int
```
The `IrisResponse` class is another Pydantic model that defines the structure of the response data for an endpoint. When you specify response_model=IrisResponse in a route operation, it tells FastAPI to:
- Serialize the Output: Convert the output data to JSON format according to the IrisResponse model.
- Document the API: Include the IrisResponse model in the generated API documentation, so API consumers know what to expect in the response.

### Request Body Reading
---
When a client sends a request to a FastAPI endpoint, the request can include a body with data. For routes that expect data (commonly POST, PUT, or PATCH requests), this data is often in JSON format. FastAPI automatically reads the request body by checking the Content-Type header, which should be set to application/json for JSON payloads.
The **IrisResponse** class is another Pydantic model that defines the structure of the response data for an endpoint. When you specify **response_model=IrisResponse** in a route operation, it tells FastAPI to:
- **Serialize the Output**: Convert the output data to JSON format according to the IrisResponse model.
- **Document the API**: Include the IrisResponse model in the generated API documentation, so API consumers know what to expect in the response.
### Data Conversion
---
Once the request body is read, FastAPI utilizes Pydantic models to parse the JSON data. Pydantic attempts to construct an instance of the specified model using the data from the request body. During this instantiation, Pydantic converts the JSON data into the proper Python data types as declared in the model.

For instance, if the JSON object has a field like petal_length with a value of "5.1" (a string), and the model expects a float, Pydantic will transform the string into a float. If conversion isn't possible (say, the value was "five point one"), Pydantic will raise a validation error.

### Data Validation
---
Pydantic checks that all required fields are present and that the values are of the correct type, adhering to any constraints defined in the model (such as string length or number range). If the validation passes, the endpoint has a verified Python object to work with. If validation fails (due to missing fields, incorrect types, or constraint violations), FastAPI responds with a 422 Unprocessable Entity status. This response includes a JSON body detailing the validation errors, aiding clients in correcting their request data.

## Error Handling
Error handling in FastAPI can be effectively managed using the HTTPException class. HTTPException is used to explicitly signal an HTTP error status code and return additional details about the error. When an HTTPException is raised within a route, FastAPI will catch the exception and use its content to form the HTTP response.
### FastAPI features
- Instantiation: The HTTPException class is instantiated with at least two arguments: status_code and detail. The status_code argument is an integer that represents the HTTP status code (e.g., 404 for Not Found, 400 for Bad Request). The detail argument is a string or any JSON-encodable object that describes the error.
- Response: When an HTTPException is raised, FastAPI sends an HTTP response with the status code specified. The detail provided in the HTTPException is sent as the body of the response in JSON format.
1. **Request Body Reading**: When a client sends a request to a FastAPI endpoint, the request can include a body with data. For routes that expect data (commonly POST, PUT, or PATCH requests), this data is often in JSON format. FastAPI automatically reads the request body by checking the Content-Type header, which should be set to application/json for JSON payloads.
2. **Data Conversion**: Once the request body is read, FastAPI utilizes Pydantic models to parse the JSON data. Pydantic attempts to construct an instance of the specified model using the data from the request body. During this instantiation, Pydantic converts the JSON data into the proper Python data types as declared in the model.
- For instance, if the JSON object has a field like petal_length with a value of "5.1" (a string), and the model expects a float, Pydantic will transform the string into a float. If conversion isn't possible (say, the value was "five point one"), Pydantic will raise a validation error.
3. **Data Validation**: Pydantic checks that all required fields are present and that the values are of the correct type, adhering to any constraints defined in the model (such as string length or number range). If the validation passes, the endpoint has a verified Python object to work with. If validation fails (due to missing fields, incorrect types, or constraint violations), FastAPI responds with a 422 Unprocessable Entity status. This response includes a JSON body detailing the validation errors, aiding clients in correcting their request data.
4. **Error Handling**: Error handling in FastAPI can be effectively managed using the HTTPException class. HTTPException is used to explicitly signal an HTTP error status code and return additional details about the error. When an HTTPException is raised within a route, FastAPI will catch the exception and use its content to form the HTTP response.
- **Instantiation**: The HTTPException class is instantiated with at least two arguments: status_code and detail. The status_code argument is an integer that represents the HTTP status code (e.g., 404 for Not Found, 400 for Bad Request). The detail argument is a string or any JSON-encodable object that describes the error.
- **Response**: When an HTTPException is raised, FastAPI sends an HTTP response with the status code specified. The detail provided in the HTTPException is sent as the body of the response in JSON format.

```Python
```python
from fastapi import FastAPI, HTTPException
app = FastAPI()
Expand All @@ -80,33 +137,14 @@ async def read_item(item_id: int):
raise HTTPException(status_code=404, detail=f"Item with ID {item_id} not found")
return item
```
In this example, `get_item_by_id` is a function that retrieves an item based on its ID. If no item with the given ID is found, an HTTPException with a 404 Not Found status code is raised, and the detail message is customized to include the ID of the item that was not found.

In this example, **get_item_by_id** is a function that retrieves an item based on its ID. If no item with the given ID is found, an HTTPException with a 404 Not Found status code is raised, and the detail message is customized to include the ID of the item that was not found.

FastAPI will catch this exception and return a response with a 404 status code and a JSON body like this:
```JSON

```json
{
"detail": "Item with ID 1 not found"
}
```

For more information on how to handle errors in FASTAPI refer to this [documentation](https://fastapi.tiangolo.com/tutorial/handling-errors/)


## Testing endpoints
To view the documentation of your api model you can use http://127.0.0.1:8000/docs (or) http://localhost:8000/docs after you run you run your FASTAPI app.

![API page](assets/docs.png)

You can also test out the results of your endpoints by interacting with them. Click on the dropdown button of your endpoint -> Try it out -> Fill the Request body -> Click on Execute button.

![API response](assets/api_response.png)

You can also use other tools like POSTMAN for API testing.
FAST API detailed video: https://www.youtube.com/watch?v=KReburHqRIQ&list=PLcS4TrUUc53LeKBIyXAaERFKBJ3dvc9GZ&index=5







- For more information on how to handle errors in FASTAPI refer to this [documentation](https://fastapi.tiangolo.com/tutorial/handling-errors/).
Binary file removed Labs/API_Labs/FastAPI_Labs/iris_model.pkl
Binary file not shown.
Binary file added Labs/API_Labs/FastAPI_Labs/model/iris_model.pkl
Binary file not shown.
44 changes: 2 additions & 42 deletions Labs/API_Labs/FastAPI_Labs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,42 +1,2 @@
annotated-types==0.6.0
anyio==4.2.0
certifi==2023.11.17
click==8.1.7
dnspython==2.5.0
email-validator==2.1.0.post1
exceptiongroup==1.2.0
fastapi==0.109.0
h11==0.14.0
httpcore==1.0.2
httptools==0.6.1
httpx==0.26.0
idna==3.6
itsdangerous==2.1.2
Jinja2==3.1.3
joblib==1.3.2
MarkupSafe==2.1.4
numpy==1.26.3
orjson==3.9.12
pandas==2.2.0
pydantic==2.6.0
pydantic-extra-types==2.5.0
pydantic-settings==2.1.0
pydantic_core==2.16.1
python-dateutil==2.8.2
python-dotenv==1.0.1
python-multipart==0.0.6
pytz==2023.4
PyYAML==6.0.1
scikit-learn==1.4.0
scipy==1.12.0
six==1.16.0
sniffio==1.3.0
starlette==0.35.1
threadpoolctl==3.2.0
typing_extensions==4.9.0
tzdata==2023.4
ujson==5.9.0
uvicorn==0.27.0.post1
uvloop==0.19.0
watchfiles==0.21.0
websockets==12.0
scikit-learn==1.5.1
fastapi[all]==0.111.1
2 changes: 1 addition & 1 deletion Labs/API_Labs/FastAPI_Labs/src/predict.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ def predict_data(X):
Returns:
y_pred (numpy.ndarray): Predicted class labels.
"""
model = joblib.load("../iris_model.pkl")
model = joblib.load("../model/iris_model.pkl")
y_pred = model.predict(X)
return y_pred
2 changes: 1 addition & 1 deletion Labs/API_Labs/FastAPI_Labs/src/train.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def fit_model(X_train, y_train):
"""
dt_classifier = DecisionTreeClassifier(max_depth=3, random_state=12)
dt_classifier.fit(X_train, y_train)
joblib.dump(dt_classifier, "iris_model.pkl")
joblib.dump(dt_classifier, "../model/iris_model.pkl")

if __name__ == "__main__":
X, y = load_data()
Expand Down

0 comments on commit 474809e

Please sign in to comment.