Skip to main content

AWS Integrations for FastAPI exported OpenAPI specifications

Project description

FastAPI AWS ApiGateway Integration

This extension enables routes to be defined in FastAPI with extra decorator parameters for AWS integrations.

The exported private_openapi.json will then contain integrations for:

  • AWS Lambda function
  • AWS Step function
  • AWS S3 bucket

Secured via:

  • AWS Cognito User Pools
  • AWS Lambda Authorizer

This specification can be uploaded to AWS APIGateway as a definition for a REST API (see below for automatation with terraform). CORS definitions for routes are also auto-generated.

This allows your AWS APIGateway REST API to be defined in python, reference pydantic models, integrated into your application code, etc.

A public_openapi.json is also produced which can be presented to swagger and other tools for use in documentation.

Usage

Endpoints

Import the AWSAPIRouter which uses the default AWSAPIRoute to define routes. Simply use one of the following integration keywords to define the openapi_extra contents:

  • aws_lambda_uri trigger a lambda function
  • aws_sfn_sync_arn invoke a step-function synchronously
  • aws_s3_bucket s3 object access from apigw
  • mock_response fixed apigw responses (TBD)
  • aws_dynamodb_table_name dynamodb PutItem and GetItem via apigw endpoints
    • this integration creates a mapping_template in VTL to convert the POST body to a dynamodb Item.
    • custom fields can be set, and POST body parameters referenced with $body.<field_name>.
    • POST object is not validated against the pydantic model (TBD)
    • a value $expiration is set as $context.requestTimeEpoch) + 1 month for use as a ttl field.

The keyword-argument is used to define the format of the x-amazon-apigateway-integration added to the openapi_extra parameter.

NOTE: the default FastAPI.router should be replaced with a new AWSAPIRouter before adding any routes. If routes are added to the router, and the app.include_router(aws_router) is called, the app.include_router method will fail to pass the aws kwargs and cause the AWSAPIRoute to throw a ValueError. This is due to FastAPI sanitizing the kwargs to routes.

NOTE: if an AWS integration requires access to a HTTP header (origin, etc) you must set the header as a parameter of the handler function for it to be included in the openapi.json spec.

from fastapi import FastAPI, Depends
from pydantic import BaseModel
from fastapi.security import HTTPBearer

from fastapi_aws import AWSAPIRouter

# Instantiate the custom router
router = AWSAPIRouter()

# Create the FastAPI app and include the router
# NB: the router must be overridden **before** creating any routes
app = FastAPI()
app.router = router
# app.include_router(router) will cause the @router.get decorator to fail to add routes correctly

# Use the custom router to define routes so we can use the .get, .post, etc methods
@router.get(
    "/user/{name}",
    aws_lambda_uri="${user_function_arn}",
    description="Get a user profile information",
    summary="Username",
    tags=["user"],
)
async def get_user_profile_information(name: str):
    return {"status": "ok", "name": "hello, world"}

@router.post(
    "/user/{name}",
    aws_lambda_uri="${user_function_arn}",
    description="Set a user profile name",
    summary="Username",
    tags=["user"],
)
async def set_user_profile(name: str):
    return {"status": "ok"}

@router.post(
    "/user-step-function",
    aws_sfn_sync_arn="${step_function_arn}",
    description="trigger a sync step function from this endpoint",
    summary="operation",
    tags=["user", "operation"]
)
async def user_post_data(user_info: UserInfoModel):
    return "goodbye"

Authorization

fastapi_aws.authorizers exports a number of authorizers which can be added to endpoints for security.

These classes will also export the correct AWS integrations to build and associate authorizers with API Gateway endpoints.

  • CognitoAuthorizer enables AWS Cognito user pool authorization,
  • LambdaAuthorizer enables custom AWS Lambda function authorizers.

Both classes export the authorizers in the openapi spec, so an authorizer should NOT be provisioned in infrastructure.

To add custom header fields for the authorizers use the header_names parameter.

from fastapi import Security
from fastapi_aws import AWSAPIRouter, LambdaAuthorizer

bearer_auth = LambdaAuthorizer(
    authorizer_name="${bearer_authorizer_name}",
    aws_lambda_uri="${lambda_authorizer_uri}",
    aws_iam_role_arn="${lambda_authorizer_iam_role_arn}",
    header_names=["Authorization", "x-api-key"]
)

router = AWSAPIRouter()

@router.get(
    "/user/{name}",
    aws_lambda_uri="${user_function_arn}",
    description="Get a user profile information",
    summary="Username",
    tags=["user"],
)
async def get_user_profile_information(name: str, username=Security(bearer_auth)):
    return {"status": "ok", "name": "hello, world"}

In the above example, the /user/{name} endpoint will only succeed if the correct credentials are provided in either the Authorization or x-api-key header fields.

NB: the assignment user=Security... is required to ensure export to OpenAPI.

Export OpenAPI specification

The module will create an OpenAPI spec from a routes file, and output two specifications:

  1. A private OpenAPI spec with the (optional) CORS definitions and x-amazon-apigateway-integration definitions for consumption by AWS ApiGateway REST API, and
  2. A public OpenAPI spec which can be presented for public consumption via swagger documentation etc.

NB: the python code for these endpoints will not be executed in anyway, they are purely descriptive.

