Writing Lambda Function

In this step, we will update the code for the book_create function created in the previous workshop:

  1. Open AWS Lambda console, click on book_create function LambdaConsole

  2. Copy the following code into lambda_function.py, then click Deploy

The cgi module was completely removed in Python 3.13+. To use the code below, make sure your Lambda function is using Python 3.12 runtime or earlier. You can change the runtime under Configuration tab > Runtime settings > Edit.

import boto3
import json
import base64
import io
import cgi
import os
from decimal import Decimal

# AWS clients
s3 = boto3.client("s3")
dynamodb = boto3.resource("dynamodb")

# ENV variables
UPLOAD_BUCKET = os.environ.get("UPLOAD_BUCKET", "book-image-stores-tranvix")
RESIZE_BUCKET = os.environ.get("RESIZE_BUCKET", "book-image-resize-stores-tranvix")
AWS_REGION = os.environ["AWS_REGION"]

TABLE_NAME = "Books"


def parse_multipart_form(content_type, body):
    """
    Parse multipart/form-data from API Gateway (using cgi)
    """
    fp = io.BytesIO(base64.b64decode(body))

    environ = {
        "REQUEST_METHOD": "POST",
        "CONTENT_TYPE": content_type,
        "CONTENT_LENGTH": str(len(body)),
    }

    fs = cgi.FieldStorage(
        fp=fp,
        environ=environ,
        keep_blank_values=True
    )
    return fs


def lambda_handler(event, context):
    # ---- CORS headers ----
    cors_headers = {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type,Authorization"
    }
    
    if event.get("httpMethod") == "OPTIONS":
        return {
            "statusCode": 200,
            "headers": cors_headers,
            "body": ""
        }

    try:
        headers = event.get("headers", {}) or {}
        content_type = headers.get("content-type") or headers.get("Content-Type", "")

        table = dynamodb.Table(TABLE_NAME)

        body = event.get("body")

        if body is None and isinstance(event, dict) and "id" in event:
            book_item = event

        elif content_type.startswith("application/json"):
            book_item = json.loads(body)
            # Convert price to string if it's a number (DynamoDB will handle it)
            if "price" in book_item and isinstance(book_item["price"], (int, float)):
                book_item["price"] = str(book_item["price"])

        elif content_type.startswith("multipart/form-data"):
            form = parse_multipart_form(content_type, body)

            image_file = form["image"]
            file_name = image_file.filename
            file_bytes = image_file.file.read()
            content_type_img = image_file.type or "application/octet-stream"

            s3.put_object(
                Bucket=UPLOAD_BUCKET,
                Key=file_name,
                Body=file_bytes,
                ContentType=content_type_img
            )

            image_url = f"https://{RESIZE_BUCKET}.s3.{AWS_REGION}.amazonaws.com/{file_name}"

            book_item = {
                "id": form["id"].value,
                "rv_id": 0,
                "name": form["name"].value,
                "author": form["author"].value,
                "category": form["category"].value,
                "price": form["price"].value,
                "description": form["description"].value,
                "image": image_url
            }
        else:
            return {
                "statusCode": 400,
                "body": json.dumps({"error": "Unsupported request format"}),
                "headers": {**cors_headers, "Content-Type": "application/json"}
            }

        # -------- Save to DynamoDB --------
        table.put_item(Item=book_item)

        return {
            "statusCode": 200,
            "body": json.dumps({
                "message": "Book created successfully",
                "item": book_item
            }),
            "headers": {
                **cors_headers,
                "Content-Type": "application/json"
            }
        }
    except Exception as e:
        # Always return CORS headers even on error
        return {
            "statusCode": 500,
            "body": json.dumps({"error": str(e)}),
            "headers": {**cors_headers, "Content-Type": "application/json"}
        }

This code handles image uploads from users and saves them to S3 bucket

LambdaConsole

  1. After deploying the code, switch to the Configuration tab to change the runtime

    • Click the Configuration tab in the navigation bar below LambdaConsole
  2. Scroll down to Runtime settings, confirm the runtime is Python 3.13 or higher

    • Click Edit to change it LambdaConsole
  3. On the Edit runtime settings page:

    • Change Runtime to Python 3.12 (or lower)
    • Click Save LambdaConsole
  4. Confirm the runtime has been updated successfully LambdaConsole

  5. Configure environment variables for Lambda function

    • Click Configuration tab
    • Select Environment variables from the left menu
    • Click Edit LambdaConsole
  6. Add the following environment variables:

    • UPLOAD_BUCKET: bucket name for original images (e.g., book-image-stores-tranvix)
    • RESIZE_BUCKET: bucket name for resized images (e.g., book-image-resize-stores-tranvix)
    • Click Save LambdaConsole
  7. Confirm environment variables are updated successfully LambdaConsole

  8. Grant Lambda function permission to write files to S3 bucket and DynamoDB

    • Select Permissions from the left menu
    • Click on the role that the function is using LambdaConsole
  9. On the IAM Role page, click Add permissions > Create inline policy LambdaConsole

  10. Select JSON tab and add the following policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::book-image-stores-tranvix/*"
        },
        {
            "Effect": "Allow",
            "Action": "dynamodb:PutItem",
            "Resource": "arn:aws:dynamodb:*:*:table/Books"
        }
    ]
}

LambdaConsole

  1. Click Next LambdaConsole

  2. Enter policy name, e.g., LambdaBookCreatePolicy

    • Review the configured permissions
    • Click Create policy LambdaConsole
  3. Policy created successfully and attached to role LambdaConsole