First implementation API for PIP (Proyecto: Pharmacy Integration Platform)
Run Tests on Branches / Backend Tests (push) Successful in 3m32s
Run Tests on Branches / Frontend Tests (push) Successful in 3m27s

This commit is contained in:
Antoni Nuñez Romeu
2026-07-01 16:46:52 +02:00
parent d935db4b3e
commit c5df0ec0b2
121 changed files with 9575 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
from __future__ import annotations
import asyncio
from dataclasses import dataclass, field
from functools import wraps
from typing import Any, Callable, TypeVar
from src.core.exceptions import ConnectorException
from src.core.logging import get_logger
logger = get_logger("connector_retry")
T = TypeVar("T")
DEFAULT_RETRYABLE_EXCEPTIONS = (ConnectionError, TimeoutError, OSError)
@dataclass
class RetryPolicy:
max_attempts: int = 3
base_delay: float = 1.0
max_delay: float = 30.0
exponential_base: float = 2.0
jitter: bool = True
retryable_exceptions: tuple[type[Exception], ...] = field(default_factory=lambda: DEFAULT_RETRYABLE_EXCEPTIONS)
def delay_for_attempt(self, attempt: int) -> float:
import random
delay = min(self.base_delay * (self.exponential_base ** (attempt - 1)), self.max_delay)
if self.jitter:
delay = delay * (0.5 + random.random() * 0.5)
return delay
def should_retry(self, exception: Exception) -> bool:
if isinstance(exception, ConnectorException):
return False
return isinstance(exception, self.retryable_exceptions)
async def with_retry(
func: Callable[..., Any],
*args: Any,
policy: RetryPolicy | None = None,
connector_type: str = "unknown",
operation: str = "unknown",
**kwargs: Any,
) -> Any:
p = policy or RetryPolicy()
last_exception: Exception | None = None
for attempt in range(1, p.max_attempts + 1):
try:
result = await func(*args, **kwargs)
if attempt > 1:
logger.info("retry_succeeded", extra={"connector_type": connector_type, "operation": operation, "attempt": attempt})
return result
except Exception as exc:
last_exception = exc
if not p.should_retry(exc) or attempt >= p.max_attempts:
break
delay = p.delay_for_attempt(attempt)
logger.warning(
"retry_attempt",
extra={
"connector_type": connector_type,
"operation": operation,
"attempt": attempt,
"delay_s": round(delay, 2),
"error": str(exc),
},
)
await asyncio.sleep(delay)
raise last_exception # type: ignore[misc]
def retry(policy: RetryPolicy | None = None):
p = policy or RetryPolicy()
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
connector_type = getattr(args[0], "connector_type", "unknown") if args else "unknown"
return await with_retry(func, *args, policy=p, connector_type=connector_type, operation=func.__name__, **kwargs)
return wrapper
return decorator