fix: Correct retry logic for 4xx errors and update deprecated datetime calls
- Fixed resilient_http_call decorator to NOT retry on 4xx client errors (only 5xx) - Changed retry condition from retry_if_exception_type to retry_if_exception with custom logic - Updated datetime.utcnow() to datetime.now(UTC) to fix deprecation warnings - Fixed test imports to add lib/ to sys.path All 12 unit tests now pass with no warnings.
This commit is contained in:
parent
6337182226
commit
eb8b14b86f
@ -15,7 +15,7 @@ Usage:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, UTC
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Callable, TypeVar
|
from typing import Any, Callable, TypeVar
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
@ -25,7 +25,7 @@ from tenacity import (
|
|||||||
retry,
|
retry,
|
||||||
stop_after_attempt,
|
stop_after_attempt,
|
||||||
wait_exponential,
|
wait_exponential,
|
||||||
retry_if_exception_type,
|
retry_if_exception,
|
||||||
before_sleep_log,
|
before_sleep_log,
|
||||||
RetryError,
|
RetryError,
|
||||||
)
|
)
|
||||||
@ -81,7 +81,7 @@ class CircuitBreaker:
|
|||||||
async with self._lock:
|
async with self._lock:
|
||||||
# Check if we should transition from OPEN → HALF_OPEN
|
# Check if we should transition from OPEN → HALF_OPEN
|
||||||
if self.state == CircuitState.OPEN:
|
if self.state == CircuitState.OPEN:
|
||||||
if self.last_failure_time and datetime.utcnow() - self.last_failure_time > timedelta(seconds=self.timeout_seconds):
|
if self.last_failure_time and datetime.now(UTC) - self.last_failure_time > timedelta(seconds=self.timeout_seconds):
|
||||||
logger.info(f"[{self.service_name}] Circuit transitioning OPEN → HALF_OPEN (testing recovery)")
|
logger.info(f"[{self.service_name}] Circuit transitioning OPEN → HALF_OPEN (testing recovery)")
|
||||||
self.state = CircuitState.HALF_OPEN
|
self.state = CircuitState.HALF_OPEN
|
||||||
else:
|
else:
|
||||||
@ -112,7 +112,7 @@ class CircuitBreaker:
|
|||||||
"""Handle failed call — increment failures and potentially open circuit."""
|
"""Handle failed call — increment failures and potentially open circuit."""
|
||||||
async with self._lock:
|
async with self._lock:
|
||||||
self.consecutive_failures += 1
|
self.consecutive_failures += 1
|
||||||
self.last_failure_time = datetime.utcnow()
|
self.last_failure_time = datetime.now(UTC)
|
||||||
|
|
||||||
if self.state == CircuitState.HALF_OPEN:
|
if self.state == CircuitState.HALF_OPEN:
|
||||||
# Half-open test failed → back to OPEN
|
# Half-open test failed → back to OPEN
|
||||||
@ -185,16 +185,11 @@ def resilient_http_call(
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Apply tenacity retry decorator
|
# Apply tenacity retry decorator with custom retry condition
|
||||||
retrying_func = retry(
|
retrying_func = retry(
|
||||||
stop=stop_after_attempt(max_attempts),
|
stop=stop_after_attempt(max_attempts),
|
||||||
wait=wait_exponential(multiplier=1, min=2, max=10),
|
wait=wait_exponential(multiplier=1, min=2, max=10),
|
||||||
retry=retry_if_exception_type((
|
retry=retry_if_exception(should_retry_exception),
|
||||||
httpx.TimeoutException,
|
|
||||||
httpx.HTTPStatusError,
|
|
||||||
httpx.ConnectError,
|
|
||||||
httpx.RemoteProtocolError,
|
|
||||||
)),
|
|
||||||
before_sleep=before_sleep_log(logger, logging.INFO),
|
before_sleep=before_sleep_log(logger, logging.INFO),
|
||||||
reraise=True,
|
reraise=True,
|
||||||
)(func)
|
)(func)
|
||||||
|
|||||||
@ -2,9 +2,14 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
|
# Add lib/ to path so we can import resilience module
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
|
||||||
|
|
||||||
from resilience import (
|
from resilience import (
|
||||||
resilient_http_call,
|
resilient_http_call,
|
||||||
handle_404_gracefully,
|
handle_404_gracefully,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user