import json from abc import ABC, abstractmethod from typing import List, Optional import redis.asyncio from sqlalchemy.ext.asyncio import AsyncSession import queries.general from database import Marketplace from limiter import redis_client from marketplaces import MarketplaceApiFactory from marketplaces.base import BaseMarketplaceApi from queries.general import StockData from schemas.general import StockUpdate from sender.base import StockRequest from sender.factory import SenderFactory class BaseMarketplaceUpdater(ABC): marketplace: Marketplace marketplace_api: BaseMarketplaceApi session: AsyncSession lock_key: Optional[str] cache_key: Optional[str] redis_client: redis.asyncio.Redis def __init__(self, marketplace: Marketplace, session: AsyncSession): self.marketplace = marketplace self.session = session self.marketplace_api = MarketplaceApiFactory.get_marketplace_api(marketplace) self.redis_client = redis_client.get_client() self.sender = SenderFactory.get_sender(self) self.cache_key = None self.lock_key = None def is_valid_updater(self) -> bool: if not self.marketplace_api: return False return self.marketplace_api.is_valid @abstractmethod def get_stock_request(self, stock_data: StockData) -> StockRequest: pass @abstractmethod def _get_identifier(self) -> str: raise NotImplementedError() def __get_base_marketplace_key(self): base_marketplace = 'wb' if self.marketplace.base_marketplace == 1: base_marketplace = 'ozon' elif self.marketplace.base_marketplace == 2: base_marketplace = 'yandexmarket' return base_marketplace def get_lock_key(self) -> str: identifier = self._get_identifier() base_marketplace = self.__get_base_marketplace_key() return f'{base_marketplace}_{identifier}_lock' def get_cache_key(self): identifier = self._get_identifier() base_marketplace = self.__get_base_marketplace_key() return f'{base_marketplace}_{self.marketplace.warehouse_id}_{identifier}_cache' def get_auth_data(self) -> dict: try: return json.loads(self.marketplace.auth_data) except Exception as e: return {} async def filter_stocks_data(self, stock_data_list: list[StockData]) -> list[StockData]: cached_stocks: dict = await self.redis_client.hgetall(self.get_cache_key()) cached_stocks = {int(k): int(v) for k, v in cached_stocks.items()} result = [] for stock_data in stock_data_list: cached_stock = cached_stocks.get(stock_data['product_id']) if cached_stock is not None and cached_stock == stock_data['full_stock']: continue result.append(stock_data) return result async def after_sender_sent(self, stock_requests: list[StockRequest], invalid_product_ids: list[int]): stock_requests = list(filter(lambda stock: stock['product_id'] not in invalid_product_ids, stock_requests)) mapping = {stock['product_id']: stock['full_stock'] for stock in stock_requests} await self.redis_client.hset(self.get_cache_key(), mapping=mapping) async def get_marketplace_updates(self, stock_data_list: list[StockData]) -> list[StockRequest]: marketplace_updates = [] for stock_data in stock_data_list: marketplace_update = self.get_stock_request(stock_data) marketplace_updates.append(marketplace_update) return marketplace_updates async def update(self, updates: List[StockUpdate]): product_ids = list(set([update.product_id for update in updates])) await self.update_products(product_ids) async def update_products(self, product_ids: list[int]): if not self.is_valid_updater(): return stock_data_list = await queries.general.get_stocks_data( session=self.session, marketplace=self.marketplace, product_ids=product_ids ) stock_data_list = await self.filter_stocks_data(stock_data_list) stock_requests = await self.get_marketplace_updates(stock_data_list) invalid_product_ids = await self.sender.send(stock_requests) await self.after_sender_sent(stock_requests, invalid_product_ids) async def update_all(self): if not self.is_valid_updater(): return stock_data_list = await queries.general.get_stocks_data( session=self.session, marketplace=self.marketplace, ) stock_data_list = await self.filter_stocks_data(stock_data_list) stock_requests = await self.get_marketplace_updates(stock_data_list) invalid_product_ids = await self.sender.send(stock_requests) await self.after_sender_sent(stock_requests, invalid_product_ids) async def get_all_stocks(self, only_available: bool) -> List[StockData]: if not self.is_valid_updater(): return [] stock_data_list = await queries.general.get_stocks_data( session=self.session, marketplace=self.marketplace ) if only_available: stock_data_list = list(filter(lambda x: x["full_stock"] > 0, stock_data_list)) for idx, stock_data in enumerate(stock_data_list): stock_data['product_id'] = stock_data['marketplace_product'].product_id del stock_data["marketplace_product"] stock_data_list[idx] = stock_data return stock_data_list async def reset(self): if not self.is_valid_updater(): return stock_data_list = await queries.general.get_stocks_data( session=self.session, marketplace=self.marketplace ) stock_requests = await self.get_marketplace_updates(stock_data_list) await self.sender.send(stock_requests)