Python SDK
Python SDK verstka-sdk помогает реализовать интеграцию Verstka на Python backend: открыть редактор, принять callback после сохранения, скачать ZIP, сохранить медиа, обновить HTML/JSON и вернуть ответ Verstka.
Перед этой страницей лучше прочитать общий flow: ОбзорИнтеграция
Установка
Базовая установка:
pip install verstka-sdkЕсли хотите использовать готовые адаптеры для framework, установите extra для вашего framework:
pip install 'verstka-sdk[fastapi]'
pip install 'verstka-sdk[flask]'
pip install 'verstka-sdk[django]'
pip install 'verstka-sdk[drf]'Минимальная версия Python: 3.10.
Конфигурация
Обычно достаточно указать api_key, api_secret и публичный callback_url вашего сайта:
from verstka_sdk import VerstkaConfig
config = VerstkaConfig(
api_key="verstka-api-key",
api_secret="verstka-api-secret",
callback_url="https://site.example/verstka/callback",
)callback_url должен быть доступен Verstka backend снаружи. На этот URL Verstka придет при сохранении публикации в редакторе.
Дополнительные настройки можно задать явно:
config = VerstkaConfig(
api_key="verstka-api-key",
api_secret="verstka-api-secret",
callback_url="https://site.example/verstka/callback",
api_url="https://api.r2.verstka.org/integration",
max_content_size=200 * 1024 * 1024,
request_timeout=60.0,
download_timeout=120.0,
debug=False,
)api_url по умолчанию уже равен https://api.r2.verstka.org/integration.
1. Открыть редактор из админки
В админке лучше сделать кнопку обычной ссылкой, которая открывается в новой вкладке и ведет на route вашего backend:
<a
href="/admin/verstka/edit?post=123"
target="_blank"
>
Редактировать в Verstka
</a>Backend route проверяет права пользователя, загружает статью из CMS, получает editor_url через SDK и делает redirect на этот URL. Ниже пример в стиле FastAPI/Starlette; в другом framework верните redirect своим способом.
from starlette.responses import RedirectResponse
from verstka_sdk import AsyncVerstkaClient
async def open_verstka_editor(article_id: int, user) -> RedirectResponse:
article = await db.get_article(article_id)
if not await permissions.can_edit(user, article):
raise PermissionError("Access denied")
async with AsyncVerstkaClient(config) as client:
editor_url = await client.get_editor_url(
material_id=str(article.id),
vms_json=article.vms_json,
metadata={},
)
return RedirectResponse(editor_url)Если статья открывается впервые, vms_json можно не передавать или передать None. Если статья уже редактировалась в Verstka, передайте последний сохраненный vms_json, чтобы редактор открыл актуальную версию публикации.
2. Сохранить файлы через StorageAdapter
SDK умеет скачать ZIP и найти файлы публикации, но не знает, где ваш сайт хранит медиа. Это решает storage adapter.
Storage adapter нужно написать на стороне сайта. SDK вызывает его методы, а ваша реализация решает, куда положить файл и какой публичный URL вернуть.
Adapter получает временный файл, сохраняет его в ваше публичное хранилище uploads, CDN, S3 bucket или другое место и возвращает публичный URL.
from pathlib import Path
from collections.abc import Mapping
from typing import Any
class CmsStorage:
def save_media(
self,
filename: str,
temp_path: Path,
material_id: str,
metadata: Mapping[str, Any],
) -> str:
return save_to_cdn(f"articles/{material_id}/{filename}", temp_path)
def save_font_file(
self,
filename: str,
temp_path: Path,
material_id: str,
metadata: Mapping[str, Any],
) -> str:
return save_to_cdn(f"verstka/fonts/{filename}", temp_path)
def save_fonts_manifest(
self,
filename: str,
temp_path: Path,
material_id: str,
metadata: Mapping[str, Any],
) -> str:
return save_to_cdn(f"verstka/fonts/{filename}", temp_path)Каждый метод должен вернуть URL, который будет доступен читателям сайта. SDK подставит эти URL в vms_html, vms_json.assets[*].clientUrl, vms_fonts.css и дерево fonts.
save_to_cdn в примере - условная функция вашего приложения, которая сохраняет файл и возвращает публичный URL.
Для локальной разработки можно использовать LocalStorageAdapter или LocalAsyncStorageAdapter, но в production обычно нужен adapter под хранилище вашего сайта.
3. Принять callback после Save
Когда пользователь нажимает Save в Verstka, backend сайта получает callback. Внутри callback SDK проверяет подпись, скачивает ZIP, сохраняет медиа через storage adapter, заменяет временные URL файлов на публичные и вызывает ваш on_finalize.
В on_finalize нужно сохранить обновленные vms_html и vms_json в базу данных сайта.
from verstka_sdk import (
AsyncVerstkaClient,
ContentFinalizeContext,
ContentFinalizeResult,
)
storage = CmsStorage()
async def on_content_finalize(ctx: ContentFinalizeContext) -> ContentFinalizeResult:
await db.save_article_verstka_content(
article_id=ctx.material_id,
vms_html=ctx.vms_html,
vms_json=ctx.vms_json,
)
return ContentFinalizeResult(success=True, vms_json=ctx.vms_json)
async def verstka_callback(request):
callback_data = await request.json()
signature = request.headers.get("X-Verstka-Signature", "")
async with AsyncVerstkaClient(config) as client:
result = await client.process_material_callback(
callback_data,
signature=signature,
storage=storage,
on_finalize=on_content_finalize,
)
return result.to_response()ContentFinalizeContext содержит:
| Поле | Назначение |
|---|---|
material_id | ID материала из CMS. |
metadata | Metadata из callback. |
vms_json | JSON публикации с обновленными clientUrl. |
vms_html | HTML публикации с замененными временными URL. |
saved_media_urls | Словарь {filename: public_url}. |
Важно сохранить обновленный vms_json из callback. При следующем открытии редактора его нужно передать в get_editor_url.
4. Обработать callback шрифтов
Если в Verstka используется свой набор шрифтов сайта, после изменения шрифтов придет отдельное событие site_fonts_updated.
Его обрабатывает process_fonts_callback. Если используете один callback route для всех событий, выбирайте обработчик по callback_data["event"].
from verstka_sdk import FontsFinalizeContext, FontsFinalizeResult
async def on_fonts_finalize(ctx: FontsFinalizeContext) -> FontsFinalizeResult:
await db.save_site_fonts(
fonts=ctx.fonts,
css_url=ctx.css_url,
json_url=ctx.json_url,
)
return FontsFinalizeResult(success=True, fonts=ctx.fonts)
async def verstka_fonts_callback(request):
callback_data = await request.json()
signature = request.headers.get("X-Verstka-Signature", "")
async with AsyncVerstkaClient(config) as client:
result = await client.process_fonts_callback(
callback_data,
signature=signature,
storage=storage,
on_finalize=on_fonts_finalize,
)
return result.to_response()on_finalize для fonts опционален. Даже без него SDK сохранит файлы через storage и вернет Verstka дерево fonts с публичными URL.
5. Использовать framework adapters
Если вы не хотите вручную писать callback route, используйте готовый адаптер для вашего framework. Он регистрирует один callback endpoint и сам выбирает обработку по event: article_saved или site_fonts_updated.
| Framework | Инструмент |
|---|---|
| FastAPI | install_exception_handlers(app), build_callback_router(...) |
| Flask | register_error_handlers(app), build_blueprint(...) |
| Django | build_callback_views(...), VerstkaExceptionMiddleware |
| DRF | build_callback_views(...), verstka_exception_handler |
Metadata
metadata - это необязательный JSON-объект, который сайт передает при открытии редактора. Verstka сохранит его в сессии и вернет в callback.
Metadata удобно использовать для данных, которые помогут сайту обработать сохранение:
- ID пользователя в CMS;
- ID раздела, бренда или проекта;
- флаги окружения;
- короткоживущий токен для дополнительной проверки прав;
- данные для basic авторизации или bearer token
- конфигурация Custom Containers.
editor_url = await client.get_editor_url(
material_id=str(article.id),
vms_json=article.vms_json,
metadata={
"editor_user_id": str(user.id),
"section_id": str(article.section_id),
"customContainers": {},
},
)SDK автоматически добавляет version: "python_<sdk-version>".
Не используйте metadata как произвольное хранилище секретов, приватных ключей или персональных данных, которые не нужны callback-обработчику. Полное описание: metadata.
PreSave hooks
process_material_callback и process_fonts_callback принимают on_pre_save. Hook вызывается после проверки подписи, но до скачивания ZIP. Это место для проверки прав, блокировок и лимитов, основываясь на метаданных callback.
from verstka_sdk import ContentPreSaveContext, PreSaveDecision
def can_save(ctx: ContentPreSaveContext) -> PreSaveDecision:
if not user_can_edit(ctx.metadata.get("timeLimitedAuthToken"), ctx.material_id):
return PreSaveDecision(allow=False, reason="Access denied")
return PreSaveDecision(allow=True)Если вернуть allow=False, SDK не скачивает ZIP, не пишет файлы и отвечает Verstka rc: 0.
Callback authorization
Если callback URL сайта защищен Basic Auth или Bearer token, передайте служебные ключи в metadata при открытии редактора.
Basic Auth:
metadata={
"webhook_auth_user": "callback-user",
"webhook_auth_password": "callback-password",
}Bearer token:
metadata={
"webhook_auth_user": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
}Verstka добавит заголовок Authorization к исходящему callback. Подробнее: Callback Authorization.
Sync client
Если приложение не async, используйте VerstkaClient. Методы называются так же, но вызываются без await:
from verstka_sdk import VerstkaClient
with VerstkaClient(config) as client:
editor_url = client.get_editor_url(
material_id=str(article.id),
vms_json=article.vms_json,
)Справочник методов
| Метод | Где | Назначение |
|---|---|---|
VerstkaClient.get_editor_url(...) | sync | Открывает сессию через POST /session/open и возвращает URL редактора. |
AsyncVerstkaClient.get_editor_url(...) | async | То же для async-приложений. |
VerstkaClient.process_material_callback(...) | sync | Проверяет article_saved, скачивает ZIP, сохраняет медиа, вызывает on_finalize. |
AsyncVerstkaClient.process_material_callback(...) | async | Async-версия обработки article_saved. |
VerstkaClient.process_fonts_callback(...) | sync | Проверяет site_fonts_updated, скачивает fonts ZIP, сохраняет шрифты и manifests. |
AsyncVerstkaClient.process_fonts_callback(...) | async | Async-версия обработки site_fonts_updated. |
LocalStorageAdapter | sync | Файловая reference-реализация storage adapter. |
LocalAsyncStorageAdapter | async | Async reference-реализация storage adapter. |
sign_material(...) | helper | Строит HMAC для material_id:url. |
verify_signature(...) | helper | Безопасно проверяет HMAC. |
build_authorized_content_url(...) | helper | Добавляет api_key и material_id к content_url для скачивания ZIP. |