fix cdek client
This commit is contained in:
@@ -14,3 +14,7 @@ class DeliveryProvider(ABC):
|
||||
"""Fetch one quote from an external provider."""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ProviderRequestError(RuntimeError):
|
||||
"""Raised when a provider rejects input request data."""
|
||||
|
||||
@@ -1,23 +1,38 @@
|
||||
"""CDEK HTTP client and provider adapter."""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from app.adapters.delivery_providers.base import DeliveryProvider
|
||||
from app.adapters.delivery_providers.base import DeliveryProvider, ProviderRequestError
|
||||
from app.adapters.delivery_providers.cdek.auth import CDEKAuthClient
|
||||
from app.adapters.delivery_providers.cdek.mapper import map_cdek_response
|
||||
from app.config import AdapterConfig
|
||||
from app.schemas.request import DeliveryRequest
|
||||
from app.schemas.response import DeliveryPrice
|
||||
import logging
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
handlers=[logging.StreamHandler(sys.stdout)] # важно: stdout, не stderr
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
log.info("test")
|
||||
|
||||
|
||||
class CDEKClientError(RuntimeError):
|
||||
"""Raised when CDEK tariff request fails."""
|
||||
|
||||
|
||||
class CDEKRequestError(CDEKClientError, ProviderRequestError):
|
||||
"""Raised when CDEK rejects request data as invalid."""
|
||||
|
||||
|
||||
class CDEKClient:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -133,6 +148,7 @@ class CDEKClient:
|
||||
raise CDEKClientError(
|
||||
f"CDEK city lookup failed with status {response.status_code}."
|
||||
)
|
||||
log.info(response.json())
|
||||
try:
|
||||
response.raise_for_status()
|
||||
body = response.json()
|
||||
@@ -143,7 +159,9 @@ class CDEKClient:
|
||||
raise CDEKClientError("CDEK city lookup failed unexpectedly.")
|
||||
|
||||
if not isinstance(body, list) or not body:
|
||||
raise CDEKClientError(f"CDEK city lookup returned no matches for '{city}'.")
|
||||
raise CDEKRequestError(
|
||||
f"CDEK city lookup returned no matches for '{city}'."
|
||||
)
|
||||
first_item = body[0]
|
||||
if not isinstance(first_item, dict):
|
||||
raise CDEKClientError("CDEK city lookup response entry must be an object.")
|
||||
|
||||
@@ -8,7 +8,11 @@ from app.controllers.http_client import build_controller_http_client
|
||||
from app.repositories.cache.redis_cache import PriceCache
|
||||
from app.schemas.request import DeliveryRequest
|
||||
from app.schemas.response import DeliveryPrice
|
||||
from app.services.aggregator import AggregatorService, AggregatorServiceError
|
||||
from app.services.aggregator import (
|
||||
AggregatorService,
|
||||
AggregatorServiceError,
|
||||
InvalidDeliveryRequestError,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/delivery", tags=["delivery"])
|
||||
|
||||
@@ -56,6 +60,14 @@ async def get_delivery_price(
|
||||
) -> list[DeliveryPrice]:
|
||||
try:
|
||||
return await service.get_all_prices(delivery_request)
|
||||
except InvalidDeliveryRequestError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "invalid_delivery_request",
|
||||
"message": "Delivery request contains unknown or unsupported location.",
|
||||
},
|
||||
) from exc
|
||||
except AggregatorServiceError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
from typing import Protocol
|
||||
|
||||
from app.adapters.delivery_providers.base import DeliveryProvider
|
||||
from app.adapters.delivery_providers.base import DeliveryProvider, ProviderRequestError
|
||||
from app.domain.price import (
|
||||
DEFAULT_WEIGHT_ROUND_SCALE,
|
||||
NormalizedDeliveryRequest,
|
||||
@@ -21,6 +21,10 @@ class AggregatorServiceError(RuntimeError):
|
||||
"""Base exception for AggregatorService failures."""
|
||||
|
||||
|
||||
class InvalidDeliveryRequestError(AggregatorServiceError):
|
||||
"""Raised when provider rejects delivery request as invalid."""
|
||||
|
||||
|
||||
class PriceCacheProtocol(Protocol):
|
||||
async def get(self, key: str) -> object | None: ...
|
||||
|
||||
@@ -69,6 +73,19 @@ class AggregatorService:
|
||||
for price in provider_results
|
||||
if isinstance(price, DeliveryPrice)
|
||||
]
|
||||
provider_errors = [
|
||||
error
|
||||
for error in provider_results
|
||||
if isinstance(error, Exception)
|
||||
]
|
||||
if (
|
||||
not successful_results
|
||||
and provider_errors
|
||||
and any(isinstance(error, ProviderRequestError) for error in provider_errors)
|
||||
):
|
||||
raise InvalidDeliveryRequestError(
|
||||
"Delivery request is invalid for configured providers."
|
||||
)
|
||||
filtered_and_sorted = self._filter_and_sort_prices(successful_results)
|
||||
return [self._coerce_delivery_price(price) for price in filtered_and_sorted]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user