Skip to content

Commit 36880a8

Browse files
committed
add fastapi part 2 blog
1 parent 883f1d1 commit 36880a8

File tree

68 files changed

+1552
-846
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+1552
-846
lines changed

Diff for: content/blogs/fastapi2.md

+127-63
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ tags:
1616
weight: 1 # You can add weight to some posts to override the default sorting (date descending)
1717
---
1818

19+
In the [previous blog](../fastapi), I mentioned how I was able to make simple endpoints with FastAPI and how I was even able to use it to deploy a pretrained ML model. Moving forward, I will be discussing about how to make our FastAPI application more robust and efficient with some commonly used practices I found around the internet. Note that these are completely optional and for just deploying a model, even the previous blog will suffice. It's just that I thought while I am at it, might just learn a bit more the best practices regarding FastAPI.
20+
1921
## Advanced Features with FastAPI
2022
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.
2123

@@ -54,46 +56,64 @@ def read_item(item_id : int):
5456
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:
5557

5658
```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
59+
# Pydantic models
60+
class User(BaseModel):
61+
username: str
62+
email: Optional[str] = None
63+
full_name: Optional[str] = None
64+
65+
class UserInDB(User):
66+
hashed_password: str
67+
68+
# OAuth2 scheme using JWT tokens
69+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
70+
71+
# Function to verify JWT token and extract user information
72+
def verify_token(token: str = Depends(oauth2_scheme)):
73+
try:
74+
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
75+
username: str = payload.get("sub")
76+
if username is None:
77+
raise HTTPException(status_code=401, detail="Invalid credentials")
78+
token_data = {"username": username}
79+
except jwt.ExpiredSignatureError:
80+
raise HTTPException(status_code=401, detail="Token has expired")
81+
except jwt.JWTError:
82+
raise HTTPException(status_code=401, detail="Invalid token")
83+
return token_data
84+
85+
# Route to generate JWT token
86+
@app.post("/token")
87+
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
88+
user_dict = fake_users_db.get(form_data.username)
89+
if user_dict and form_data.password == "fakepassword":
90+
# Generate JWT token
91+
expiration = datetime.utcnow() + timedelta(minutes=TOKEN_EXPIRATION)
92+
token_data = {"sub": form_data.username, "exp": expiration}
93+
token = jwt.encode(token_data, SECRET_KEY, algorithm="HS256")
94+
return {"access_token": token, "token_type": "bearer"}
95+
raise HTTPException(status_code=401, detail="Incorrect username or password")
96+
97+
# Example protected route
98+
@app.get("/users/me", response_model=User)
99+
async def read_users_me(current_user: User = Depends(verify_token)):
100+
return fake_users_db[current_user["username"]]
94101
```
95102

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.
103+
- **User Database:** Here, `fake_users_db` simulates a user database with a single user for demonstration.
104+
- **Pydantic models:** `User` and `UserInDB` are Pydantic models used for type checking.
105+
- **OAuth2 Scheme:** `oauth2_scheme` is configured using `OAuth2PasswordBearer`, specifying the token URL `(/token)` for token retrieval.
106+
- **Token Verification:** `verify_token` function is a dependency that verifies and decodes the JWT token sent in the Authorization header of requests.
107+
- **Token Generation:** The `/token` endpoint (login function) handles user authentication. If the credentials are valid (fakepassword is the hardcoded password for demonstration), it generates a JWT token using jwt.encode.
108+
- **Protected Route:** The `/users/me` endpoint (read_users_me function) demonstrates a protected route that requires JWT token authentication (verify_token dependency). It returns user information based on the decoded JWT token and remains protected if try to access it without logging in or logging in with some other username or password.
109+
110+
Now, if open up `Swagger UI`, and try to access the `/users/me` endpoint, it won't show anything and give an error message saying we are not authenticated.
111+
112+
![Unautorized access](unauthorized.png)
113+
114+
However, if we login (Use `Autheticate` at the top right hand side of Swagger) with the credentials, `johndoe` and `fakepasword`, we are able to generate our JWT token and thus are logged in and can now access our protected endpoint.
115+
116+
![Authorized Access](authorized.png)
97117

98118
## Deployment Strategies for FASTApi
99119
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.
@@ -121,24 +141,66 @@ For my learning purposes, I did not go in depth but still learned how to contain
121141

