import asyncio import json import logging from typing import Union import jwt import utils from database import Marketplace from limiter import BatchLimiter from marketplaces.base import BaseMarketplaceApi class WildberriesMarketplaceApi(BaseMarketplaceApi): def __init__(self, marketplace: Marketplace): self.marketplace = marketplace auth_data = json.loads(marketplace.auth_data) token = auth_data.get('token') self.is_valid = True try: decoded_token = jwt.decode(token, algorithms=["HS256"], options={"verify_signature": False}) except Exception: logging.error(f"Couldn't decode token for {marketplace.id}") self.is_valid = False return self.limiter_key = str(marketplace.company_id) + str(decoded_token.get('sid')) self.headers = { 'Authorization': token, 'Content-Type': 'application/json', 'User-Agent': 'wbas_seller.denco.store3547' } def get_headers(self): return self.headers @property def api_url(self): return 'https://marketplace-api.wildberries.ru' def _filter_chunk_with_conflict(self, chunk: dict, response: list): if not isinstance(response, list): return chunk filter_skus = [] for error in response: for sku in error.get('data', []): filter_skus.append(sku['sku']) return list(filter(lambda x: x['sku'] not in filter_skus, chunk)) async def update_stocks(self, data: Union[list, dict]): if type(data) is not list: return if not self.is_valid: logging.warning(f'Skipping marketplace [{self.marketplace.id}] because of invalid token') return max_stocks = 1000 chunks = list(utils.chunk_list(data, max_stocks)) if not chunks: return self.init_session() limiter = BatchLimiter() max_retries = 10 while chunks: current_retry = 0 chunk = chunks.pop() while current_retry <= max_retries: try: await limiter.acquire_wildberries(self.limiter_key) request_data = {'stocks': chunk} response = await self._method('PUT', f'/api/v3/stocks/{self.marketplace.warehouse_id}', data=request_data) current_retry += 1 if (response.status not in [204, 409, 429]): response = await response.json() error_message = response.get('message') error_code = response.get('code') logging.warning( f'Error occurred when sending stocks to [{self.marketplace.id}]: {error_message} ({error_code})') break if response.status == 429: logging.warning(f'WB rate limit exceeded for marketplace [{self.marketplace.id}]') await asyncio.sleep(1) continue if response.status == 409: response_data = await response.json() logging.warning( f'Conflict occurred when sending stocks to [{self.marketplace.id}]: {data}') await asyncio.sleep(1) chunk = self._filter_chunk_with_conflict(chunk, response_data) continue await asyncio.sleep(0.2) break except Exception as e: logging.error( f'Exception occurred while sending stocks to marketplace ID [{self.marketplace.id}]: {str(e)}') break await self.session.close()