Skip to content

Instantly share code, notes, and snippets.

@m1yag1
Last active November 4, 2024 19:44
Show Gist options
  • Save m1yag1/d5eca28b680925d882e0da1a42849c27 to your computer and use it in GitHub Desktop.
Save m1yag1/d5eca28b680925d882e0da1a42849c27 to your computer and use it in GitHub Desktop.
Using Zappa to deploy a Flask application to AWS Lambda

Install zappa

pip install zappa

Copy the zappa_settings.json file below to project root

Update the values as needed.

Copy the requirements.txt file below

zappa is required as a dependency in the requirements.txt file

Copy the dynamodb.json file below

Update to match your schema

Create the directory for the app in project root and copy main.py

mkdir app

Set your AWS Creds, create the dynamodb table, and deploy zappa

  • Create DB

    aws dynamodb create-table --cli-input-json file://./dynamodb.json
    
  • Deploy flask app to AWS lambda

    zappa deploy dev
    
  • Wait for the API Gateway URL ⌛

If you need to make changes

  • Update your app by running:
    zappa update dev
    

View the logs with:

zappa tail dev

To the moon!

🚀 🌔

{
"TableName": "sc_36616_timeout_test",
"KeySchema": [
{
"AttributeName": "request_id",
"KeyType": "HASH"
}
],
"AttributeDefinitions": [
{
"AttributeName": "request_id",
"AttributeType": "S"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1
}
}
# Ensure the folder structure exists such as:
# ./app/main.py
import json
import time
import boto3
from flask import Flask, jsonify, request
USER_AGENT = "globus-sdk-py-3.41.0"
MAX_SLEEP = 90
dynamodb_table_name = "sc_36616_timeout_test"
dynamodb = boto3.client("dynamodb")
def get_flows_request_item(dynamodb_client, table_name, request_id):
flows_req = dynamodb_client.get_item(
TableName=table_name, Key={"request_id": {"S": str(request_id)}}
)
if "Item" in flows_req:
return flows_req["Item"]
else:
return None
def insert_flows_request_item(dynamodb_client, table_name, item):
resp = dynamodb_client.put_item(TableName=table_name, Item=item)
if resp["ResponseMetadata"]["HTTPStatusCode"] != 200:
raise Exception(f"Error inserting item into {table_name}: {resp}")
return resp
def update_flows_request_item(dynamodb_client, table_name, request_id, count):
dynamodb_client.update_item(
TableName=table_name,
Key={"request_id": {"S": request_id}},
UpdateExpression="set #count = :count",
ExpressionAttributeNames={"#count": "count"},
ExpressionAttributeValues={":count": {"N": str(count)}},
)
def create_app():
app = Flask(__name__)
@app.errorhandler(Exception)
def handle_exception(error):
response = {"error": str(error)}
print(str(error))
return jsonify(response), 500
@app.route("/", methods=["GET"])
def home():
return jsonify({"message": "ping"}), 200
@app.route("/run", methods=["GET", "POST"])
def run():
user_agent = request.headers.get("User-Agent")
# Only process request if it is from flows
if request.method == "POST" and user_agent == USER_AGENT:
print("Received POST request from flows!")
data = request.get_json()
request_id = data.get("request_id", None)
if request_id and request_id.startswith("flows"):
req_id = request_id.split("_")[-1]
print(json.dumps(data))
flows_req_item = get_flows_request_item(
dynamodb, dynamodb_table_name, req_id
)
if flows_req_item is None:
print(f"No item found for {req_id}. Inserting new item.")
insert_flows_request_item(
dynamodb,
dynamodb_table_name,
{"request_id": {"S": req_id}, "count": {"N": "1"}},
)
time.sleep(MAX_SLEEP)
else:
count = int(flows_req_item["count"]["N"]) + 1
print(f"Request id {req_id} found. Updating count to {count}")
update_flows_request_item(
dynamodb, dynamodb_table_name, req_id, count + 1
)
time.sleep(MAX_SLEEP)
return (
jsonify(
{
"message": f"slept for {MAX_SLEEP} sec for {req_id} with count {count}"
}
),
200,
)
else:
return jsonify({"message": data}), 200
else:
print("Received GET request!")
return jsonify({"message": "Hello!"}), 200
@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def catch_all(path):
print(
"Received an unregistered path for: "
f"{path} with method: {request.method}"
)
return jsonify({"message": "Unregistered path"}), 404
return app
fake_ap = create_app()
if __name__ == "__main__":
fake_ap = create_app()
fake_ap.run(debug=True, host="0.0.0.0", port=8080)
argcomplete==3.5.1
blinker==1.8.2
boto3==1.35.53
botocore==1.35.53
certifi==2024.8.30
cfn-flip==1.3.0
charset-normalizer==3.4.0
click==8.1.7
durationpy==0.9
Flask==3.0.3
hjson==3.1.0
idna==3.10
itsdangerous==2.2.0
Jinja2==3.1.4
jmespath==1.0.1
kappa==0.6.0
MarkupSafe==3.0.2
placebo==0.9.0
python-dateutil==2.9.0.post0
python-slugify==8.0.4
PyYAML==6.0.2
requests==2.32.3
s3transfer==0.10.3
six==1.16.0
text-unidecode==1.3
toml==0.10.2
tqdm==4.66.6
troposphere==4.8.3
urllib3==2.2.3
Werkzeug==3.1.1
wheel==0.44.0
zappa==0.59.0
{
"dev": {
"app_function": "app.main.fake_ap",
"exclude": [
"boto3",
"dateutil",
"botocore",
"s3transfer",
"concurrent"
],
"project_name": "sc-36116-test-timeout",
"runtime": "python3.12",
"s3_bucket": "zappa-uf8cgequs",
"timeout_seconds": 300,
"log_level": "DEBUG"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment