Fastapi App Endpoint With A Decorator

TechieTalkAI Blogs by Schogini Systems

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

  1. Build the Docker image:
docker build -t fastapi-hello-world .
  1. 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

  1. Build the Docker image:
docker build -t fastapi-hello-world .
  1. 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:

*args and **kwargs are used in Python functions to allow a flexible number of arguments. Here’s what they mean:

*args

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

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:

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:

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

  1. Build the Docker image:
docker build -t fastapi-user-info .
  1. 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:

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

  1. Build the Docker image:
docker build -t fastapi-form-example .
  1. 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.