122142
1. **Dockerfile:** Create a `Dockerfile` in your FastAPI project directory.
123143
```Dockerfile
124-
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
144+
# Use an official Python runtime as a parent image
145+
FROM python:3.9-slim
146+
147+
# Set environment variables
148+
ENV PYTHONDONTWRITEBYTECODE 1
149+
ENV PYTHONUNBUFFERED 1
150+
151+
# Set the working directory in the container
152+
WORKDIR /app
153+
154+
# Install system dependencies
155+
RUN apt-get update \
156+
&& apt-get install -y --no-install-recommends netcat-traditional \
157+
&& apt-get clean \
158+
&& rm -rf /var/lib/apt/lists/*
159+
160+
# Install Python dependencies
161+
COPY requirements.txt /app/
162+
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
163+
164+
# Copy the FastAPI app code into the container
165+
COPY . /app/
166+
167+
# Expose the port that FastAPI runs on
168+
EXPOSE 8000
169+
170+
# Command to run the FastAPI application
171+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
125172

126-
COPY ./app /app
127173
```
128174

129-
Replace `./app` with the path to your FastAPI application directory.
175+
2. Add a `requirements.txt` file in the FastAPI directory
176+
```requirements.txt
177+
fastapi
178+
uvicorn
179+
pyjwt
180+
prometheus-client
181+
```
130182

131183
2. **Build Docker Image:** Build the docker image
132184
```bash
133-
docker build -t fastapi-app
185+
docker build -t my-fastapi-app .
134186
```
135187

136188
3. **Run Docker Container:** Run the Docker container.
137189
```bash
138-
docker run -d -p 80:80 fastapi-app
190+
docker run -d --name my-fastapi-container -p 8000:8000 my-fastapi-app
191+
```
192+
193+
4. The container should run without any issue and we can even monitor its health using:
194+
```bash
195+
sudo docker logs my-fastapi-container
139196
```
140197

141-
Adjust `-p 80:80` to map the container port to a desired host port.
198+
![Running Docker Container](docker.png)
199+
200+
5. To stop the container use:
201+
```bash
202+
docker stop my-fastapi-container
203+
```
142204

143205
## Continuous Integration and Delivery (CI/CD) for FastAPI
144206
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.
@@ -250,37 +312,39 @@ REQUEST_COUNT = Counter("request_count", "Total count of requests", ["method", "
250312
REQUEST_LATENCY = Histogram("request_latency_seconds", "Request latency in seconds", ["method", "endpoint"])
251313

252314
class PrometheusMiddleware(BaseHTTPMiddleware):
253-
async def dispatch(self, request, call_next):
254-
path = request.url.path
255-
method = request.method
315+
async def dispatch(self, request : Response, call_next):
316+
start_time = time.time()
256317
try:
257318
response = await call_next(request)
258319
status_code = response.status_code
259320
return response
321+
except HTTPException as http_exc:
322+
# Capture HTTPException to get status_code
323+
status_code = http_exc.status_code
324+
raise http_exc
325+
260326
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)
327+
REQUEST_COUNT.labels(method=request.method, endpoint=request.url.path, status_code=status_code).inc()
328+
latency = time.time() - start_time
329+
REQUEST_LATENCY.labels(method=request.method, endpoint=request.url.path).observe(latency)
264330

265331
app.add_middleware(PrometheusMiddleware)
266-
```
267-
268-
3. Expose Metrics Endpoint:
269-
```python
270-
from fastapi.responses import Response
271332

272333
@app.get("/metrics")
273334
def get_metrics():
274335
return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)
275336
```
276337

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-
```
338+
![FastAPI Metrics](metrics.png)
339+
340+
After this, we will be able to access our FastAPI metrics at the given endpoint. However, in order to understand it more efficiently, one can even use services like `Prometheus` and `Grafana`
284341

285342
## 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!
343+
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!
344+
345+
## Appendix
346+
- [Code for this blog](main.py)
347+
- [Dockerfile Code](Dockerfile)
348+
- [GitHub Actions Code](fastapi.yaml)
349+
350+
> Photo by [Data Scientist](https://datascientest.com/en/fastapi-everything-you-need-to-know-about-the-most-widely-used-python-web-framework-for-machine-learning)

0 commit comments

Comments
 (0)