import json import redis import os import pickle import io import base64 from typing import Any, Optional class DjangoModelStub: """Stub class to represent Django models when unpickling""" def __init__(self, *args, **kwargs): if args and isinstance(args[0], dict): for key, value in args[0].items(): setattr(self, key, value) for key, value in kwargs.items(): setattr(self, key, value) def __getstate__(self): return self.__dict__ def __setstate__(self, state): for key, value in state.items(): setattr(self, key, value) class CustomUnpickler(pickle.Unpickler): """Custom unpickler that handles Django models without requiring Django""" def find_class(self, module, name): if module.startswith('django.') or module.startswith('studio_api.'): return DjangoModelStub return super().find_class(module, name) def handler(event, context): """ AWS Lambda handler for Redis operations. Expected event format: { "operation": "get"|"set_json"|"delete", "keys": ["key1", "key2", ...], "values": ["value1", "value2", ...], # Only for 'set_json' operation "ttl": 3600, # Optional, only for 'set_json' operation } """ try: operation = event.get('operation') if not operation: return error_response(400, "Missing required parameter: 'operation'") keys = event.get('keys') if not keys: return error_response(400, "Missing required parameter: 'keys'") redis_client = get_redis_client() if operation == 'get': result = handle_get(redis_client, keys) elif operation == 'set_json': values = event.get('values') if not values: return error_response(400, "Missing required parameter: 'values' for 'set_json' operation") ttl = event.get('ttl') result = handle_set_json(redis_client, keys, values, ttl) elif operation == 'delete': result = handle_delete(redis_client, keys) else: return error_response(400, f"Invalid operation: {operation}") return success_response(result) except Exception as e: return error_response(500, f"Error processing Redis operation: {str(e)}") def get_redis_client(): redis_url = os.environ.get('REDIS_URL') if not redis_url: raise ValueError("REDIS_URL environment variable is not set") return redis.Redis.from_url(redis_url) def handle_get(redis_client, keys): def deserialize(value): if value is None: return None try: unpickler = CustomUnpickler(io.BytesIO(value)) return unpickler.load() except Exception: pass try: return json.loads(value.decode('utf-8')) except Exception: pass try: return value.decode('utf-8') except Exception: pass try: return base64.b64encode(value).decode('ascii') except Exception: return value if len(keys) == 1: value = redis_client.get(keys[0]) return deserialize(value) else: # For multiple keys, use pipeline for efficiency pipe = redis_client.pipeline() for key in keys: pipe.get(key) values = pipe.execute() # Deserialize all values results = {} for i, key in enumerate(keys): results[key] = deserialize(values[i]) return results def handle_set_json(redis_client, keys, values, ttl=None): def serialize(value): if isinstance(value, (dict, list)): return json.dumps(value) return value if len(keys) == 1: success = redis_client.set(keys[0], serialize(values[0]), ex=ttl) return success else: # For multiple keys, use pipeline for efficiency pipe = redis_client.pipeline() for i, key in enumerate(keys): pipe.set(key, serialize(values[i]), ex=ttl) results = pipe.execute() return dict(zip(keys, results)) def handle_delete(redis_client, keys): if len(keys) == 1: deleted = redis_client.delete(keys[0]) return deleted > 0 else: # For multiple keys, use pipeline for efficiency pipe = redis_client.pipeline() for key in keys: pipe.delete(key) results = pipe.execute() return dict(zip(keys, [count > 0 for count in results])) def convert_to_serializable(obj): """ Convert any object to a JSON-serializable format. Recursively handles nested objects, lists, and dictionaries. """ if isinstance(obj, DjangoModelStub): result = {} for key, value in obj.__dict__.items(): result[key] = convert_to_serializable(value) return result elif isinstance(obj, dict): return {k: convert_to_serializable(v) for k, v in obj.items()} elif isinstance(obj, list): return [convert_to_serializable(item) for item in obj] elif isinstance(obj, (str, int, float, bool, type(None))): return obj else: try: if hasattr(obj, '__dict__'): return convert_to_serializable(obj.__dict__) elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes)): return [convert_to_serializable(item) for item in obj] else: return str(obj) except: return str(obj) def success_response(data): serializable_data = convert_to_serializable(data) result = { 'statusCode': 200, 'body': json.dumps(serializable_data) } print(result) return result def error_response(status_code, message): result = { 'statusCode': status_code, 'body': json.dumps({'error': message}) } print(result) return result