feat: implement backend API clients for multi-marketplace integration and frontend service layer

This commit is contained in:
2026-05-12 13:16:14 +03:00
parent 6974af486a
commit ee39a3d83b
65 changed files with 2501 additions and 33 deletions

View File

@@ -0,0 +1,15 @@
from .client import WBClient
from .services.content import ContentService
from .services.marketplace import MarketplaceService
class WildberriesAPI:
"""
Main entry point for Wildberries SDK.
Instantiates all services with a shared client.
"""
def __init__(self, api_token: str = None):
self.client = WBClient(api_token)
# Initialize services
self.content = ContentService(self.client)
self.marketplace = MarketplaceService(self.client)

67
backend/wb_api/client.py Normal file
View File

@@ -0,0 +1,67 @@
import os
import requests
import time
from typing import Dict, Any, Optional
from .exceptions import WBAPIError, WBAuthError, WBRateLimitError, WBBadRequestError
class WBClient:
"""Base client for interacting with Wildberries API."""
# WB domains
CONTENT_API = "https://content-api.wildberries.ru"
MARKETPLACE_API = "https://marketplace-api.wildberries.ru"
PRICES_API = "https://discounts-prices-api.wildberries.ru"
STATISTICS_API = "https://statistics-api.wildberries.ru"
def __init__(self, api_token: Optional[str] = None):
# Fetch from env if not provided
self.api_token = api_token or os.getenv('WB_API_TOKEN')
if not self.api_token:
raise ValueError("WB_API_TOKEN must be provided")
self.session = requests.Session()
self.session.headers.update({
"Authorization": self.api_token,
"Content-Type": "application/json"
})
def request(self, method: str, base_url: str, path: str, data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None, retries: int = 3) -> Dict[str, Any]:
"""Perform HTTP request to WB API with automatic retries."""
url = f"{base_url}{path}"
for attempt in range(retries):
try:
response = self.session.request(method, url, json=data, params=params)
if response.status_code == 429:
if attempt < retries - 1:
time.sleep(2 ** attempt) # Exponential backoff
continue
raise WBRateLimitError("Rate limit exceeded")
if response.status_code in (401, 403):
raise WBAuthError(f"Authentication failed: {response.text}")
if response.status_code == 400:
raise WBBadRequestError(f"Bad Request: {response.text}")
response.raise_for_status()
if response.content:
return response.json()
return {}
except requests.exceptions.RequestException as e:
if attempt < retries - 1:
time.sleep(2 ** attempt)
continue
raise WBAPIError(f"Request failed: {str(e)}")
raise WBAPIError("Request failed after max retries")
def post(self, base_url: str, path: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
return self.request("POST", base_url, path, data=data)
def get(self, base_url: str, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
return self.request("GET", base_url, path, params=params)

View File

@@ -0,0 +1,15 @@
class WBAPIError(Exception):
"""Base exception for all Wildberries API errors."""
pass
class WBAuthError(WBAPIError):
"""Raised when authentication fails (HTTP 401)."""
pass
class WBRateLimitError(WBAPIError):
"""Raised when API rate limits are exceeded (HTTP 429)."""
pass
class WBBadRequestError(WBAPIError):
"""Raised when API returns HTTP 400 (Bad Request)."""
pass

View File

@@ -0,0 +1,39 @@
from typing import Dict, Any, List
from ..client import WBClient
class ContentService:
"""Service to handle WB product cards (Content API)."""
def __init__(self, client: WBClient):
self.client = client
self.base_url = WBClient.CONTENT_API
def get_cards_list(self, limit: int = 100, updated_at: str = "", last_id: str = "") -> Dict[str, Any]:
"""
Get list of nomenclatures (product cards).
Endpoint: /content/v2/get/cards/list
"""
payload = {
"settings": {
"cursor": {
"limit": limit
},
"filter": {
"withPhoto": -1
}
}
}
if updated_at:
payload["settings"]["cursor"]["updatedAt"] = updated_at
if last_id:
payload["settings"]["cursor"]["nmID"] = int(last_id)
return self.client.post(self.base_url, "/content/v2/get/cards/list", data=payload)
def create_cards(self, cards: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Create product cards.
Endpoint: /content/v2/cards/upload
"""
return self.client.post(self.base_url, "/content/v2/cards/upload", data=cards)

View File

@@ -0,0 +1,39 @@
from typing import Dict, Any, List
from ..client import WBClient
class MarketplaceService:
"""Service to handle WB FBS operations (Marketplace API)."""
def __init__(self, client: WBClient):
self.client = client
self.base_url = WBClient.MARKETPLACE_API
def get_new_orders(self) -> Dict[str, Any]:
"""
Get new orders (FBS).
Endpoint: /api/v3/orders/new
"""
return self.client.get(self.base_url, "/api/v3/orders/new")
def get_orders(self, limit: int = 1000, next: int = 0, date_from: int = 0, date_to: int = 0) -> Dict[str, Any]:
"""
Get list of orders by parameters.
Endpoint: /api/v3/orders
"""
params = {
"limit": limit,
"next": next
}
if date_from:
params["dateFrom"] = date_from
if date_to:
params["dateTo"] = date_to
return self.client.get(self.base_url, "/api/v3/orders", params=params)
def put_orders_status(self, orders: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Change status of orders (e.g. to assembly).
Endpoint: /api/v3/orders/status
"""
return self.client.post(self.base_url, "/api/v3/orders/status", data={"orders": orders})