[READ-ONLY] Mirror of https://github.com/mrgnw/scripts.
0

Configure Feed

Select the types of activity you want to include in your feed.

scripts / ddb.py
4.7 kB 169 lines
1#!/usr/bin/env -S uv run --script 2 3# /// script 4# requires-python = ">=3.13" 5# dependencies = [ 6# "boto3", 7# ] 8# /// 9""" 10DynamoDB utilities for converting AWS DynamoDB JSON format to simple Python dictionaries. 11 12Clean, Pythonic approach using recursion and type converters. 13""" 14 15from decimal import Decimal 16from typing import Any, Dict, List, Union 17 18 19def deserialize_dynamodb_value(value: Dict[str, Any]) -> Any: 20 """ 21 Convert a DynamoDB value to Python value using clean recursive approach. 22 23 Args: 24 value: DynamoDB value like {"S": "hello"}, {"N": "42"}, {"L": [...]} 25 26 Returns: 27 Native Python value 28 """ 29 if not isinstance(value, dict) or len(value) != 1: 30 return value 31 32 type_key, type_value = next(iter(value.items())) 33 34 # Type converter mapping - much cleaner than if/elif chain 35 converters = { 36 "S": str, # String 37 "N": lambda x: int(x) if "." not in x else float(x), # Number (smart int/float) 38 "B": bytes, # Binary 39 "BOOL": bool, # Boolean 40 "NULL": lambda x: None, # Null 41 "SS": list, # String Set 42 "NS": lambda x: [int(n) if "." not in n else float(n) for n in x], # Number Set 43 "BS": list, # Binary Set 44 "L": lambda x: [deserialize_dynamodb_value(item) for item in x], # List 45 "M": lambda x: {k: deserialize_dynamodb_value(v) for k, v in x.items()}, # Map 46 } 47 48 converter = converters.get(type_key, lambda x: x) 49 return converter(type_value) 50 51 52def deserialize_dynamodb_item(item: Dict[str, Any]) -> Dict[str, Any]: 53 """ 54 Convert a DynamoDB item to a clean Python dict. 55 56 Args: 57 item: DynamoDB item with typed attributes 58 59 Returns: 60 Clean Python dict with native values 61 """ 62 return {key: deserialize_dynamodb_value(value) for key, value in item.items()} 63 64 65def deserialize_dynamodb_response(response: Dict[str, Any]) -> List[Dict[str, Any]]: 66 """ 67 Convert a DynamoDB query/scan response to clean Python list of dicts. 68 69 Args: 70 response: DynamoDB response like {"Items": [{"id": {"S": "value"}}], "Count": 1} 71 72 Returns: 73 List of clean Python dicts like [{"id": "value"}] 74 """ 75 items = response.get("Items", []) 76 return [deserialize_dynamodb_item(item) for item in items] 77 78 79def deserialize_dynamodb_json(json_data: Union[str, Dict[str, Any]]) -> List[Dict[str, Any]]: 80 """ 81 Convert DynamoDB JSON (string or dict) to clean Python list of dicts. 82 83 Args: 84 json_data: JSON string or dict containing DynamoDB response 85 86 Returns: 87 List of clean Python dicts 88 """ 89 import json 90 91 if isinstance(json_data, str): 92 try: 93 response = json.loads(json_data) 94 except json.JSONDecodeError as e: 95 raise ValueError(f"Invalid JSON string: {e}") 96 else: 97 response = json_data 98 99 return deserialize_dynamodb_response(response) 100 101 102# Alternative: Use boto3's built-in deserializer (if boto3 is available) 103def deserialize_with_boto3(response: Dict[str, Any]) -> List[Dict[str, Any]]: 104 """ 105 Use boto3's built-in TypeDeserializer for the most accurate conversion. 106 This is the "official" way but requires boto3 dependency. 107 """ 108 try: 109 from boto3.dynamodb.types import TypeDeserializer 110 111 deserializer = TypeDeserializer() 112 items = response.get("Items", []) 113 return [{k: deserializer.deserialize(v) for k, v in item.items()} for item in items] 114 115 except ImportError: 116 # Fallback to our custom implementation 117 return deserialize_dynamodb_response(response) 118 119 120def main(): 121 """CLI interface for converting DynamoDB JSON files to clean format.""" 122 import argparse 123 import json 124 import sys 125 from pathlib import Path 126 127 parser = argparse.ArgumentParser(description="Convert DynamoDB JSON format to clean Python dictionaries") 128 parser.add_argument("file", help="Path to DynamoDB JSON file to convert") 129 parser.add_argument( 130 "--use-boto3", 131 action="store_true", 132 help="Use boto3's TypeDeserializer instead of custom implementation", 133 ) 134 135 args = parser.parse_args() 136 137 # Check if file exists 138 file_path = Path(args.file) 139 if not file_path.exists(): 140 print(f"Error: File '{args.file}' not found", file=sys.stderr) 141 sys.exit(1) 142 143 try: 144 # Load the DynamoDB JSON file 145 with open(file_path, "r") as f: 146 raw_data = f.read() 147 148 # Convert to clean format 149 if args.use_boto3: 150 # Parse JSON first, then use boto3 151 response = json.loads(raw_data) 152 clean_data = deserialize_with_boto3(response) 153 else: 154 # Use our custom implementation 155 clean_data = deserialize_dynamodb_json(raw_data) 156 157 # Output clean JSON 158 print(json.dumps(clean_data, indent=2, default=str)) 159 160 except json.JSONDecodeError as e: 161 print(f"Error: Invalid JSON in file '{args.file}': {e}", file=sys.stderr) 162 sys.exit(1) 163 except Exception as e: 164 print(f"Error processing file '{args.file}': {e}", file=sys.stderr) 165 sys.exit(1) 166 167 168if __name__ == "__main__": 169 main()