FastAPI app that has an endpoint using a decorator¶
Here’s a simple containerized example of a FastAPI app that has an endpoint using a decorator that prints the received parameters from the Request. We will use Docker for containerization.
FastAPI App (app.py)¶
from fastapi import FastAPI, Request
from functools import wraps
app = FastAPI()
# Demo decorator to print request parameters
def print_request_params(func):
@wraps(func)
async def wrapper(*args, **kwargs):
request: Request = kwargs.get('request')
if request:
query_params = request.query_params
print(f"Received query parameters: {query_params}")
return await func(*args, **kwargs)
return wrapper
@app.get("/hello")
@print_request_params
async def hello_world(request: Request):
return {"message": "Hello, World!"}
Dockerfile¶
# Use an official Python runtime as a parent image
FROM python:3.10-slim
# Set the working directory in the container
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . /app
# Install FastAPI and Uvicorn
RUN pip install fastapi uvicorn
# Make port 8000 available to the world outside this container
EXPOSE 8000
# Run app.py when the container launches
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Docker Commands¶
- Build the Docker image:
docker build -t fastapi-hello-world .
- Run the container:
docker run -d -p 8000:8000 fastapi-hello-world
Test the API¶
Now, you can test the API by sending a request with query parameters, for example:
curl "http://localhost:8000/hello?name=John&age=30"
In the container logs, you should see the printed query parameters:
Received query parameters: name=John&age=30
This demonstrates a FastAPI app containerized with a decorator that prints the received query parameters from the request.
If you pass a header parameter to the FastAPI app in the request, it will not be captured or printed by the current implementation of the decorator, because the decorator is only printing query parameters (request.query_params).
To capture and print header parameters as well, you need to modify the decorator to access the headers from the Request object.
Here’s how you can adjust the decorator to print both query parameters and headers:
Updated app.py to Print Headers¶
from fastapi import FastAPI, Request
from functools import wraps
app = FastAPI()
# Demo decorator to print request query parameters and headers
def print_request_params(func):
@wraps(func)
async def wrapper(*args, **kwargs):
request: Request = kwargs.get('request')
if request:
# Print query parameters
query_params = request.query_params
print(f"Received query parameters: {query_params}")
# Print headers
headers = request.headers
print(f"Received headers: {headers}")
return await func(*args, **kwargs)
return wrapper
@app.get("/hello")
@print_request_params
async def hello_world(request: Request):
return {"message": "Hello, World!"}
Now, if you send a request with headers, for example:
curl -H "X-Custom-Header: custom_value" "http://localhost:8000/hello?name=John&age=30"
The log output will print both query parameters and headers:¶
Received query parameters: name=John&age=30
Received headers: Headers({'host': 'localhost:8000', 'x-custom-header': 'custom_value', 'user-agent': 'curl/7.68.0', 'accept': '*/*'})
As you can see, the headers will now be printed along with any other headers automatically included by the client (like Host, User-Agent, etc.). The custom header X-Custom-Header: custom_value is included in the printed output.
To capture and print query parameters, headers, and body parameters (including JSON body) in the decorator, we need to access the body of the request in addition to the query parameters and headers. Here’s how you can extend the decorator to print the body as JSON, along with the query parameters and headers.
Updated app.py to Print Query Params, Headers, and JSON Body¶
from fastapi import FastAPI, Request
from functools import wraps
app = FastAPI()
# Demo decorator to print request query parameters, headers, and JSON body
def print_request_params(func):
@wraps(func)
async def wrapper(*args, **kwargs):
request: Request = kwargs.get('request')
if request:
# Print query parameters
query_params = request.query_params
print(f"Received query parameters: {query_params}")
# Print headers
headers = request.headers
print(f"Received headers: {headers}")
# Print JSON body (if available)
if request.method in ["POST", "PUT", "PATCH"]:
body = await request.json() # Parse body as JSON
print(f"Received JSON body: {body}")
return await func(*args, **kwargs)
return wrapper
@app.post("/hello")
@print_request_params
async def hello_world(request: Request):
return {"message": "Hello, World!"}
Dockerfile (same as before)¶
# Use an official Python runtime as a parent image
FROM python:3.10-slim
# Set the working directory in the container
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . /app
# Install FastAPI and Uvicorn
RUN pip install fastapi uvicorn
# Make port 8000 available to the world outside this container
EXPOSE 8000
# Run app.py when the container launches
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Running the Container¶
- Build the Docker image:
docker build -t fastapi-hello-world .
- Run the container:
docker run -d -p 8000:8000 fastapi-hello-world
Sending a POST Request¶
Now, send a POST request with a JSON body, query parameters, and headers:
curl -X POST "http://localhost:8000/hello?name=John&age=30" \
-H "Content-Type: application/json" \
-H "X-Custom-Header: custom_value" \
-d '{"greeting": "Hello", "language": "English"}'
Log Output¶
The log output will now include:
Received query parameters: name=John&age=30
Received headers: Headers({'host': 'localhost:8000', 'x-custom-header': 'custom_value', 'user-agent': 'curl/7.68.0', 'accept': '*/*', 'content-type': 'application/json'})
Received JSON body: {'greeting': 'Hello', 'language': 'English'}
Summary¶
With this update:
- Query parameters (
name=John&age=30) are printed. - Headers (e.g.,
X-Custom-Header: custom_value) are printed. - The JSON body (e.g.,
{"greeting": "Hello", "language": "English"}) is printed for POST, PUT, or PATCH requests.
*args and **kwargs are used in Python functions to allow a flexible number of arguments. Here’s what they mean:
*args¶
*argsallows you to pass a variable number of positional arguments to a function.- These arguments are stored in a tuple, and you can access them by iterating over them.
Example:¶
def my_function(*args):
for arg in args:
print(arg)
my_function(1, 2, 3) # Outputs: 1, 2, 3
In this example, *args captures all positional arguments passed to my_function into a tuple (1, 2, 3), and the function prints each element.
**kwargs¶
**kwargsallows you to pass a variable number of keyword arguments (arguments with keys) to a function.- These arguments are stored in a dictionary, and you can access them using keys.
Example:¶
def my_function(**kwargs):
for key, value in kwargs.items():
print(f"{key} = {value}")
my_function(name="John", age=30) # Outputs: name = John, age = 30
In this example, **kwargs captures all keyword arguments passed to my_function into a dictionary {'name': 'John', 'age': 30}, and the function prints each key-value pair.
Combining *args and **kwargs¶
You can use both *args and **kwargs together in the same function to accept both positional and keyword arguments.
Example:¶
def my_function(*args, **kwargs):
print("Positional arguments:", args)
print("Keyword arguments:", kwargs)
my_function(1, 2, name="John", age=30)
# Outputs:
# Positional arguments: (1, 2)
# Keyword arguments: {'name': 'John', 'age': 30}
In this example:
- *args captures the positional arguments (1, 2).
- **kwargs captures the keyword arguments {'name': 'John', 'age': 30}.
Key Takeaways:¶
*argsis used for passing a variable number of positional arguments.**kwargsis used for passing a variable number of keyword arguments.- They provide flexibility when writing functions that may need to handle various types and numbers of input.
Certainly! Here's an example where the FastAPI endpoint accepts two POST variables, name (a string) and age (an integer), as part of the request body in JSON format. We will also print these parameters using the decorator.
Updated app.py to Accept name and age as POST Variables¶
from fastapi import FastAPI, Request
from pydantic import BaseModel
from functools import wraps
app = FastAPI()
# Create a Pydantic model to define the expected structure of the request body
class UserInfo(BaseModel):
name: str
age: int
# Demo decorator to print request query parameters, headers, and JSON body
def print_request_params(func):
@wraps(func)
async def wrapper(*args, **kwargs):
request: Request = kwargs.get('request')
if request:
# Print query parameters
query_params = request.query_params
print(f"Received query parameters: {query_params}")
# Print headers
headers = request.headers
print(f"Received headers: {headers}")
# Print JSON body (if available)
if request.method in ["POST", "PUT", "PATCH"]:
body = await request.json() # Parse body as JSON
print(f"Received JSON body: {body}")
return await func(*args, **kwargs)
return wrapper
@app.post("/submit")
@print_request_params
async def submit_user_info(request: Request, user_info: UserInfo):
return {"message": f"Hello {user_info.name}, you are {user_info.age} years old!"}
Explanation:¶
- The
UserInfoclass is a Pydantic model that defines the expected structure of the POST request body with two fields:name(string) andage(integer). - The
submit_user_infoendpoint accepts aRequestobject and the parsedUserInfofrom the request body. - The
print_request_paramsdecorator prints the query parameters, headers, and the JSON body content. - The endpoint returns a message based on the
nameandageprovided.
Dockerfile (same as before)¶
# Use an official Python runtime as a parent image
FROM python:3.10-slim
# Set the working directory in the container
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . /app
# Install FastAPI and Uvicorn
RUN pip install fastapi uvicorn pydantic
# Make port 8000 available to the world outside this container
EXPOSE 8000
# Run app.py when the container launches
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Running the Container¶
- Build the Docker image:
docker build -t fastapi-user-info .
- Run the container:
docker run -d -p 8000:8000 fastapi-user-info
Sending a POST Request¶
Now, send a POST request with the name and age in the JSON body:
curl -X POST "http://localhost:8000/submit" \
-H "Content-Type: application/json" \
-d '{"name": "John", "age": 30}'
Expected Response¶
{
"message": "Hello John, you are 30 years old!"
}
Log Output¶
In the container logs, you will see:
Received query parameters:
Received headers: Headers({'host': 'localhost:8000', 'content-type': 'application/json', 'user-agent': 'curl/7.68.0', 'accept': '*/*'})
Received JSON body: {'name': 'John', 'age': 30}
Summary¶
In this example:
- The name and age are sent as part of the POST body in JSON format.
- The decorator captures and prints the query parameters (if any), headers, and the JSON body.
- The FastAPI endpoint parses the JSON body into the UserInfo model and returns a customized response.
Here’s an example where the FastAPI endpoint accepts name (a string) and age (an integer) as form data in a POST request. We will also print these parameters using the decorator, similar to the previous examples, but this time using form data instead of JSON.
Updated app.py to Accept name and age as Form Data¶
from fastapi import FastAPI, Request, Form
from functools import wraps
app = FastAPI()
# Demo decorator to print request query parameters, headers, and form data
def print_request_params(func):
@wraps(func)
async def wrapper(*args, **kwargs):
request: Request = kwargs.get('request')
if request:
# Print query parameters
query_params = request.query_params
print(f"Received query parameters: {query_params}")
# Print headers
headers = request.headers
print(f"Received headers: {headers}")
# Print form data (if available)
if request.method in ["POST", "PUT", "PATCH"]:
form_data = await request.form()
print(f"Received form data: {form_data}")
return await func(*args, **kwargs)
return wrapper
@app.post("/submit")
@print_request_params
async def submit_user_info(request: Request, name: str = Form(...), age: int = Form(...)):
return {"message": f"Hello {name}, you are {age} years old!"}
Explanation:¶
- The
nameandageparameters are accepted as form data usingForm(...), which tells FastAPI to expect form data in the request. - The
print_request_paramsdecorator prints the query parameters, headers, and form data. - The endpoint returns a message based on the
nameandageprovided in the form.
Dockerfile (same as before)¶
# Use an official Python runtime as a parent image
FROM python:3.10-slim
# Set the working directory in the container
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . /app
# Install FastAPI and Uvicorn
RUN pip install fastapi uvicorn
# Make port 8000 available to the world outside this container
EXPOSE 8000
# Run app.py when the container launches
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Running the Container¶
- Build the Docker image:
docker build -t fastapi-form-example .
- Run the container:
docker run -d -p 8000:8000 fastapi-form-example
Sending a POST Request with Form Data¶
You can send form data using curl like this:
curl -X POST "http://localhost:8000/submit" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=John&age=30"
Expected Response¶
{
"message": "Hello John, you are 30 years old!"
}
Log Output¶
In the container logs, you will see:
Received query parameters:
Received headers: Headers({'host': 'localhost:8000', 'content-type': 'application/x-www-form-urlencoded', 'user-agent': 'curl/7.68.0', 'accept': '*/*'})
Received form data: {'name': 'John', 'age': '30'}
Summary¶
In this example:
- The name and age are sent as form data using the Content-Type: application/x-www-form-urlencoded header.
- The decorator captures and prints query parameters (if any), headers, and form data.
- The FastAPI endpoint parses the form data into name and age parameters and returns a customized response.