017 fix returning all tariffs

This commit is contained in:
Раис Юсупалиев
2026-03-16 00:05:10 +03:00
parent 9b9cca0a7c
commit 54160a0e38
11 changed files with 188 additions and 81 deletions
+2 -2
View File
@@ -10,8 +10,8 @@ class DeliveryProvider(ABC):
name: str
@abstractmethod
async def get_price(self, request: DeliveryRequest) -> DeliveryPrice:
"""Fetch one quote from an external provider."""
async def get_prices(self, request: DeliveryRequest) -> list[DeliveryPrice]:
"""Fetch provider tariffs for a delivery request."""
raise NotImplementedError
@@ -89,6 +89,7 @@ class CDEKClient:
if not isinstance(raw_payload, dict):
raise CDEKClientError("CDEK tariff payload must be a JSON object.")
log.info(f"Founded {len(response.json())} tarrifs")
return raw_payload
raise CDEKClientError("CDEK tariff request failed unexpectedly.")
@@ -201,7 +202,7 @@ class CDEKClient:
headers={"Authorization": f"Bearer {token}"},
timeout=self._timeout_seconds,
)
log.info(response.json())
log.info(f"Founded cities: {response.json()}")
except (httpx.TimeoutException, httpx.TransportError) as exc:
if attempt < self._retry_attempts:
await self._sleep(self._retry_delay(attempt))
@@ -282,7 +283,7 @@ class CDEKProvider(DeliveryProvider):
)
return cls(client=client, cache_ttl_seconds=adapter_config.cdek_cache_ttl_seconds)
async def get_price(self, request: DeliveryRequest) -> DeliveryPrice:
async def get_prices(self, request: DeliveryRequest) -> list[DeliveryPrice]:
raw_payload = await self._client.get_raw_price(request)
return map_cdek_response(raw_payload)
+21 -8
View File
@@ -10,9 +10,20 @@ class CDEKMappingError(ValueError):
"""Raised when CDEK response cannot be mapped."""
def map_cdek_response(payload: dict[str, Any]) -> DeliveryPrice:
tariff = _get_first_tariff(payload)
def map_cdek_response(payload: dict[str, Any]) -> list[DeliveryPrice]:
tariffs = _get_tariffs(payload)
payload_currency = payload.get("currency")
return [
_map_tariff(tariff, payload_currency=payload_currency)
for tariff in tariffs
]
def _map_tariff(
tariff: dict[str, Any],
*,
payload_currency: object,
) -> DeliveryPrice:
service_name = tariff.get("tariff_name") or tariff.get("tariff_code")
if service_name is None:
raise CDEKMappingError("CDEK tariff_name is missing.")
@@ -26,7 +37,7 @@ def map_cdek_response(payload: dict[str, Any]) -> DeliveryPrice:
raise CDEKMappingError("CDEK period_min is missing.")
period_max = tariff.get("period_max", period_min)
raw_currency = tariff.get("currency") or payload.get("currency") or "RUB"
raw_currency = tariff.get("currency") or payload_currency or "RUB"
try:
return DeliveryPrice(
@@ -41,11 +52,13 @@ def map_cdek_response(payload: dict[str, Any]) -> DeliveryPrice:
raise CDEKMappingError("CDEK response fields have invalid values.") from exc
def _get_first_tariff(payload: dict[str, Any]) -> dict[str, Any]:
def _get_tariffs(payload: dict[str, Any]) -> list[dict[str, Any]]:
tariff_codes = payload.get("tariff_codes")
if not isinstance(tariff_codes, list) or not tariff_codes:
raise CDEKMappingError("CDEK response must include non-empty tariff_codes.")
tariff = tariff_codes[0]
if not isinstance(tariff, dict):
raise CDEKMappingError("CDEK tariff entry must be an object.")
return tariff
tariffs: list[dict[str, Any]] = []
for tariff in tariff_codes:
if not isinstance(tariff, dict):
raise CDEKMappingError("CDEK tariff entry must be an object.")
tariffs.append(tariff)
return tariffs
+23 -16
View File
@@ -83,7 +83,7 @@ class AggregatorService:
provider_results = await asyncio.gather(
*(
self._get_provider_price(
self._get_provider_prices(
provider=provider,
request=provider_request,
cache_key=self._build_cache_key(
@@ -98,8 +98,9 @@ class AggregatorService:
successful_results = [
price
for price in provider_results
if isinstance(price, DeliveryPrice)
for provider_prices in provider_results
if isinstance(provider_prices, list)
for price in provider_prices
]
provider_errors = [
error
@@ -142,26 +143,26 @@ class AggregatorService:
from_attributes=True,
)
async def _get_provider_price(
async def _get_provider_prices(
self,
*,
provider: DeliveryProvider,
request: DeliveryRequest,
cache_key: str,
) -> DeliveryPrice:
cached_price = await self._get_cached_price(cache_key)
if cached_price is not None:
return cached_price
) -> list[DeliveryPrice]:
cached_prices = await self._get_cached_prices(cache_key)
if cached_prices is not None:
return cached_prices
fresh_price = await provider.get_price(request)
await self._set_cached_price(
fresh_prices = await provider.get_prices(request)
await self._set_cached_prices(
cache_key,
fresh_price,
fresh_prices,
ttl=getattr(provider, "cache_ttl_seconds", None),
)
return fresh_price
return fresh_prices
async def _get_cached_price(self, cache_key: str) -> DeliveryPrice | None:
async def _get_cached_prices(self, cache_key: str) -> list[DeliveryPrice] | None:
if self._cache is None:
return None
try:
@@ -171,14 +172,14 @@ class AggregatorService:
if payload is None:
return None
try:
return self._coerce_delivery_price(payload)
return self._coerce_delivery_prices(payload)
except Exception:
return None
async def _set_cached_price(
async def _set_cached_prices(
self,
cache_key: str,
payload: DeliveryPrice,
payload: list[DeliveryPrice],
*,
ttl: int | None,
) -> None:
@@ -227,3 +228,9 @@ class AggregatorService:
@staticmethod
def _coerce_delivery_price(value: object) -> DeliveryPrice:
return DeliveryPrice.model_validate(value, from_attributes=True)
@classmethod
def _coerce_delivery_prices(cls, value: object) -> list[DeliveryPrice]:
if not isinstance(value, list):
raise TypeError("Cached delivery prices must be a list.")
return [cls._coerce_delivery_price(item) for item in value]