import json
import logging
from typing import Optional # Python 3.7–3.9 compatibility
import boto3
from botocore.exceptions import ClientError
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
class DynamoDBDemo:
def __init__(self, region: str):
self.region = region
self.dynamodb_resource = boto3.resource("dynamodb", region_name=self.region)
self.client = self.dynamodb_resource.meta.client # low-level client for waiters
def wait_for_table_exists(self, table_name: str, delay: int = 5, max_attempts: int = 20) -> bool:
try:
waiter = self.client.get_waiter("table_exists")
waiter.wait(TableName=table_name, WaiterConfig={"Delay": delay, "MaxAttempts": max_attempts})
return True
except ClientError as e:
logging.error("Waiter table_exists failed: %s", e)
return False # [web:1]
def create_table(self, table_name, key_schema, attribute_definitions, provisioned_throughput):
try:
print(f"\nCreating table {table_name} ...")
self.dynamodb_resource.create_table(
TableName=table_name,
KeySchema=key_schema,
AttributeDefinitions=attribute_definitions,
ProvisionedThroughput=provisioned_throughput,
)
if self.wait_for_table_exists(table_name):
print(f"Table {table_name} is ACTIVE.")
return True
return False
except ClientError as e:
logging.error(e)
return False # [web:3]
def store_an_item(self, table_name, item):
try:
print(f"\nStoring item in table {table_name} ...")
table = self.dynamodb_resource.Table(table_name)
table.put_item(Item=item)
print("Item stored.")
return True
except ClientError as e:
logging.error(e)
return False # [web:59]
def get_an_item(self, table_name, key):
try:
print(f"\nRetrieving item with key from table {table_name} ...")
table = self.dynamodb_resource.Table(table_name)
response = table.get_item(Key=key)
item = response.get("Item")
if item:
print("Item found:")
print(json.dumps(item, indent=2, default=str))
return True
else:
print("Item not found.")
return False
except ClientError as e:
logging.error(e)
return False # [web:3]
def describe_table(self, table_name: str):
try:
resp = self.client.describe_table(TableName=table_name)
print(json.dumps(resp["Table"], indent=2, default=str))
return True
except ClientError as e:
logging.error(e)
return False # [web:3]
def list_tables(self):
try:
tables = []
paginator = self.client.get_paginator("list_tables")
for page in paginator.paginate():
tables.extend(page.get("TableNames", []))
if tables:
print("Tables:")
for t in tables:
print(f"- {t}")
else:
print("No tables found.")
return True
except ClientError as e:
logging.error(e)
return False # [web:3]
def update_item(
self,
table_name: str,
key: dict,
update_expression: str,
expr_attr_values: Optional[dict] = None,
expr_attr_names: Optional[dict] = None,
condition_expression: Optional[str] = None,
return_values: str = "UPDATED_NEW",
):
"""
Update an item using DynamoDB UpdateExpression with optional conditions.
ReturnValues: NONE | ALL_OLD | UPDATED_OLD | ALL_NEW | UPDATED_NEW.
"""
try:
print(f"\nUpdating item in table {table_name} ...")
table = self.dynamodb_resource.Table(table_name)
params = {
"Key": key,
"UpdateExpression": update_expression,
"ReturnValues": return_values,
}
if expr_attr_values:
params["ExpressionAttributeValues"] = expr_attr_values
if expr_attr_names:
params["ExpressionAttributeNames"] = expr_attr_names
if condition_expression:
params["ConditionExpression"] = condition_expression
resp = table.update_item(**params)
attrs = resp.get("Attributes")
if attrs:
print("Returned attributes:")
print(json.dumps(attrs, indent=2, default=str))
else:
print("Update succeeded. No attributes returned.")
return True
except ClientError as e:
logging.error(e)
return False # [web:39][web:48]
def delete_item(
self,
table_name: str,
key: dict,
condition_expression: Optional[str] = None,
expr_attr_names: Optional[dict] = None,
expr_attr_values: Optional[dict] = None,
return_values: str = "ALL_OLD",
):
"""
Delete a single item by key with optional ConditionExpression.
ReturnValues: NONE | ALL_OLD.
"""
try:
print(f"\nDeleting item from table {table_name} ...")
table = self.dynamodb_resource.Table(table_name)
params = {"Key": key, "ReturnValues": return_values}
if condition_expression:
params["ConditionExpression"] = condition_expression
if expr_attr_names:
params["ExpressionAttributeNames"] = expr_attr_names
if expr_attr_values:
params["ExpressionAttributeValues"] = expr_attr_values
resp = table.delete_item(**params)
old = resp.get("Attributes")
if old:
print("Deleted item (old attributes):")
print(json.dumps(old, indent=2, default=str))
else:
print("Delete succeeded.")
return True
except ClientError as e:
logging.error(e)
return False # [web:60][web:61]
def delete_table(self, table_name: str):
try:
print(f"\nDeleting table {table_name} ...")
self.client.delete_table(TableName=table_name)
print("Delete requested. Table will transition to DELETING and then be removed.")
return True
except ClientError as e:
logging.error(e)
return False # [web:70]
def prompt_json(prompt_text: str):
print(prompt_text)
print("Enter JSON (single line) and press Enter, or leave blank to cancel:")
line = input("> ").strip()
if not line:
return None
try:
return json.loads(line)
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}")
return None # general JSON parsing guidance
def default_music_schema():
key_schema = [
{"AttributeName": "artist", "KeyType": "HASH"},
{"AttributeName": "song", "KeyType": "RANGE"},
]
attribute_definitions = [
{"AttributeName": "artist", "AttributeType": "S"},
{"AttributeName": "song", "AttributeType": "S"},
]
provisioned_throughput = {"ReadCapacityUnits": 1, "WriteCapacityUnits": 1}
return key_schema, attribute_definitions, provisioned_throughput # [web:3]
def print_menu():
print("\nDynamoDB Menu")
print("1) Create table")
print("2) Put item")
print("3) Get item")
print("4) Describe table")
print("5) List tables")
print("6) Update item")
print("7) Delete item")
print("8) Delete table")
print("9) Exit") # [web:62]
def main():
print("DynamoDB Demo (menu-driven)")
region = input("Enter AWS region (default us-east-1): ").strip() or "us-east-1"
app = DynamoDBDemo(region)
default_table = "music"
while True:
print_menu()
choice = input("Select an option: ").strip()
if choice == "1":
table_name = input(f"Table name (default {default_table}): ").strip() or default_table
use_default = (input("Use default music schema? [Y/n]: ").strip().lower() or "y") == "y"
if use_default:
key_schema, attr_defs, thrpt = default_music_schema()
else:
key_schema = prompt_json('Enter KeySchema JSON (e.g., [{"AttributeName":"artist","KeyType":"HASH"},{"AttributeName":"song","KeyType":"RANGE"}]): ')
if key_schema is None:
continue
attr_defs = prompt_json('Enter AttributeDefinitions JSON (e.g., [{"AttributeName":"artist","AttributeType":"S"},{"AttributeName":"song","AttributeType":"S"}]): ')
if attr_defs is None:
continue
thrpt = prompt_json('Enter ProvisionedThroughput JSON (e.g., {"ReadCapacityUnits":1,"WriteCapacityUnits":1}): ')
if thrpt is None:
continue
app.create_table(table_name, key_schema, attr_defs, thrpt)
elif choice == "2":
table_name = input(f"Table name (default {default_table}): ").strip() or default_table
print("Enter item as JSON (must include the table's key attributes):")
item = prompt_json('Example: {"artist":"Michael Jackson","song":"Billie Jean","album":"Thriller","length_seconds":294}')
if item is None:
continue
app.store_an_item(table_name, item)
elif choice == "3":
table_name = input(f"Table name (default {default_table}): ").strip() or default_table
print("Enter key as JSON:")
key = prompt_json('Example: {"artist":"Michael Jackson","song":"Billie Jean"}')
if key is None:
continue
app.get_an_item(table_name, key)
elif choice == "4":
table_name = input(f"Table name (default {default_table}): ").strip() or default_table
app.describe_table(table_name)
elif choice == "5":
app.list_tables()
elif choice == "6":
table_name = input(f"Table name (default {default_table}): ").strip() or default_table
print("Enter key JSON for the item to update:")
key = prompt_json('Example: {"artist":"Michael Jackson","song":"Billie Jean"}')
if key is None:
continue
simple = (input("Use simple SET update? [Y/n]: ").strip().lower() or "y") == "y"
if simple:
attr = input("Attribute to set (e.g., album): ").strip()
val_json = prompt_json('Enter value as JSON (e.g., "Thriller" or 294 or ["a","b"]): ')
if val_json is None:
continue
update_expression = "SET #a = :v"
expr_attr_names = {"#a": attr}
expr_attr_values = {":v": val_json}
return_values = input("Return values [UPDATED_NEW|ALL_NEW|NONE] (default UPDATED_NEW): ").strip() or "UPDATED_NEW"
app.update_item(
table_name,
key,
update_expression,
expr_attr_values=expr_attr_values,
expr_attr_names=expr_attr_names,
return_values=return_values,
)
else:
print("Enter full UpdateExpression (e.g., 'SET #a=:v1, play_count = if_not_exists(play_count, :z) + :inc REMOVE old_attr'):")
update_expression = input("> ").strip()
expr_attr_names = prompt_json('ExpressionAttributeNames JSON or blank (e.g., {"#a":"album"}): ') or None
expr_attr_values = prompt_json('ExpressionAttributeValues JSON or blank (e.g., {":v1":"Thriller",":z":0,":inc":1}): ') or None
condition_expression = input("ConditionExpression or blank (e.g., attribute_exists(artist) AND attribute_exists(song)): ").strip() or None
return_values = input("Return values [NONE|ALL_OLD|UPDATED_OLD|ALL_NEW|UPDATED_NEW] (default UPDATED_NEW): ").strip() or "UPDATED_NEW"
app.update_item(
table_name,
key,
update_expression,
expr_attr_values=expr_attr_values,
expr_attr_names=expr_attr_names,
condition_expression=condition_expression,
return_values=return_values,
)
elif choice == "7":
table_name = input(f"Table name (default {default_table}): ").strip() or default_table
print("Enter key JSON for the item to delete:")
key = prompt_json('Example: {"artist":"Michael Jackson","song":"Billie Jean"}')
if key is None:
continue
guarded = (input("Use a ConditionExpression to guard delete? [y/N]: ").strip().lower() or "n") == "y"
condition_expression = None
expr_attr_names = None
expr_attr_values = None
if guarded:
condition_expression = input("ConditionExpression (e.g., attribute_exists(artist) AND attribute_exists(song)): ").strip() or None
expr_attr_names = prompt_json('ExpressionAttributeNames JSON or blank (e.g., {"#a":"album"}): ') or None
expr_attr_values = prompt_json('ExpressionAttributeValues JSON or blank (e.g., {":val": "Thriller"}): ') or None
return_values = input("Return values [NONE|ALL_OLD] (default ALL_OLD): ").strip() or "ALL_OLD"
app.delete_item(
table_name,
key,
condition_expression=condition_expression,
expr_attr_names=expr_attr_names,
expr_attr_values=expr_attr_values,
return_values=return_values,
)
elif choice == "8":
table_name = input(f"Table name to delete (default {default_table}): ").strip() or default_table
confirm = input(f"Type DELETE to confirm deletion of table {table_name}: ").strip()
if confirm == "DELETE":
app.delete_table(table_name)
else:
print("Delete cancelled.")
elif choice == "9":
print("Exiting.")
break
else:
print("Invalid choice. Please select a valid option.")
if __name__ == "__main__":
main()