Skip to content

Commit 5122b79

Browse files
Jade WibbelsJade Wibbels
authored andcommitted
API with dynamodb back end
1 parent 6858e0d commit 5122b79

18 files changed

+663
-39
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ share/python-wheels/
2525
.installed.cfg
2626
*.egg
2727
MANIFEST
28+
package/
2829

2930
# PyInstaller
3031
# Usually these files are written by a python script from a template

app/__init__.py

Whitespace-only changes.

app/awscli.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Description: This script is used to create the tables in the dynamodb
2+
aws dynamodb create-table \
3+
--table-name users \
4+
--attribute-definitions AttributeName=Id,AttributeType=S \
5+
--key-schema AttributeName=Id,KeyType=HASH \
6+
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5
7+
8+
aws dynamodb create-table \
9+
--table-name posts \
10+
--attribute-definitions AttributeName=Id,AttributeType=S \
11+
--key-schema AttributeName=Id,KeyType=HASH \
12+
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5
13+
14+
# {
15+
# "ApiEndpoint": "https://x8ttp4bxtb.execute-api.us-east-1.amazonaws.com",
16+
# "ApiId": "x8ttp4bxtb",
17+
# "ApiKeySelectionExpression": "$request.header.x-api-key",
18+
# "CreatedDate": "2024-12-04T01:24:13+00:00",
19+
# "DisableExecuteApiEndpoint": false,
20+
# "Name": "posts-api",
21+
# "ProtocolType": "HTTP",
22+
# "RouteSelectionExpression": "$request.method $request.path"
23+
# }
24+
aws apigatewayv2 create-api \
25+
--name posts-api \
26+
--protocol-type HTTP \
27+
--target arn:aws:lambda:us-east-1:123456789012:function:posts-api

app/main.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from fastapi import FastAPI
2+
from mangum import Mangum
3+
from app.routes.users import router as users_router
4+
from app.routes.posts import router as posts_router
5+
6+
app = FastAPI()
7+
8+
app.include_router(users_router, prefix="/users", tags=["users"])
9+
app.include_router(posts_router, prefix="/posts", tags=["posts"])
10+
11+
12+
@app.get("/")
13+
def read_root():
14+
"""
15+
Root endpoint
16+
"""
17+
return {"message": "Welcome to the API"}
18+
19+
20+
@app.get("/health")
21+
def read_health():
22+
"""
23+
Health check endpoint
24+
"""
25+
return {"message": "API is healthy"}
26+
27+
28+
handler = Mangum(app, lifespan="off", api_gateway_base_path="/api/v1")

app/routes/__init__.py

Whitespace-only changes.

