Cluster · разработчикам
Редактор и типы SDK
Первый модуль · Справочник методов · Типы результатов
Установка одной командой
Нужен Python 3.12 или новее. Скачай установщик, затем выполни:
python3 cluster-sdk-setup.py ./my-module --name "My module"Для существующего проекта укажи его каталог и добавь --existing, чтобы подготовить окружение без шаблона модуля. Установщик создаёт .venv, ставит cluster-sdk с Pyright, подключает интерпретатор к настройкам редактора и создаёт основу модуля, если её ещё нет. На Windows команду можно выполнить через py -3 с установленным Python 3.12 или новее.
Существующие исходники и manifest.json не заменяются. Пользовательские параметры редактора сохраняются; пути интерпретатора обновляются с резервной копией JSON. Подробности и исключения для настроек с комментариями описаны в командах проекта.
Установщик использует прямую HTTPS-ссылку на конкретный wheel с проверкой SHA-256. Снятие проверки сертификата не требуется. Ключи сайта, Telegram и доступ к ядру для установки SDK не нужны.
После установки:
./my-module/.venv/bin/python -m cluster_sdk check ./my-module --types
./my-module/.venv/bin/python -m cluster_sdk pack ./my-moduleНа Windows интерпретатор находится в my-module\.venv\Scripts\python.exe.
Установка в готовое окружение
Если окружение уже создано и выбрано в редакторе:
python -m pip install "cluster-sdk[dev] @ https://cluster.ninepeaks.dev/developers/downloads/cluster_sdk-0.1.0-py3-none-any.whl"
python -m cluster_sdk init . --existingПакет устанавливается как cluster-sdk, импортируется как cluster_sdk. В нём есть публичные классы и конструкторы, рабочая клиентская реализация, объявления .pyi и команды подготовки, проверки и сборки проекта. Ядро, серверный транспорт, сессии, ключи и серверные проверки прав сюда не входят.
Старый пакет cluster-sdk-types содержал только объявления. Если он установлен в этом же окружении, удали его перед ручной установкой SDK:
python -m pip uninstall cluster-sdk-typesУстановщик делает эту замену сам внутри выбранного окружения проекта. Для SDK не нужны extraPaths на исходники Cluster или вручную скопированная папка .types. Неизменённые старые объявления в stubPath команда init сохраняет в резервной папке; изменённые пользователем объявления не удаляются.
SDK устанавливается в окружение разработки. В requirements пакета модуля добавляются его собственные зависимости PyPI; рабочий SDK уже предоставляет Cluster.
Настройка Pyright и Pylance
Создай pyrightconfig.json в корне папки с модулями:
{
"typeCheckingMode": "basic",
"pythonVersion": "3.12",
"reportMissingImports": "error",
"reportMissingModuleSource": "none"
}reportMissingImports продолжает показывать отсутствующие импорты. init создаёт конфигурацию автоматически. В готовом проекте можно сохранить свои параметры. Настраивать extraPaths на исходники Cluster не нужно.
Импорты в модуле
app и loader передаются загрузчиком Cluster. В локальном редакторе объяви их типы внутри TYPE_CHECKING:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from cluster_sdk import App, Event, loader
app: App
class Module:
@loader.command("hello", access="owner", source="both")
async def hello(self, ctx: Event) -> None:
text = "Привет." if ctx.language == "ru" else "Hello."
await ctx.reply(text)Редактор читает этот блок и даёт подсказки. Python при запуске его пропускает; from __future__ import annotations позволяет использовать типы в сигнатурах без выполнения этих импортов. Рабочие app и loader предоставляет Cluster.
Во внутренних файлах собственного модуля импортируй типы тем же способом:
from __future__ import annotations
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from cluster_sdk import App, Record
class Storage:
def __init__(self, app: App) -> None: self.app = app
@asynccontextmanager
async def record(self) -> AsyncIterator[Record]:
async with self.app.db.record("state", {}) as record: yield recordЕсли класс нужен во время выполнения, например Canvas для описания картинки, импорт размещается обычно: from cluster_sdk import Canvas. Публичные конструкторы доступны и в установленном SDK, и в среде Cluster.
Не создавай второй App вместо переданного app: запросы модуля привязаны к его текущему событию и транспорту. Не используй старые служебные импорты from app, from router и from resources в новых модулях.
Проверка
В выбранном окружении можно проверить типы отдельно:
.venv/bin/python -m pip install pyright
.venv/bin/pyright --pythonpath .venv/bin/python main.pyДля проверки поведения используй отдельную тестовую среду и проверку перед публикацией. Она загружает модуль с рабочим SDK внутри изоляции. Установка типов в локальный редактор не выдаёт модулю прав на Telegram, файлы или сеть.
Объявления строятся из тех же публичных контрактов, что справочник методов. Проверка покрытия сравнивает классы, аргументы и сигнатуры. В распространяемый SDK попадают публичные клиенты и конструкторы из явного списка; ядро и серверный ввод-вывод не включаются. При обновлении переустанови текущий SDK с --upgrade и повтори python -m cluster_sdk init ..
Точные типы своих данных
dict без параметров не описывает содержимое словаря. Редактор знает только сам контейнер и показывает dict[Unknown, Unknown]. Для однородных данных укажи оба типа: dict[str, int], dict[str, list[int]]. Для объекта с разными полями используй TypedDict.
db.get() и record.get() возвращают JSON: заранее неизвестно, что хранится под ключом. Аргумент default задаёт значение отсутствующей записи, но не проверяет уже сохранённую запись. Поэтому SDK не выдаёт тип значения по одному только default.
Опиши структуру и проверь её при чтении:
from __future__ import annotations
from collections.abc import Mapping
from typing import TYPE_CHECKING, TypedDict, cast
if TYPE_CHECKING: from cluster_sdk import App
class State(TypedDict):
count: int
users: list[int]
def state(value: object) -> State:
if not isinstance(value, Mapping): raise ValueError("Expected a state object")
fields = cast(Mapping[str, object], value)
count, raw_users = fields.get("count"), fields.get("users")
if type(count) is not int or not isinstance(raw_users, list):
raise ValueError("Invalid state fields")
users = cast(list[object], raw_users)
if any(type(user) is not int for user in users): raise ValueError("Invalid user ID")
return {"count": count, "users": cast(list[int], users)}
async def increment(app: App) -> State:
default: State = {"count": 0, "users": []}
document = app.document()
async with app.db.record("state", document.encode(default)) as record:
value = record.decode(state)
value["count"] += 1
record.set((), record.encode(value))
return valuedecode(decoder, path=()) передаёт копию значения проверяющей функции и сохраняет тип её результата. В этом примере value имеет тип State, value["count"]: int, а value["users"]: list[int]. Метод доступен у Document и наследующих его Record, ConfigView, Settings, Page. Можно проверить отдельное вложенное значение через path.
Ошибки проверяющей функции передаются без подмены. Проверка и изменение полученной копии не меняют документ: для записи нужен set(), а для отправки в ядро: save() либо успешный выход из контекстного менеджера записи. Если проверка завершилась ошибкой внутри async with, запись автоматически не сохраняется.
encode(value) проверяет JSON-совместимость собственного TypedDict и возвращает глубокую копию с типом JSON. Он не превращает произвольный объект в словарь и не приводит строки к числам. Для сохранения нетипичного объекта сначала явно опиши его представление в JSON.
Проверка isinstance(value, dict) уточняет только вид контейнера. Она не проверяет поля и вложенные списки. Один cast(State, value) также ничего не проверяет во время выполнения. В примере приведения применяются после проверки соответствующих значений; они не заменяют эту проверку.
Настройки проходят проверку по схеме модуля в ядре. Для них можно описать собственный TypedDict и уточнить тип в одном методе чтения. Произвольные данные из базы, файла или сети проверяй отдельно.
Пагинация и обработчики
Pages[T] сохраняет тип элементов, в том числе после переключения страницы:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING: from cluster_sdk import App
def page(app: App) -> list[int]:
pages = app.inline.pages([101, 102, 103], size=2)
return pages.next().currentЗдесь результат: list[int]. Для списка собственных TypedDict получится список того же типа. Строки клавиатуры содержат Button, слои сцены: LayerData, сведения о файле: FileInfo, задачи: TaskInfo. Их поля описаны в типах результатов.
Декораторы loader.command, handler, callback, action, function, signal, task, request, init, stop сохраняют аргументы и возвращаемый тип функции. Поддерживаются обе формы: @loader.callback и @loader.callback(...). loader.thread сохраняет аргументы синхронной функции, а результат нужно получить через await.
У методов app.bot.api и app.client.api результат имеет тип JSON; конструкторы types возвращают JSON-описания. Это динамические пространства имён: наличие имени и набор параметров проверяются по схеме Bot API или Telethon во время вызова. Типы редактора не подменяют эту проверку. Формы конкретных объектов и параметры доступны в справочнике Telegram.
Проверка неполных подсказок
Чтобы Unknown не оставался незаметным, добавь в pyrightconfig.json:
{
"reportMissingTypeArgument": "error",
"reportUnknownParameterType": "error",
"reportUnknownVariableType": "error",
"reportUnknownMemberType": "error",
"reportUnknownArgumentType": "error",
"reportUnknownLambdaType": "error"
}Эти параметры дополняют конфигурацию выше. Они находят пропущенные параметры контейнеров и места, в которых потерян тип. Не исправляй их заменой всего на Any: сначала уточни структуру данных, тип пустого списка или словаря и возвращаемый тип функции.
Event используется для команд, кнопок, задач и других событий. У задачи может не быть сообщения и чата. Перед использованием ctx.chat_id, ctx.sender_id или ctx.id напрямую проверь, что нужное поле не равно None. Методы ctx.reply() и ctx.edit() выполняют свою проверку контекста.
Устанавливаемый SDK включает уточнённые типы полей объектов и результатов, типизированную пагинацию и декораторы. Document.encode() и Document.decode() доступны в рабочем SDK. Полные сигнатуры, права и ошибки приведены в справочнике.