Given a my-routes.py:

from pydantic import BaseModel
from fastapi import Security

from fastapi_aws import AWSAPIRouter, LambdaAuthorizer


class UserRequest(BaseModel):
    username: str


class UserResponse(BaseModel):
    username: str


class UserPublicResponse(BaseModel):
    username: str


lambda_auth = LambdaAuthorizer(
    authorizer_name="${lambda_authorizer_name}",
    aws_lambda_uri="${lambda_authorizer_uri}",
    aws_iam_role_arn="${lambda_authorizer_iam_role_arn}",
)

router = AWSAPIRouter()

@router.post(
    "/user",
    description="post some user information",
    response_model=UserResponse,
    aws_lambda_uri="${polling_start_lambda_arn}",
    aws_iam_arn="${polling_start_lambda_iam_role_arn}",
    tags=["users"],
)
async def user(body: UserRequest, user=Security(lambda_auth)):
    return UserResponse()


@router.get(
    "/public/user",
    description="retrieve public user information",
    response_model=UserPublicResponse,
    aws_lambda_uri="${user_public_data_lambda_arn}",
    aws_iam_arn="${user_public_data_lambda_iam_role_arn}",
    tags=["users"],
)
async def fetch_user_info():
    return UserPublicResponse()

These specifications are created from the module via:

fastapi_aws \
  --title my-api \
  --router my-routes.py \
  --out-public ./api_public.json \
  --out-private ./api_private.json \
  --version 0.0.1

or via the docker image with:

docker run -it --rm \
  -v $(shell pwd)/my-api:/app/routes:ro \
  -v $(shell pwd)/terraform/rest-definition:/out \
  fastapi_aws \
    --title my-api \
    --router routes.routes:router \
    --out-public /out/api_public_definition.json \
    --out-private /out/api_private_definition.json \
    --version 0.0.1

Terraform integration

The openapi.json can be used to define a REST API in AWS ApiGateway.

By setting the aws_integration_uri to a placeholder string compatible with terraform templates, the exported openapi.json file can be loaded with templatefile. The template can then substitute the placeholder string with the actual lambda function arns from the terraform resources.

# Define a lambda function which we want to invoke from the API endpoint
resource "aws_lambda_function" "user_function" {
  # ... Lambda configuration ...
  function_name = "user-function"
  # Other configurations
}

# Create an IAM role for API Gateway to invoke Lambda functions
resource "aws_iam_role" "api_gateway_role" {
  name = "api-gateway-lambda-invoke-role"

  assume_role_policy = jsonencode({
    "Version": "2012-10-17",
    "Statement": [{
      "Action": "sts:AssumeRole",
      "Effect": "Allow",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      }
    }]
  })
}

# Attach policy to allow invocation of Lambda functions
resource "aws_iam_role_policy" "api_gateway_policy" {
  name = "api-gateway-lambda-invoke-policy"
  role = aws_iam_role.api_gateway_role.id

  policy = jsonencode({
    "Version": "2012-10-17",
    "Statement": [{
      "Action": "lambda:InvokeFunction",
      "Effect": "Allow",
      "Resource": aws_lambda_function.user_function.arn
    }]
  })
}

# Load the OpenAPI specification and replace placeholders
data "template_file" "openapi_spec" {
  template = file("${path.module}/openapi.json")

  vars = {
    user_function_arn           = aws_lambda_function.user_function.arn
    lambda_invoke_iam_role_arn  = aws_iam_role.api_gateway_role.arn
  }
}

# Create the API Gateway REST API using the OpenAPI specification
resource "aws_api_gateway_rest_api" "api" {
  name = "My API"

  body = data.template_file.openapi_spec.rendered
}

# Deploy the API
resource "aws_api_gateway_deployment" "api_deployment" {
  depends_on = [aws_api_gateway_rest_api.api]
  rest_api_id = aws_api_gateway_rest_api.api.id
  stage_name = "prod"
}

Example

The following Makefile produces the openapi specs during a CICD process with:

APP_NAME=Example-App
GIT_TAG=$(shell git describe --tags)

build/api-definition:
  docker run -it --rm \
    -v $(shell pwd)/package/src/api:/app/routes:ro \
    -v $(shell pwd)/terraform/rest:/out \
    fastapi_aws \
      --title $(APP_NAME) \
      --router routes.routes:router \
      --out-public /out/api_public_definition.json \
      --out-private /out/api_private_definition.json \
      --version $(GIT_TAG)

build/infrastructure:
  terraform init
  terraform plan -out new.plan
  terraform apply && rm new.plan

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_aws-0.0.11-py3-none-any.whl (27.2 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_aws-0.0.11-py3-none-any.whl.

File metadata

  • Download URL: fastapi_aws-0.0.11-py3-none-any.whl
  • Upload date:
  • Size: 27.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for fastapi_aws-0.0.11-py3-none-any.whl
Algorithm Hash digest
SHA256 ef97d2b5ad7b9d37b63cd1cceacd78fc276f0e21043a2d7481dbfeb794bb7e5f
MD5 c29a64bd3389968584d5333f39337c0c
BLAKE2b-256 1d3d0e3686052fc3a5d181ece3c81fa953d78b1e16db6b99985c7d614a368bfc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page