app/routes/posts.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
from fastapi import APIRouter, HTTPException
2+
from pydantic import BaseModel
3+
import boto3
4+
from botocore.exceptions import ClientError
5+
6+
router = APIRouter()
7+
8+
# Initialize the DynamoDB client
9+
dynamodb = boto3.resource("dynamodb")
10+
table = dynamodb.Table("posts")
11+
12+
13+
class Post(BaseModel):
14+
Id: str
15+
title: str
16+
content: str
17+
author_id: str
18+
19+
20+
@router.get("/")
21+
def read_posts():
22+
"""
23+
Gets all posts from the DynamoDB table "posts"
24+
25+
Example call:
26+
``curl -X GET "http://127.0.0.1:8000/posts/" -H "accept: application/json"``
27+
28+
Response:
29+
``[{"Id": "1", "title": "First Post", ... "author_id": "1"}]``
30+
"""
31+
try:
32+
response = table.scan()
33+
return response["Items"]
34+
except ClientError as e:
35+
raise HTTPException(status_code=500, detail=e.response["Error"]["Message"])
36+
37+
38+
@router.get("/{Id}")
39+
def read_post(Id: str):
40+
"""
41+
Gets a post from the DynamoDB table "posts" by Id
42+
43+
Example call:
44+
``curl -X GET "http://127.0.0.1:8000/posts/1" -H "accept: application/json"``
45+
46+
Response:
47+
``{"content":"This is the first post","author_id":"1","Id":"1","title":"First Post"}``
48+
"""
49+
try:
50+
response = table.get_item(Key={"Id": Id})
51+
if "Item" not in response:
52+
raise HTTPException(status_code=404, detail="Post not found")
53+
return response["Item"]
54+
except ClientError as e:
55+
raise HTTPException(status_code=500, detail=e.response["Error"]["Message"])
56+
57+
58+
@router.post("/")
59+
def create_post(post: Post):
60+
"""
61+
Creates a post in the DynamoDB table "posts"
62+
63+
Example call:
64+
``curl -X POST "http://127.0.0.1:8000/posts/" -H "accept: application/json" -H "Content-Type: application/json" -d '{ # noqa E501
65+
"Id": "1",
66+
"title": "First Post",
67+
"content": "This is the first post",
68+
"author_id": "1"
69+
}'``
70+
71+
Response:
72+
``{"message": "Post created"}``
73+
"""
74+
try:
75+
table.put_item(Item={k: v for k, v in post.model_dump().items()})
76+
77+
return {"message": "Post created"}
78+
except ClientError as e:
79+
raise HTTPException(status_code=500, detail=e.response["Error"]["Message"])
80+
81+
82+
@router.put("/{Id}")
83+
def update_post(Id: str, post: Post):
84+
"""
85+
Updates a post in the DynamoDB table "posts" by Id
86+
87+
Example call:
88+
``curl -X PUT "http://127.0.0.1:8000/posts/1" -H "accept: application/json" -H "Content-Type: application/json" -d # noqa E501
89+
'{
90+
"Id": "1",
91+
"title": "Updated Post",
92+
"content": "This is the updated post",
93+
"author_id": "1"
94+
}'
95+
96+
Response:
97+
``{"message": "Post with id {Id} updated", "updated_attributes": {updated_attributes}}``
98+
"""
99+
try:
100+
response = table.update_item(
101+
Key={"Id": Id},
102+
UpdateExpression="set title=:t, content=:c, author_id=:a",
103+
ExpressionAttributeValues={":t": post.title, ":c": post.content, ":a": post.author_id},
104+
ReturnValues="UPDATED_NEW",
105+
)
106+
return {
107+
"message": f"Post with id {Id} updated",
108+
"updated_attributes": response["Attributes"],
109+
}
110+
except ClientError as e:
111+
raise HTTPException(status_code=500, detail=e.response["Error"]["Message"])
112+
113+
114+
@router.delete("/{Id}")
115+
def delete_post(Id: str):
116+
"""
117+
Deletes a post from the DynamoDB table "posts" by Id
118+
119+
Example call:
120+
``curl -X DELETE "http://127.0.0.1:8000/posts/1" -H "accept: application/json"``
121+
122+
Response:
123+
``{"message":"Post with id 1 deleted"}
124+
"""
125+
try:
126+
table.delete_item(Key={"Id": Id})
127+
return {"message": f"Post with id {Id} deleted"}
128+
except ClientError as e:
129+
raise HTTPException(status_code=500, detail=e.response["Error"]["Message"])

