rewritten crap
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -21,3 +21,4 @@ class MarketplaceApiFactory:
|
||||
return WildberriesMarketplaceApi(marketplace)
|
||||
case BaseMarketplace.YANDEX_MARKET:
|
||||
return YandexmarketMarketplaceApi(marketplace)
|
||||
raise ValueError()
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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})
|
||||
|
||||
Reference in New Issue
Block a user