import time
import random
from functools import wraps

TRANSIENT_CODES = {
    "ProvisionedThroughputExceededException",
    "ThrottlingException",
    "RequestLimitExceeded"
}

def with_retry(max_attempts: int = 5, base_delay: float = 0.2, max_delay: float = 2.0, jitter: float = 0.15):
    """
    Decorator adding exponential backoff retry for transient DynamoDB errors.
    - max_attempts: total tries (first + retries).
    - base_delay: starting delay.
    - max_delay: upper bound on delay.
    - jitter: random extra seconds to reduce collision.
    Retries only when error code is in TRANSIENT_CODES.
    """
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            attempt = 0
            while True:
                try:
                    return fn(*args, **kwargs)
                except Exception as e:
                    code = getattr(e, "response", {}).get("Error", {}).get("Code")
                    attempt += 1
                    should_retry = code in TRANSIENT_CODES and attempt < max_attempts
                    if not should_retry:
                        raise
                    # Exponential backoff: delay = base * 2^(attempt-1)
                    delay = min(base_delay * (2 ** (attempt - 1)), max_delay)
                    delay += random.uniform(0, jitter)
                    time.sleep(delay)
        return wrapper
    return decorator