Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions db/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# DynamoDB Database Setup and Usage

## Setting Up the Database

The `setup.py` script creates the DynamoDB table used to store meeting data. This should be run once before using the application for the first time.

### Prerequisites

1. AWS credentials configured in your environment:
- Set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` in your environment or in a `.env` file at the project root
- Ensure the AWS account has permissions to create and manage DynamoDB tables

2. Required Python packages:
```bash
poetry add boto3 python-dotenv
```

### Running the Setup Script

Basic usage with default settings:
```bash
poetry run python db/setup.py
```

Custom table name:
```bash
poetry run python db/setup.py --table-name MyCustomTable
```

Custom schema path:
```bash
poetry run python db/setup.py --schema-path /path/to/schema.json
```

The script is idempotent - you can run it multiple times without creating duplicate tables.

## Table Structure

The DynamoDB table uses the following structure:

- **Primary Key:**
- Partition Key: `name` (String) - The name of the meeting
- Sort Key: `date` (String) - The date and time of the meeting

- **Secondary Indexes:**
- `DateIndex` - Allows querying meetings by date
- `ClipIdIndex` - Allows querying meetings by clip ID

- **Main Attributes:**
- `meeting` - Name of the meeting (String)
- `date` - Date and time of the meeting (String)
- `clip_id` - Granicus clip ID (String, optional)
- `value` - Map containing index values and all other meeting attributes

## Data Storage Pattern

Meeting data follows this pattern:
- Core identification fields (`name`, `date`, `clip_id`) are stored as top-level attributes to allow for efficient querying
- All other meeting details (duration, agenda URL, video URL, etc.) are stored in a single `value` map attribute

## Limitations
- You cannot directly query or filter based on attributes inside the `value` map
179 changes: 179 additions & 0 deletions db/dynamo_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#!/usr/bin/env python3
"""Example script demonstrating the use of DynamoDB service with Meeting objects."""

import asyncio
import os
from datetime import datetime
from typing import List

from dotenv import load_dotenv
from pydantic import HttpUrl

import sys
from pathlib import Path

# Add the parent directory to the Python path to allow importing from src
sys.path.append(str(Path(__file__).parent.parent))

from db.meeting_db import DynamoDBService
from src.models.meeting import Meeting


async def main():
"""Demonstrate DynamoDB operations with Meeting objects."""
# Load environment variables from .env file
load_dotenv()

# Initialize DynamoDB service
dynamo_service = DynamoDBService(table_name="ExampleMeetings")

# Example meeting data
meetings = [
Meeting(
name="City Council",
date="2023-05-15T18:00:00",
duration="2h 15m",
agenda=HttpUrl("https://example.com/agenda1"),
video=HttpUrl("https://example.com/video1"),
clip_id="12345",
),
Meeting(
name="Planning Commission",
date="2023-05-16T10:00:00",
duration="1h 30m",
agenda=HttpUrl("https://example.com/agenda2"),
video=HttpUrl("https://example.com/video2"),
clip_id="67890",
),
Meeting(
name="City Council",
date="2023-05-22T18:00:00",
duration="2h 45m",
agenda=HttpUrl("https://example.com/agenda3"),
video=HttpUrl("https://example.com/video3"),
clip_id="54321",
),
]

# Insert meetings
print("\n=== Inserting meetings ===")
for meeting in meetings:
success = await dynamo_service.save(meeting)
if success:
print(f"Successfully inserted: {meeting}")
else:
print(f"Failed to insert: {meeting}")

# Query meetings by name
print("\n=== Querying meetings by name ===")
city_council_meetings = await dynamo_service.query_meetings_by_name("City Council")
print(f"Found {len(city_council_meetings)} City Council meetings:")
for meeting in city_council_meetings:
print(f" - {meeting}")

# Query meetings by date
print("\n=== Querying meetings by date ===")
may16_meetings = await dynamo_service.query_meetings_by_date("2023-05-16T10:00:00")
print(f"Found {len(may16_meetings)} meetings on May 16, 2023:")
for meeting in may16_meetings:
print(f" - {meeting}")

# Query meetings by clip_id
print("\n=== Querying meetings by clip_id ===")
clip_meetings = await dynamo_service.query_meetings_by_clip_id("67890")
print(f"Found {len(clip_meetings)} meetings with clip_id '67890':")
for meeting in clip_meetings:
print(f" - {meeting}")

# Get a specific meeting
print("\n=== Getting a specific meeting ===")
specific_meeting = await dynamo_service.get_meeting(
"Planning Commission", "2023-05-16T10:00:00"
)
if specific_meeting:
print(f"Found specific meeting: {specific_meeting}")
else:
print("Specific meeting not found")

# Update meeting using the dictionary-based method
print("\n=== Updating meeting (dictionary-based) ===")
update_success = await dynamo_service.update_meeting(
"Planning Commission",
"2023-05-16T10:00:00",
{
"duration": "2h 0m", # Changed from 1h 30m to 2h 0m
"clip_id": "67890-updated",
},
)
if update_success:
print("Successfully updated Planning Commission meeting")
# Get the updated meeting to verify changes
updated_meeting = await dynamo_service.get_meeting(
"Planning Commission", "2023-05-16T10:00:00"
)
if updated_meeting:
print(f"Updated meeting: {updated_meeting}")
else:
print("Failed to update meeting")

# Query meetings with updated clip_id
print("\n=== Querying meetings by updated clip_id ===")
updated_clip_meetings = await dynamo_service.query_meetings_by_clip_id(
"67890-updated"
)
print(f"Found {len(updated_clip_meetings)} meetings with clip_id '67890-updated':")
for meeting in updated_clip_meetings:
print(f" - {meeting}")

# Update meeting using the model-based method
print("\n=== Updating meeting (model-based) ===")
# Create a partial model with only the fields to update
updated_model = Meeting(
name="City Council",
date="2023-05-15T18:00:00",
duration="3h 0m", # Changed from 2h 15m to 3h 0m
video=HttpUrl("https://example.com/video1-new"),
)

update_success = await dynamo_service.update(
"City Council", "2023-05-15T18:00:00", updated_model
)

if update_success:
print("Successfully updated City Council meeting")
# Get the updated meeting to verify changes
updated_meeting = await dynamo_service.get_meeting(
"City Council", "2023-05-15T18:00:00"
)
if updated_meeting:
print(f"Updated meeting: {updated_meeting}")
else:
print("Failed to update meeting")

# List all meetings
print("\n=== Listing all meetings ===")
all_meetings = await dynamo_service.all()
print(f"Found {len(all_meetings)} total meetings:")
for meeting in all_meetings:
print(f" - {meeting}")

# Delete a meeting
print("\n=== Deleting a meeting ===")
delete_success = await dynamo_service.delete(
"Planning Commission", "2023-05-16T10:00:00"
)
if delete_success:
print("Successfully deleted Planning Commission meeting")
else:
print("Failed to delete meeting")

# Verify deletion by listing all meetings again
print("\n=== Verifying deletion ===")
remaining_meetings = await dynamo_service.all()
print(f"Remaining meetings ({len(remaining_meetings)}):")
for meeting in remaining_meetings:
print(f" - {meeting}")


if __name__ == "__main__":
asyncio.run(main())
Loading
Loading