app/routes/users.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
from fastapi import APIRouter, HTTPException
2+
from pydantic import BaseModel
3+
import boto3
4+
from botocore.exceptions import ClientError
5+
6+
router = APIRouter()
7+
8+
# Initialize the DynamoDB client to connect to AWS DynamoDB
9+
dynamodb = boto3.resource("dynamodb")
10+
table = dynamodb.Table("users")
11+
12+
13+
class User(BaseModel):
14+
Id: str # Change user_id to Id to match the DynamoDB table schema
15+
user_name: str
16+
password: str
17+
email_address: str
18+
19+
20+
@router.post("/")
21+
def create_user(user: User):
22+
"""
23+
Creates a new user in the DynamoDB table "users"
24+
25+
Example call:
26+
``curl -X POST "http://127.0.0.1:8000/users/" -H "accept: application/json" -H "Content-Type: application/json" -d '{ # noqa E501
27+
"Id": "1",
28+
"user_name": "johndoe",
29+
"password": "password",
30+
"email_address": "[email protected]"
31+
}'``
32+
33+
Response: ``{"message":"User created"}``
34+
"""
35+
try:
36+
table.put_item(
37+
Item={
38+
"Id": user.Id,
39+
"user_name": user.user_name,
40+
"password": user.password,
41+
"email_address": user.email_address,
42+
}
43+
)
44+
return {"message": "User created"}
45+
except ClientError as e:
46+
raise HTTPException(status_code=500, detail=e.response["Error"]["Message"])
47+
48+
49+
@router.get("/{Id}")
50+
def read_user(Id: str):
51+
"""
52+
Reads a user from the DynamoDB table "users" by Id
53+
54+
Example call:
55+
``curl -X GET "http://127.0.0.1:8000/users/1" -H "accept: application/json"``
56+
57+
Response: ``{"Id": "1", "user_name": "johndoe", "password": ...``
58+
"""
59+
try:
60+
response = table.get_item(Key={"Id": Id})
61+
if "Item" not in response:
62+
raise HTTPException(status_code=404, detail="User not found")
63+
return response["Item"]
64+
except ClientError as e:
65+
raise HTTPException(status_code=500, detail=e.response["Error"]["Message"])
66+
67+
68+
@router.get("/")
69+
def read_users():
70+
"""
71+
Reads all users from the DynamoDB table "users"
72+
73+
Example call:
74+
``curl -X GET "http://127.0.0.1:8000/users/" -H "accept: application/json"``
75+
76+
Response: ``[{"Id": "1", "user_name": "johndoe", "password": "password", "email_address": ...``
77+
"""
78+
try:
79+
response = table.scan()
80+
return response["Items"]
81+
except ClientError as e:
82+
raise HTTPException(status_code=500, detail=e.response["Error"]["Message"])
83+
84+
85+
@router.put("/{Id}")
86+
def update_user(Id: str, user: User):
87+
"""
88+
Updates a user in the DynamoDB table "users" by Id
89+
90+
Example call:
91+
``curl -X PUT "http://127.0.0.1:8000/users/{Id}" -H "accept: application/json" -H "Content-Type: application/json" -d '{ # noqa E501
92+
"Id": "1",
93+
"user_name": "johndoeupdated",
94+
"password": "newpassword",
95+
"email_address": "[email protected]"
96+
}'
97+
98+
Response: ``{"message":"User with id 1 updated","updated_attributes": ...}``
99+
"""
100+
try:
101+
response = table.update_item(
102+
Key={"Id": Id},
103+
UpdateExpression="set user_name=:u, password=:p, email_address=:e",
104+
ExpressionAttributeValues={
105+
":u": user.user_name,
106+
":p": user.password,
107+
":e": user.email_address,
108+
},
109+
ReturnValues="UPDATED_NEW",
110+
)
111+
return {
112+
"message": f"User with id {Id} updated",
113+
"updated_attributes": response["Attributes"],
114+
}
115+
except ClientError as e:
116+
raise HTTPException(status_code=500, detail=e.response["Error"]["Message"])
117+
118+
119+
@router.delete("/{Id}")
120+
def delete_user(Id: str):
121+
"""
122+
Deletes a user from the DynamoDB table "users" by Id
123+
124+
Example call: ``curl -X DELETE "http://127.0.0.1:8000/users/1" -H "accept: application/json"``
125+
126+
Response: ``{"message":"User with id 1 deleted"}``
127+
"""
128+
try:
129+
table.delete_item(Key={"Id": Id})
130+
return {"message": f"User with id {Id} deleted"}
131+
except ClientError as e:
132+
raise HTTPException(status_code=500, detail=e.response["Error"]["Message"])

lambda_function.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import json
2+
import logging
3+
from app.main import handler
4+
5+
logging.basicConfig(level=logging.INFO)
6+
7+
8+
def lambda_handler(event, context):
9+
"""
10+
Lambda handler function
11+
12+
Args:
13+
event (dict): Event data
14+
context (object): Runtime information
15+
"""
16+
logging.info(f"Received event: {json.dumps(event, indent=2)}")
17+
18+
try:
19+
response = handler(event, context)
20+
except Exception as e:
21+
logging.error(f"Error: {e}")
22+
response = {
23+
"statusCode": 500,
24+
"headers": {"Content-Type": "application/json"},
25+
"body": json.dumps({"error": str(e)}),
26+
}
27+
28+
return response

mydeployment.zip

24 MB
Binary file not shown.

requirements.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ pytest-asyncio
33
pytest-cov
44

55
boto3 == 1.35.72
6+
fastapi == 0.115.*
7+
httpx == 0.28.*
68
pydantic == 2.9.*
7-
datamodel-code-generator == 0.26.*
9+
mangum == 0.19.*
10+
uvicorn == 0.32.*
811

912

1013

0 commit comments

Comments
 (0)