rewritten crap

This commit is contained in:
2025-05-11 07:46:57 +03:00
parent 41a5fb91f3
commit b5110ec69a
20 changed files with 475 additions and 193 deletions

View File

@@ -8,13 +8,14 @@ from database import Marketplace
class BaseMarketplaceApi(ABC):
session: ClientSession
is_valid: bool
@abstractmethod
def __init__(self, marketplace: Marketplace):
pass
@abstractmethod
async def update_stocks(self, data: Union[list, dict]):
async def update_stocks(self, data: Union[list, dict]) -> ClientResponse:
pass
@abstractmethod
@@ -26,15 +27,15 @@ class BaseMarketplaceApi(ABC):
def api_url(self):
pass
def init_session(self):
self.session = ClientSession()
async def _method(self, http_method: Literal['POST', 'GET', 'PATCH', 'PUT', 'DELETE'],
method: str,
data: dict) -> ClientResponse:
return await self.session.request(
self.session = ClientSession()
response = await self.session.request(
http_method,
f'{self.api_url}{method}',
json=data,
headers=self.get_headers()
)
await self.session.close()
return response

View File

@@ -21,3 +21,4 @@ class MarketplaceApiFactory:
return WildberriesMarketplaceApi(marketplace)
case BaseMarketplace.YANDEX_MARKET:
return YandexmarketMarketplaceApi(marketplace)
raise ValueError()

View File

@@ -1,11 +1,10 @@
import asyncio
import json
import logging
from typing import Union
import utils
from aiohttp import ClientResponse
from database import Marketplace
from limiter import BatchLimiter
from marketplaces.base import BaseMarketplaceApi
@@ -35,47 +34,5 @@ class OzonMarketplaceApi(BaseMarketplaceApi):
def api_url(self):
return 'https://api-seller.ozon.ru'
async def update_stocks(self, data: Union[list, dict]):
if type(data) is not list:
return
if not self.is_valid:
return
max_stocks = 100
chunks = 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_ozon(self.limiter_key)
request_data = {'stocks': chunk}
response = await self._method('POST', '/v2/products/stocks', data=request_data)
current_retry += 1
response = await response.json()
error_message = response.get('message')
error_code = response.get('code')
if error_message:
if error_code == 8:
logging.warning(f'Ozon rate limit exceeded for marketplace [{self.marketplace.id}]')
await asyncio.sleep(1)
continue
else:
logging.warning(
f'Error occurred when sending stocks to [{self.marketplace.id}]: {error_message} ({error_code})')
break
else:
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()
async def update_stocks(self, data: Union[list, dict]) -> ClientResponse:
return await self._method('POST', '/v2/products/stocks', data={'stocks': data})

View File

@@ -1,17 +1,15 @@
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)
@@ -38,61 +36,7 @@ class WildberriesMarketplaceApi(BaseMarketplaceApi):
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()
warehouse_id = self.marketplace.warehouse_id
return await self._method('PUT', f'/api/v3/stocks/{warehouse_id}', data={'stocks': data})
logging.warning(
f'Conflict occurred when sending stocks to [{self.marketplace.id}]')
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()

View File

@@ -28,6 +28,9 @@ class YandexmarketMarketplaceApi(BaseMarketplaceApi):
}
else:
access_token = auth_data.get('accessToken')
if not access_token:
self.is_valid = False
return
self.limiter_key = str(marketplace.company_id) + str(access_token) + str(self.marketplace.campaign_id)
self.headers = {
'Authorization': f'OAuth oauth_token="{access_token}", oauth_client_id="{YANDEX_CLIENT_ID}"'
@@ -41,44 +44,7 @@ class YandexmarketMarketplaceApi(BaseMarketplaceApi):
return 'https://api.partner.market.yandex.ru/v2'
async def update_stocks(self, data: Union[list, dict]):
if type(data) is not list:
return
if not self.is_valid:
return
campaign_id = self.marketplace.campaign_id
max_stocks = 2000
chunks = chunk_list(data, max_stocks)
if not chunks:
return
self.init_session()
limiter = BatchLimiter()
async def send_stock_chunk(chunk):
try:
await limiter.acquire_yandexmarket(self.limiter_key)
request_data = {
'skus': chunk
}
response = await self._method('PUT',
f'/campaigns/{campaign_id}/offers/stocks',
data=request_data)
if response.status != 200:
logging.warning(
f'Error occurred when sending stocks to [{self.marketplace.id}]')
return False
return True
except Exception as e:
logging.error(
f'Exception occurred while sending stocks to marketplace ID [{self.marketplace.id}]: {str(e)}')
return False
tasks = [send_stock_chunk(chunk) for chunk in chunks]
first_request = tasks[0]
first_response = await first_request
if not first_response:
logging.error(f'Skipping marketplace [{self.marketplace.id}] because first request was unsuccessful')
await self.session.close()
return
await asyncio.gather(*tasks[1:])
await self.session.close()
return await self._method('PUT',
f'/campaigns/{campaign_id}/offers/stocks',
data={'skus': data})