|
| 1 | +--- |
| 2 | +title: "Building Efficient ML APIs with FastAPI: A Comprehensive Guide - Part 2" |
| 3 | +description: "FastAPI: Guide to building prototype ML applications quick and easy AF" |
| 4 | +slug: fastapi2 |
| 5 | +date: 2024-07-13 00:00:00+0000 |
| 6 | +image: /blogs/fastapi/fastAPI.webp #Fixit |
| 7 | +license: false |
| 8 | +categories: |
| 9 | + - Deployment |
| 10 | + - REST |
| 11 | +tags: |
| 12 | + - Model Deployment |
| 13 | + - REST |
| 14 | + - API |
| 15 | + - FastAPI |
| 16 | +weight: 1 # You can add weight to some posts to override the default sorting (date descending) |
| 17 | +--- |
| 18 | + |
| 19 | +## Advanced Features with FastAPI |
| 20 | +After learning how to be able to serve my models using FastAPI, I decided to extend it further in order to enhance the functionality, security and scalability of my API. For this, I read about additional topics like input validation, error handling and authentication, which although won't be help me much for the purpose for which I initially started learning FastAPI, i.e., to make demo apps for my ML models, but they are crucial for developing production-ready APIs and so I thought about giving them a read too. |
| 21 | + |
| 22 | +### Input Validation |
| 23 | +Input validation ensures that incoming data meets specified criteria before processing. FastAPI integrates seamlessly with `Pydantic` for data validation, leveraging Python's type hints. Here’s how I implemented input validation in my FastAPI application: |
| 24 | + |
| 25 | +```python |
| 26 | +from pydantic import BaseModel, Field |
| 27 | + |
| 28 | +class Item(BaseModel): |
| 29 | + name : str = Field(..., decription = "The name of the item") |
| 30 | + price : float = Field(..., gt = 0, decription = "The price of the item") |
| 31 | + |
| 32 | +@app.post('/items/') |
| 33 | +def create_item(item: Item): |
| 34 | + return {'name' : item.name, 'price' : item.price} |
| 35 | +``` |
| 36 | + |
| 37 | +- **Explanation:** In this example, the `Item` Pydantic model specifies that `name` is a required string field and `price` is a required float field greater than 0. FastAPI automatically validates incoming requests against these criteria and returns appropriate error responses if validation fails. |
| 38 | + |
| 39 | +### Error Handling |
| 40 | +Error handling is crucial for providing informative responses when something goes wrong in an API request. FastAPI simplifies error handling with HTTPException and exception handling mechanisms. Here’s an example of handling errors in FastAPI which we've seen before too during CRUD operations. |
| 41 | +```python |
| 42 | +from fastapi import HTTPException |
| 43 | + |
| 44 | +@app.get('items/{item_id}') |
| 45 | +def read_item(item_id : int): |
| 46 | + if item_id not in range(1, 6): |
| 47 | + raise HTTPException(status_code = 404, detail = "Item not found") |
| 48 | + return {'item' : item_id} |
| 49 | +``` |
| 50 | + |
| 51 | +- **Explanation:** In this example, if the `item_id` provided in the request path is not within the range of valid item IDs (1 to 5), FastAPI raises an `HTTPException` with a 404 status code and a detailed error message along with that. |
| 52 | + |
| 53 | +### Authentication and Authorization |
| 54 | +Securing APIs with authentication and authorization mechanisms is essential for protecting sensitive data and restricting access to authorized users. FastAPI supports various authentication methods, including OAuth2, JWT (JSON Web Tokens), and basic authentication. Here’s a simple example of implementing JWT authentication in FastAPI: |
| 55 | + |
| 56 | +```python |
| 57 | +from fastapi import Depends, HTTPException, status |
| 58 | +from fastapi.security import OAuth2PasswordBearer |
| 59 | + |
| 60 | +security = OAuth2PasswordBearer(tokenurl='/token') |
| 61 | + |
| 62 | +# Mock user database |
| 63 | +fake_users_db = { |
| 64 | + "johndoe": { |
| 65 | + "username": "johndoe", |
| 66 | + "hashed_password": "$2b$12$V2qBpWn5GDK/9QrF3l7AyO6x9BdFssEcVbOeYURVn8t62MzK4IO5u", # hashed version of password 'secret' |
| 67 | + "disabled": False, |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +def verify_password(username: str, password: str): |
| 72 | + user = fake_users_db.get(username) |
| 73 | + if not user or not password: |
| 74 | + return False |
| 75 | + if password == 'secret': |
| 76 | + return True |
| 77 | + |
| 78 | + |
| 79 | +def get_current_user(token: str = Depends(security)): |
| 80 | + username, _ = token.split(":") |
| 81 | + user = fake_users_db.get(username) |
| 82 | + |
| 83 | + if not user: |
| 84 | + raise HTTPException( |
| 85 | + status_code = status.HTTP_401_UNAUTHORIZED, |
| 86 | + detail = 'Invalid Credentials', |
| 87 | + headers = {"WWW-Authenticate": "Bearer"} |
| 88 | + ) |
| 89 | + return user |
| 90 | + |
| 91 | +@app.get('users/me') |
| 92 | +def read_current_user(current_user : dict = Depends(get_current_user)): |
| 93 | + return current_user |
| 94 | +``` |
| 95 | + |
| 96 | +- **Explanation:** In this example, `OAuth2PasswordBearer` is used to define an authentication scheme using OAuth2 with password flow. The `get_current_user()` function verifies the JWT token and retrieves the current user from the mock database `(fake_users_db)`. The `read_current_user()` endpoint demonstrates accessing user information with authentication. |
| 97 | + |
| 98 | +## Deployment Strategies for FASTApi |
| 99 | +Moving further, after building the app, it is also essential to be able to deploy it. FastAPI applications can be deployed using various deployment options, depending on scalability requirements, infrastructure preferences, and operational constraints. Here are some common deployment strategies. |
| 100 | + |
| 101 | +- **Docker Containers:** Containerization with Docker allows packaging FastAPI applications and their dependencies into lightweight, portable containers. This approach facilitates consistent deployment across different environments and simplifies scaling. |
| 102 | + |
| 103 | +- **Serverless Deployment:** Serverless platforms like AWS Lambda or Azure Functions offer auto-scaling and pay-per-use pricing models. FastAPI applications can be deployed as serverless functions, eliminating the need to manage infrastructure manually. |
| 104 | + |
| 105 | +- **Traditional Servers:** Deploying FastAPI on traditional servers or virtual machines provides more control over infrastructure configuration and performance tuning. Platforms like AWS EC2, Google Compute Engine, or DigitalOcean Droplets are commonly used for this approach. |
| 106 | + |
| 107 | +### Deployment Best Practices |
| 108 | +When deploying FastAPI applications in production, consider the following best practices to ensure reliability, security, and scalability: |
| 109 | + |
| 110 | + |
| 111 | +- **Environment Configuration:** Use environment variables for sensitive information (e.g., API keys, database credentials) and configuration settings (e.g., logging levels, debug mode). |
| 112 | + |
| 113 | +- **Monitoring and Logging:** Implement monitoring solutions (e.g., Prometheus, Datadog) to track performance metrics, error rates, and application health. Centralized logging (e.g., ELK Stack, Splunk) helps in troubleshooting and debugging issues. |
| 114 | + |
| 115 | +- **Security Measures:** Secure FastAPI applications with HTTPS/TLS encryption to protect data in transit. Implement authentication and authorization mechanisms (e.g., OAuth2, JWT) to control access to APIs and sensitive resources. |
| 116 | + |
| 117 | +- **Scaling Strategies:** Plan for horizontal scaling by deploying multiple instances of FastAPI behind a load balancer to handle increased traffic. Use auto-scaling features offered by cloud providers for efficient resource utilization. |
| 118 | + |
| 119 | +### Deployment with Docker |
| 120 | +For my learning purposes, I did not go in depth but still learned how to containerize a FASTApi application using Docker atleast. |
| 121 | + |
| 122 | +1. **Dockerfile:** Create a `Dockerfile` in your FastAPI project directory. |
| 123 | +```Dockerfile |
| 124 | +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 |
| 125 | + |
| 126 | +COPY ./app /app |
| 127 | +``` |
| 128 | + |
| 129 | +Replace `./app` with the path to your FastAPI application directory. |
| 130 | + |
| 131 | +2. **Build Docker Image:** Build the docker image |
| 132 | +```bash |
| 133 | +docker build -t fastapi-app |
| 134 | +``` |
| 135 | + |
| 136 | +3. **Run Docker Container:** Run the Docker container. |
| 137 | +```bash |
| 138 | +docker run -d -p 80:80 fastapi-app |
| 139 | +``` |
| 140 | + |
| 141 | +Adjust `-p 80:80` to map the container port to a desired host port. |
| 142 | + |
| 143 | +## Continuous Integration and Delivery (CI/CD) for FastAPI |
| 144 | +Phewwww! Now that we've come all the way to deployment, I thought why not also add some CI/CD pipelines to it as it helps automate the building, testing and deployment processes, ensuring reliable and efficient software delivery. |
| 145 | + |
| 146 | +### Setting up a CI/CD Pipeline |
| 147 | +CI/CD pipelines automate several key tasks in the software development lifecycle, including: |
| 148 | + |
| 149 | +- **Code Compilation:** Compile and package the FastAPI application. |
| 150 | +- **Testing:** Execute automated tests to verify functionality and detect issues early. |
| 151 | +- **Deployment:** Deploy the application to staging or production environments automatically. |
| 152 | + |
| 153 | +### Example CI/CD Pipeline with GitHub Actions |
| 154 | +Here's an example of setting up a CI/CD pipeline for FastAPI. I found this online and have yet to test it though. |
| 155 | + |
| 156 | +```yaml |
| 157 | +name: CI/CD Pipeline |
| 158 | + |
| 159 | +on: |
| 160 | + push: |
| 161 | + branches: |
| 162 | + - main |
| 163 | + pull_request: |
| 164 | + branches: |
| 165 | + - main |
| 166 | + |
| 167 | +jobs: |
| 168 | + build: |
| 169 | + runs-on: ubuntu-latest |
| 170 | + |
| 171 | + steps: |
| 172 | + - name: Checkout code |
| 173 | + uses: actions/checkout@v2 |
| 174 | + |
| 175 | + - name: Set up Python |
| 176 | + uses: actions/setup-python@v2 |
| 177 | + with: |
| 178 | + python-version: 3.9 |
| 179 | + |
| 180 | + - name: Install dependencies |
| 181 | + run: | |
| 182 | + python -m pip install --upgrade pip |
| 183 | + pip install -r requirements.txt |
| 184 | +
|
| 185 | + - name: Run tests |
| 186 | + run: | |
| 187 | + pytest --cov=app tests/ |
| 188 | +
|
| 189 | + - name: Build Docker image |
| 190 | + run: | |
| 191 | + docker build -t my-fastapi-app . |
| 192 | + docker tag my-fastapi-app:latest my-fastapi-app:$(git rev-parse --short $GITHUB_SHA) |
| 193 | +
|
| 194 | + - name: Push Docker image to registry |
| 195 | + uses: docker/login-action@v2 |
| 196 | + with: |
| 197 | + username: ${{ secrets.DOCKER_USERNAME }} |
| 198 | + password: ${{ secrets.DOCKER_PASSWORD }} |
| 199 | + - name: Push Docker image |
| 200 | + run: docker push my-fastapi-app |
| 201 | + |
| 202 | + - name: Deploy to staging |
| 203 | + if: github.ref == 'refs/heads/main' |
| 204 | + run: | |
| 205 | + ssh user@staging-server 'docker pull my-fastapi-app:latest' |
| 206 | + ssh user@staging-server 'docker-compose up -d' |
| 207 | +``` |
| 208 | +
|
| 209 | +## Monitoring and Optimization for FastAPI |
| 210 | +Last but not the least, it is equally important to monitor and log the metrics of our FastAPI application as building one as monitoring provides us with insights into application behaviour and performance metrics and also helps enhance efficiency and responsiveness. |
| 211 | +
|
| 212 | +### Monitoring Strategies |
| 213 | +Effective monitoring helps identify issues, track performance metrics, and ensure the smooth operation of FastAPI applications. Consider the following monitoring strategies: |
| 214 | +
|
| 215 | +- **Logging:** Implement logging to record application events, errors, and debug information. Use structured logging for better analysis and troubleshooting. |
| 216 | +
|
| 217 | +- **Metrics Collection:** Monitor key performance indicators (KPIs) such as request/response times, error rates, and resource utilization (CPU, memory). Tools like Prometheus, DataDog, or AWS CloudWatch Metrics can be integrated for metrics collection. |
| 218 | +
|
| 219 | +- **Alerting:** Set up alerts based on predefined thresholds (e.g., high error rates, CPU utilization) to proactively address potential issues. |
| 220 | +
|
| 221 | +### Performace Optimization |
| 222 | +Optimizing FastAPI applications improves efficiency and responsiveness, enhancing user experience and reducing operational costs. Consider the following optimization techniques: |
| 223 | +
|
| 224 | +- **Code Profiling:** Identify performance bottlenecks using profiling tools (e.g., cProfile) to analyze execution times and optimize critical sections of code. |
| 225 | +
|
| 226 | +- **AsyncIO:** Leverage FastAPI's asynchronous capabilities and AsyncIO to handle concurrent requests efficiently, especially for I/O-bound operations. |
| 227 | +
|
| 228 | +- **Caching:** Implement caching mechanisms (e.g., Redis, Memcached) to store and retrieve frequently accessed data, reducing database load and improving response times. |
| 229 | +
|
| 230 | +- **Database Optimization:** Optimize database queries with indexes, query optimization techniques, and connection pooling to enhance database performance. |
| 231 | +
|
| 232 | +### Adding Metrics to FastAPI |
| 233 | +Integrate Prometheus for monitoring metrics in FastAPI: |
| 234 | +
|
| 235 | +1. Install Prometheus client |
| 236 | +```bash |
| 237 | +pip install prometheus-client |
| 238 | +``` |
| 239 | + |
| 240 | +2. Add Metrics Middleware |
| 241 | +```python |
| 242 | +from fastapi import FastAPI |
| 243 | +from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST |
| 244 | +from starlette.middleware.base import BaseHTTPMiddleware |
| 245 | + |
| 246 | +app = FastAPI() |
| 247 | + |
| 248 | +# Metrics |
| 249 | +REQUEST_COUNT = Counter("request_count", "Total count of requests", ["method", "endpoint", "status_code"]) |
| 250 | +REQUEST_LATENCY = Histogram("request_latency_seconds", "Request latency in seconds", ["method", "endpoint"]) |
| 251 | + |
| 252 | +class PrometheusMiddleware(BaseHTTPMiddleware): |
| 253 | + async def dispatch(self, request, call_next): |
| 254 | + path = request.url.path |
| 255 | + method = request.method |
| 256 | + try: |
| 257 | + response = await call_next(request) |
| 258 | + status_code = response.status_code |
| 259 | + return response |
| 260 | + finally: |
| 261 | + REQUEST_COUNT.labels(method=method, endpoint=path, status_code=status_code).inc() |
| 262 | + latency = time.time() - request.scope["start_time"] |
| 263 | + REQUEST_LATENCY.labels(method=method, endpoint=path).observe(latency) |
| 264 | + |
| 265 | +app.add_middleware(PrometheusMiddleware) |
| 266 | +``` |
| 267 | + |
| 268 | +3. Expose Metrics Endpoint: |
| 269 | +```python |
| 270 | +from fastapi.responses import Response |
| 271 | + |
| 272 | +@app.get("/metrics") |
| 273 | +def get_metrics(): |
| 274 | + return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST) |
| 275 | +``` |
| 276 | + |
| 277 | +4. Instrument Endpoints: |
| 278 | +```python |
| 279 | +@app.get("/items/") |
| 280 | +async def read_items(): |
| 281 | + # Endpoint logic |
| 282 | + return {"message": "Items retrieved successfully"} |
| 283 | +``` |
| 284 | + |
| 285 | +## Conclusion |
| 286 | +And that's a wrap. Phewww.. While there is a lot to cover in FastAPI and certainly it can't be done in a single blog, I still attempted to atleast log some of the things which I learnt during this time of learning FastAPI. After all, this is not a book lol. Just a reference blog for me to look back to when I get stuck with something working with FastAPI again. I think it covers most of the basic stuff and just in case I missed out something, I can always go back and have a look at the FastAPI docs. For now, I'd say that FastAPI is truly amazing as for how easy it is to make and manage endpoints and deploying my ML apps to it. And the best part? It's fairly easy to learn too. It took me just 2 days to go through all this and I believe someone who works with Python will find it fairly easy to use. With that being said, I think that's all from my side regarding FastAPI for now. Until Next Time. Adios! |
0 commit comments