Skip to content

Python SDK

Python SDK verstka-sdk помогает реализовать интеграцию Verstka на Python backend: открыть редактор, принять callback после сохранения, скачать ZIP, сохранить медиа, обновить HTML/JSON и вернуть ответ Verstka.

Перед этой страницей лучше прочитать общий flow: ОбзорИнтеграция

Установка

Базовая установка:

bash
pip install verstka-sdk

Если хотите использовать готовые адаптеры для framework, установите extra для вашего framework:

bash
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 вашего сайта:

python
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 придет при сохранении публикации в редакторе.

Дополнительные настройки можно задать явно:

python
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:

html
<a
  href="/admin/verstka/edit?post=123"
  target="_blank"
>
  Редактировать в Verstka
</a>

Backend route проверяет права пользователя, загружает статью из CMS, получает editor_url через SDK и делает redirect на этот URL. Ниже пример в стиле FastAPI/Starlette; в другом framework верните redirect своим способом.

python
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.

python
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 в базу данных сайта.

python
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_idID материала из CMS.
metadataMetadata из callback.
vms_jsonJSON публикации с обновленными clientUrl.
vms_htmlHTML публикации с замененными временными 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"].

python
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Инструмент
FastAPIinstall_exception_handlers(app), build_callback_router(...)
Flaskregister_error_handlers(app), build_blueprint(...)
Djangobuild_callback_views(...), VerstkaExceptionMiddleware
DRFbuild_callback_views(...), verstka_exception_handler

Metadata

metadata - это необязательный JSON-объект, который сайт передает при открытии редактора. Verstka сохранит его в сессии и вернет в callback.

Metadata удобно использовать для данных, которые помогут сайту обработать сохранение:

  • ID пользователя в CMS;
  • ID раздела, бренда или проекта;
  • флаги окружения;
  • короткоживущий токен для дополнительной проверки прав;
  • данные для basic авторизации или bearer token
  • конфигурация Custom Containers.
python
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.

python
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:

python
metadata={
    "webhook_auth_user": "callback-user",
    "webhook_auth_password": "callback-password",
}

Bearer token:

python
metadata={
    "webhook_auth_user": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
}

Verstka добавит заголовок Authorization к исходящему callback. Подробнее: Callback Authorization.

Sync client

Если приложение не async, используйте VerstkaClient. Методы называются так же, но вызываются без await:

python
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(...)asyncAsync-версия обработки article_saved.
VerstkaClient.process_fonts_callback(...)syncПроверяет site_fonts_updated, скачивает fonts ZIP, сохраняет шрифты и manifests.
AsyncVerstkaClient.process_fonts_callback(...)asyncAsync-версия обработки site_fonts_updated.
LocalStorageAdaptersyncФайловая reference-реализация storage adapter.
LocalAsyncStorageAdapterasyncAsync 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.