Проект: Marketplace MVP (mp1)
Версия: 1.0.0
Последнее обновление: 2025-11-08
# Создать feature branch
git checkout -b feature/cdek-integration
# Изучить документацию
# - MP1-PROJECT-MASTER.md (текущее состояние)
# - MP1-ROADMAP.md (приоритеты)
# - MP1-API-GUIDE.md (API справка)
# - CODE-GUIDE.md (стандарты кода)
Структура:
modules/
├── delivery/
│ ├── __init__.py
│ ├── cdek.py # Новый модуль
│ └── base.py # Базовый класс (если нужен)
tests/
├── test_cdek_api.py # Тесты
pages/
├── 90_Доставка.py # Новая страница (если нужна)
Код:
# modules/delivery/cdek.py
from typing import List, Dict, Optional
import requests
import logging
logger = logging.getLogger(__name__)
class CdekAPI:
"""
Клиент для работы с СДЭК API v2.
Docs: https://api-docs.cdek.ru/
"""
def __init__(self, account: str, secure_password: str, timeout: int = 10):
self.account = account
self.secure_password = secure_password
self.timeout = timeout
self.base_url = "https://api.cdek.ru/v2"
self.token = None
def _get_token(self) -> str:
"""Получить OAuth токен"""
url = f"{self.base_url}/oauth/token"
data = {
"grant_type": "client_credentials",
"client_id": self.account,
"client_secret": self.secure_password
}
try:
response = requests.post(url, data=data, timeout=self.timeout)
response.raise_for_status()
return response.json()["access_token"]
except Exception as e:
logger.error(f"Ошибка получения токена СДЭК: {e}")
raise
def create_order(self, order_data: dict) -> dict:
"""Создать заказ в СДЭК"""
if not self.token:
self.token = self._get_token()
url = f"{self.base_url}/orders"
headers = {"Authorization": f"Bearer {self.token}"}
try:
response = requests.post(url, json=order_data, headers=headers, timeout=self.timeout)
response.raise_for_status()
return response.json()
except Exception as e:
logger.error(f"Ошибка создания заказа СДЭК: {e}")
raise
Тесты:
# tests/test_cdek_api.py
import pytest
from modules.delivery.cdek import CdekAPI
def test_cdek_connection():
api = CdekAPI(
account="test_account",
secure_password="test_password"
)
# Мок для теста
assert api.base_url == "https://api.cdek.ru/v2"
@pytest.mark.integration
def test_cdek_create_order_real():
"""Интеграционный тест с реальным API (sandbox)"""
api = CdekAPI(
account="z9GRRu7FxmO53CQ9cFfI6qiy32wpfTkd",
secure_password="w24JTCv4MnAcuRTx0oHjHLDtyt3I6IBq"
)
order_data = {
"tariff_code": 136,
# ... данные заказа
}
result = api.create_order(order_data)
assert "entity" in result
assert "uuid" in result["entity"]
Обновить MP1-API-GUIDE.md:
## 🚚 СДЭК API
**Статус:** ✅ Реализовано
**Модуль:** `modules/delivery/cdek.py`
### create_order(order_data: dict) → dict
Создать заказ в СДЭК
\`\`\`python
from modules.delivery.cdek import CdekAPI
api = CdekAPI(account="...", secure_password="...")
order = api.create_order({
"tariff_code": 136,
...
})
\`\`\`
Обновить MP1-CHANGELOG.md:
## [2.5.0] - 2025-11-09
### Added
- Интеграция СДЭК API (создание заказов, получение этикеток)
- Страница "Доставка" для управления отправками
### Changed
- Обновлён MP1-API-GUIDE.md (добавлен СДЭК)
# Проверить код
flake8 modules/delivery/cdek.py
mypy modules/delivery/cdek.py
# Запустить тесты
pytest tests/test_cdek_api.py -v
# Коммит
git add .
git commit -m "feat: интеграция СДЭК API"
# Merge
git checkout master
git merge feature/cdek-integration
# Push
git push origin master
# modules/database/models.py
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime
class Shipment(Base):
"""Отправления СДЭК/Почты"""
__tablename__ = "shipments"
id = Column(Integer, primary_key=True, index=True)
order_id = Column(Integer, ForeignKey("orders.id"), nullable=False)
delivery_service = Column(String(20), nullable=False, index=True) # cdek, pochta
tracking_number = Column(String(100), unique=True, nullable=False, index=True)
status = Column(String(50), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Relationships
order = relationship("Order", back_populates="shipments")
def __repr__(self):
return f"<Shipment {self.tracking_number}>"
# Активировать venv
source venv/bin/activate
# Создать миграцию
alembic revision --autogenerate -m "add shipments table"
# Проверить миграцию
cat alembic/versions/xxx_add_shipments_table.py
# Применить
alembic upgrade head
# Проверить текущую версию
alembic current
# SQLite
sqlite3 data/marketplace.db
sqlite> .schema shipments
sqlite> SELECT * FROM alembic_version;
sqlite> .quit
# PostgreSQL
psql -U postgres -d marketplace_db
\d shipments
SELECT * FROM alembic_version;
\q
# modules/database/models.py
class Order(Base):
# ... существующие поля ...
# Новое поле
delivery_deadline = Column(DateTime, nullable=True)
alembic revision --autogenerate -m "add delivery_deadline to orders"
# alembic/versions/xxx_add_delivery_deadline_to_orders.py
def upgrade():
# ✅ Проверить что поле добавляется корректно
op.add_column('orders',
sa.Column('delivery_deadline', sa.DateTime(), nullable=True)
)
def downgrade():
# ✅ Проверить что downgrade работает
op.drop_column('orders', 'delivery_deadline')
# Применить
alembic upgrade head
# Если что-то пошло не так - откатить
alembic downgrade -1
# Откатить последнюю миграцию
alembic downgrade -1
# Откатить к конкретной версии
alembic downgrade abc123
# Откатить все миграции
alembic downgrade base
# Посмотреть историю
alembic history
Документация: https://openapi.wildberries.ru/
Авторизация: API токен
Rate limit: 200 запросов/минуту
# modules/api/wildberries.py
import requests
import logging
from typing import List, Dict, Optional
logger = logging.getLogger(__name__)
class WildberriesAPI:
"""
Клиент для работы с Wildberries API.
Docs: https://openapi.wildberries.ru/
"""
def __init__(self, api_key: str, timeout: int = 10):
self.api_key = api_key
self.timeout = timeout
self.base_url = "https://suppliers-api.wildberries.ru"
def test_connection(self) -> bool:
"""Проверка подключения к API"""
try:
# Тестовый запрос
response = self._request("GET", "/api/v2/ping")
return response.status_code == 200
except Exception as e:
logger.error(f"Ошибка подключения к WB API: {e}")
return False
def fetch_orders(self, date_from: str, date_to: str) -> List[Dict]:
"""Получить заказы за период"""
endpoint = "/api/v2/orders"
params = {
"dateFrom": date_from,
"dateTo": date_to
}
response = self._request("GET", endpoint, params=params)
return response.json().get("orders", [])
def _request(self, method: str, endpoint: str, **kwargs):
"""Базовый метод для запросов"""
url = f"{self.base_url}{endpoint}"
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
try:
response = requests.request(
method, url, headers=headers, timeout=self.timeout, **kwargs
)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
logger.error(f"WB API error: {e}")
raise
# modules/database/models.py
class Channel(Base):
# ... существующие поля ...
# Добавить новый тип маркетплейса
marketplace = Column(String(20), nullable=False) # ozon, wildberries, yandex
Создать миграцию для изменения ENUM (если используется PostgreSQL).
# pages/20_Каналы.py
# Визард подключения
if marketplace == "Wildberries":
st.info("📘 Получите API ключ в личном кабинете WB: Настройки → API")
api_key = st.text_input("API ключ Wildberries", type="password")
if st.button("Проверить подключение"):
from modules.api.wildberries import WildberriesAPI
api = WildberriesAPI(api_key=api_key)
if api.test_connection():
st.success("✅ Подключение успешно!")
else:
st.error("❌ Ошибка подключения")
MP1-API-GUIDE.md:
## 🛒 WILDBERRIES API
**Документация:** https://openapi.wildberries.ru/
**Модуль:** `modules/api/wildberries.py`
**Класс:** `WildberriesAPI`
### Аутентификация
\`\`\`python
from modules.api.wildberries import WildberriesAPI
api = WildberriesAPI(api_key="your_api_key")
\`\`\`
MP1-CHANGELOG.md:
## [2.6.0] - 2025-11-10
### Added
- Интеграция Wildberries API
- Поддержка загрузки заказов WB
См. раздел "Добавление новой функции" → пример с СДЭК API.
Ключевые отличия:
modules/delivery/ (не modules/api/)# modules/delivery/base.py
from abc import ABC, abstractmethod
from typing import Dict
class DeliveryServiceBase(ABC):
"""Базовый класс для служб доставки"""
@abstractmethod
def create_order(self, order_data: dict) -> dict:
"""Создать заказ"""
pass
@abstractmethod
def get_label(self, order_id: str) -> bytes:
"""Получить этикетку (PDF)"""
pass
@abstractmethod
def track(self, tracking_number: str) -> dict:
"""Трекинг отправления"""
pass
# modules/delivery/cdek.py
from modules.delivery.base import DeliveryServiceBase
class CdekAPI(DeliveryServiceBase):
def create_order(self, order_data: dict) -> dict:
# Реализация для СДЭК
pass
# Убедиться что все изменения закоммичены
git status
# Запустить тесты
pytest tests/ -v
# Проверить код
flake8 .
mypy modules/
MP1-CHANGELOG.md:
## [2.5.0] - 2025-11-09
### Added
- Интеграция СДЭК API
- Автоматическая обработка realFBS заказов
- Страница "Доставка"
### Changed
- Оптимизирован OzonAPI.fetch_orders
- Обновлён UI страницы заказов
### Fixed
- Исправлена загрузка трек-номеров для realFBS
version.py (если есть):
# version.py
__version__ = "2.5.0"
# Создать аннотированный тег
git tag -a v2.5.0 -m "Release v2.5.0: СДЭК integration"
# Проверить
git tag -l
# Push тега
git push origin v2.5.0
# Обновить на production сервере
ssh user@server
cd /path/to/project
git pull origin master
source venv/bin/activate
alembic upgrade head
systemctl restart marketplace-app
Симптомы:
streamlit run app.py
# Ошибка: ModuleNotFoundError: No module named 'streamlit'
Решение:
# Проверить venv
which python
# Должно быть: /path/to/marketplace-mvp/venv/bin/python
# Если нет - активировать
source venv/bin/activate
# Переустановить зависимости
pip install -r requirements.txt
Симптомы:
alembic upgrade head
# sqlalchemy.exc.IntegrityError: UNIQUE constraint failed
Решение:
# Откатить миграцию
alembic downgrade -1
# Проверить текущую версию
alembic current
# Исправить миграцию (добавить обработку дубликатов)
# alembic/versions/xxx_migration.py
# Применить снова
alembic upgrade head
Симптомы:
api = OzonAPI(client_id="...", api_key="...")
orders = api.fetch_orders(...)
# Ошибка: 401 Unauthorized
Решение:
# 1. Проверить .env
cat .env | grep OZON
# 2. Проверить что ключи валидны
# Зайти в личный кабинет Ozon → Настройки → API
# 3. Проверить права ключа
# Ключ должен иметь права на чтение заказов
# 4. Тест подключения
python -c "
from modules.api.ozon import OzonAPI
api = OzonAPI(client_id='...', api_key='...')
print(api.test_connection())
"
Симптомы:
Страница "Заказы" загружается 10+ секунд
Решение:
# 1. Добавить кеширование
import streamlit as st
@st.cache_data(ttl=600) # Кеш на 10 минут
def load_orders():
return db.query(Order).all()
# 2. Пагинация
page = st.number_input("Страница", min_value=1, value=1)
page_size = 50
orders = db.query(Order).limit(page_size).offset((page - 1) * page_size).all()
# 3. Lazy loading для relationships
from sqlalchemy.orm import lazyload
orders = db.query(Order).options(lazyload(Order.items)).all()
Симптомы:
git merge feature/my-branch
# CONFLICT (content): Merge conflict in modules/database/models.py
Решение:
# 1. Посмотреть конфликты
git status
# 2. Открыть файл и разрешить конфликт вручную
vim modules/database/models.py
# Найти:
<<<<<<< HEAD
# Ваша версия
=======
# Версия из ветки
>>>>>>> feature/my-branch
# 3. Оставить нужную версию, удалить маркеры
# 4. Добавить файл
git add modules/database/models.py
# 5. Завершить merge
git commit -m "Merge feature/my-branch"
Симптомы:
pytest tests/
# 5 failed, 10 passed
# В CI все тесты проходят
Решение:
# 1. Проверить версии зависимостей
pip freeze > local_requirements.txt
# Сравнить с requirements.txt
# 2. Пересоздать venv
deactivate
rm -rf venv
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# 3. Очистить кеши
rm -rf __pycache__
rm -rf .pytest_cache
rm -rf .mypy_cache
# 4. Запустить снова
pytest tests/ -v
Симптомы:
Приложение использует всё больше памяти со временем
Решение:
# 1. Проверить session_state
# Не сохранять большие объекты
if 'large_data' in st.session_state:
del st.session_state.large_data
# 2. Ограничить ttl кеша
@st.cache_data(ttl=600, max_entries=10) # Макс 10 записей в кеше
def load_data():
pass
# 3. Закрывать DB сессии
db = get_db_session()
try:
# работа с БД
finally:
db.close() # ✅ Всегда закрывать
# 4. Перезапускать приложение периодически (cron)
# crontab -e
# 0 4 * * * systemctl restart streamlit-app
Последнее обновление: 2025-11-08
Автор: MP1 Team