Skip to main content

adapterkit

Тонкий коннектор сетевых адаптеров поверх s-librarykit. adapterkit отвечает на один вопрос: «как подключить сетевой адаптер к приложению». Он даёт декларативный контракт плагина, реестр с автодискавери через entry-points и базовый фасад адаптера — а весь сетевой движок (transport/auth/retry/errmap/ pagination/sessions/antibot/browser) реэкспортирует из librarykit, не дублируя его. Пишется один раз, переиспользуется любым доменным пакетом.

librarykit   ← КОРЕНЬ: весь сетевой движок (transport/auth/retry/errmap/
                        pagination/sessions/antibot/browser)
   ▲
adapterkit   ← ЭТОТ КИТ: контракт NetworkAdapter + registry (entry-points) +
                        BaseAdapter + orchestration_api.
                        Остальное — тонкий реэкспорт-шим из librarykit.
   ▲
домен        ← конкретные адаптеры (endpoint-таблица + мапперы на сеть)

Зависимости направлены только внутрь: adapterkit зависит только от librarykit, но НЕ от домена и НЕ от clikit (онион-граф librarykit <- adapterkit <- clikit: clikit — слой ВЫШЕ, adapterkit его не импортит). Адаптеры кодируются против стабильных typing.Protocol из adapterkit.contract (структурный контракт, а не наследование от домена). Композиция конкретных реализаций — единственный composition root на приложение.

Установка

uv add s-adapterkit

Имя дистрибутива — s-adapterkit, имя для импорта — adapterkit. librarykit подтянется автоматически как транзитивная зависимость.

Опциональные extra:

uv add "s-adapterkit[browser]"   # Playwright — browser-login
uv add "s-adapterkit[antibot]"   # curl-cffi — JA3-impersonate
uv add "s-adapterkit[oauth]"     # authlib — OAuth2-flows

Быстрый старт

Описать адаптер декларативно и зарегистрировать его в реестре:

from adapterkit import BaseAdapter, Endpoint, register_adapter

class TwitterAdapter(BaseAdapter):
    api_version = 1
    endpoints = {
        "search": Endpoint(name="search", method="GET", path="/2/tweets/search/recent"),
    }

register_adapter("twitter", TwitterAdapter)

Либо отдать адаптер на автодискавери — объявить entry-point в своём pyproject.toml, и любой потребитель adapterkit подхватит его без явного импорта:

[project.entry-points."adapterkit.adapters"]
twitter = "my_package.adapter:TwitterAdapter"
from adapterkit import discover_adapters, get_adapter_class

discover_adapters()                       # загрузить все плагины из entry-points
cls = get_adapter_class("twitter")        # получить класс по имени сервиса

Карта модулей

Модуль Назначение Реализация
contract.py граничные Protocol (NetworkAdapter/Transport/Auth/ErrorMapper/Paginator/SessionStoreProtocol) + DTO (Endpoint/RequestSpec/SessionRef/Creds) + ADAPTER_API_VERSION/MIN_SUPPORTED_API_VERSION контракт коннектора
registry.py AdapterRegistry + автодискавери через entry-points adapterkit.adapters, ленивая загрузка, ручная регистрация код коннектора
base.py BaseAdapter (описание запроса build_request + исполнение execute) + ресурс-под-сервисы (ContentResource/CommentsResource/MetricsResource/SearchResource) — Stripe-стиль фасад код коннектора
throttle.py ядро само троттлит плагин по его метаданным: @ratelimit, ServiceThrottle, ManagedExecutor, шов ExecutionContext код коннектора
orchestration_api.py тонкий registry-driven API: onboard_all / health_check_all (без импортов домена) код коннектора
onboarding_contract.py онбординг/health-контракты (LoginMode/OnboardingProtocol/HealthProtocol, api_version 2) реэкспорт librarykit.protocols
errors.py единая иерархия ошибок реэкспорт librarykit.errors
retry.py header-driven RetryPolicy реэкспорт librarykit.retry
transport.py / client.py HttpxTransport + choke-point HttpClient реэкспорт librarykit.transport
auth.py TokenAuth/OAuth2Auth/CookieSessionAuth/BrowserLoginAuth реэкспорт librarykit.auth
errmap.py декларативная карта ответ → доменная ошибка реэкспорт librarykit.errmap
pagination.py CursorPaginator (offset/cursor/page) реэкспорт librarykit.pagination
sessions.py envelope-шифрованный SessionStore реэкспорт librarykit.sessions
antibot.py выбор транспорта Tier 0-4 (curl-cffi JA3 / CDP) ленивый реэкспорт librarykit.antibot
browser.py warm/cold-login (требует extra browser) ленивый реэкспорт librarykit.browser
endpoints.py EndpointRegistry — адреса скрытого API как данные, проба живости код коннектора
clientkit/ СБОРЩИК клиента по декларации сервиса (TOML): WhiteApiClient/HiddenApiClient, каталог имён сервисов (clientkit.identity), адреса-кандидаты поверх EndpointRegistry (clientkit._candidates) — extras client/client-hidden (переехал из отдельного кита clientkit, волна 5 #2710) код коннектора

Всё, что помечено «реэкспорт», — тонкий shim: единая реализация живёт в librarykit, adapterkit лишь предоставляет её под привычным именем. Собственный код коннектора — только contract/registry/base/throttle/orchestration_api.

Ядро само держит лимиты плагина

Плагин объявляет лимиты ОДНОЙ строкой метаданных и не пишет кода лимитов, ретраев и удержания сессии — очередь запросов и exponential backoff делает ядро:

from adapterkit import BaseAdapter, Endpoint, ratelimit

@ratelimit(calls=2, period=1)          # ← всё, что плагин пишет про лимиты
class ExampleAdapter(BaseAdapter):
    service = "example"
    endpoints = {"get_item": Endpoint("get_item", "GET", "/items/{item_id}",
                                      required_params=("item_id",))}

Полная инструкция (формы декларации, оси квот QuotaScope, инварианты, чек-лист для кодогенератора) — docs/PLUGIN_LIMITS.md. Адаптер БЕЗ объявленных лимитов работает ровно как раньше: исполнитель ядра не собирается, запросы уходят в клиент напрямую.

Плагины-способности: единая группа skillery.plugins

Кроме ПОЛНОГО адаптера сети кит несёт вторую, независимую ось подключения — capability: одна способность, один-два метода, ресурсы приходят аргументом ctx: ExecutionContext (плагин не читает окружение).

from adapterkit import PluginRegistry, PublisherProtocol

registry = PluginRegistry()
registry.capabilities()                              # имена БЕЗ импорта чужих пакетов
registry.plugins_supporting(PublisherProtocol)       # {capability_id: КЛАСС}, без инстанцирования

Формы (PluginProtocol / PublisherProtocol / TranscriberProtocol / IngestProtocol / AskProtocol / DeepResearchProtocol), версия-гейт PLUGIN_API_VERSION, мост AdapterCapability поверх существующего адаптера и чек-лист публикации — docs/PLUGIN_CONTRACT.md. Прежние группы (adapterkit.adapters, transcribe.channels) не переименованы и работают как раньше — новая ось добавлена рядом, а не вместо.

ask-способность в три строки + отбор по виду без импортов

Чат-навык объявляет ask наследованием (AskCapability) или фабрикой (ask_capability(fn, service=…)) — capability_id вида <service>_ask и версия контракта приезжают сами. По суффиксу имени реестр отбирает ask-способности из метаданных, за ноль ep.load(): список «кто умеет отвечать» не стоит импорта шести чат-пакетов с браузерными зависимостями.

registry.capabilities_supporting("ask")   # ['chatgpt_ask', 'gemini_ask', …] — 0 загрузок
registry.plugins_supporting("ask")        # классы; грузятся только подошедшие по имени

Conformance-набор adapterkit.testing.AskConformanceTests ловит то, чего не ловит форма: ask не реализован (унаследована заглушка), ask синхронный, лишний обязательный аргумент, имя вне соглашения — docs/ASK_CAPABILITY.md.

Эндпоинты скрытого API — данные, а не константы в коде

Операция несёт СПИСОК адресов-кандидатов (rpcid/URL) с приоритетом, формой пагинации, живостью и датой последней успешной проверки; вызов идёт по ИМЕНИ операции. Смерть rpcid лечится правкой декларации.

registry = EndpointRegistry.from_data(json.loads(path.read_text()))
candidate = registry.resolve("conversation_history")   # мёртвые адреса пропущены
probe = await probe_endpoint(registry.get("conversation_history"), call, session=state)

probe_endpoint судит только на подтверждённо живой сессии (иначе один разлогин пометит мёртвыми все адреса разом), мёртвый адрес даёт ENDPOINT_DEAD, а не «протухла сессия» — docs/ENDPOINT_ADVISOR.md.

Способность локально ИЛИ за сетью — решает реестр

Потребитель не знает, где живёт плагин: реестр по ОДНОМУ capability_id отдаёт либо локальный класс, либо remote-прокси с идентичным интерфейсом.

registry.bind_remote("vk_transcriber", transport, protocol=TranscriberProtocol)
# ↑ единственная строка сборки хоста; код вызова НЕ меняется:
await registry.get("vk_transcriber")().transcribe(ctx, "audio.mp3", lang="ru")

Дефолт — локальный (до bind_remote поведение реестра не отличается от прежнего). Лимиты применяются на стороне СЕРВИСА (иначе у каждого клиента своя квота), ключ идемпотентности едет в конверте вызова. Сериализация контекста, границы безопасности и что осталось до реального HTTP — docs/REMOTE_CAPABILITY.md.

Ядро не тянет EXTENSIONS (ленивые слои)

Антибот и браузер — тяжёлые опциональные слои (extras antibot/browser), поэтому import adapterkit их не загружает: реэкспорт идёт через module-level __getattr__ (PEP 562) и срабатывает на первом обращении к имени. Практически:

import adapterkit                      # librarykit.antibot / .browser НЕ загружены
adapterkit.HttpClient                  # ядро — как раньше
adapterkit.CurlCffiTransport           # ← вот здесь подгрузится librarykit.antibot
from adapterkit.browser import warm_or_autologin   # ← и здесь librarykit.browser

Публичный API не изменился: from adapterkit import CurlCffiTransport, from adapterkit.antibot import ..., from adapterkit.browser import ..., from adapterkit import * и dir(adapterkit) работают идентично. Инвариант закреплён fitness-тестами в tests/test_lazy_extensions.py (замер sys.modules в отдельном интерпретаторе).

Архитектурные гейты

Канон держится инструментом, а не договорённостью. Онион-граф, ленивая граница ЯДРО/РАСШИРЕНИЯ и отсутствие скрытых зависимостей проверяются одной командой:

uv run --extra dev lint-imports

Базовый слой — зрелый import-linter; два измерения, которых у него нет, добавлены его же механизмом плагинов (adapterkit.gates.contracts):

  • eager_forbidden — ядро не поднимает librarykit.browser / librarykit.antibot в момент импорта. Ленивый доступ (импорт внутри функции, PEP 562 __getattr__, if TYPE_CHECKING:) нарушением не считается — встроенный forbidden этого не различает;
  • declared_dependencies — всё импортируемое объявлено в pyproject.toml (dependencies либо любой extra).

Рядом — conformance-наборы, которые чужой репозиторий подключает тремя строками: AdapterContractTests (контракт NetworkAdapter) и PluginConformanceTests (контракт способности: capability_id = имя entry-point, ctx первым аргументом, лимиты метаданными, никакой инфраструктуры внутри плагина).

Полное описание, инструкция «как подключить у себя» и таблица «сработало — что делать» — docs/ARCHITECTURE_GATES.md.

Новый навык за одну команду

python -m adapterkit new-skill myskill --capability publish

Порождает пакет, который уже соблюдает канон: три слоя луковицы на каждую способность (mechanics/ атомы → use_cases/ сценарий → capabilities/ граница плагина), ctx-аргумент, лимиты через @ratelimit, entry-point в группе skillery.plugins под тем же именем, что capability_id, шесть гейтов в pyproject.toml и готовый conformance-тест. Сгенерированный навык проходит pytest, ruff и lint-imports без единой правки — это закреплено тестами скаффолда.

Способности: publish, transcribe, ingest, ask, deep_research. Зачем слои и какие гейты их держат (с живым примером VK-транскрибации) — docs/ONION_LAYERS.md.

Разработка

uv sync --extra dev
uv run --extra dev pytest -q
uv run --extra dev ruff check adapterkit
uv run --extra dev lint-imports

librarykit тянется из публичной группы семейство китов. Для локальной правки кита временно укажите path-источник в [tool.uv.sources] (см. комментарий в pyproject.toml) и выполните uv lock --upgrade.

Лицензия

MIT © 2026 Dmitry.

Metadata

Release files for s-adapterkit 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for s-adapterkit 0.2.0
File Size Uploaded
s_adapterkit-0.2.0.tar.gz 573.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for s-adapterkit 0.2.0
File Interpreter ABI Platform
s_adapterkit-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 861.4 kB

Release files / s_adapterkit-0.2.0.tar.gz

Download URL s_adapterkit-0.2.0.tar.gz
Size 573.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b88e946e11a89d0bfe1fed1f5097892f970d5fe3b5fbd64bf2c67b0510929a9e
BLAKE2b-256 checksum
How to use checksums
fdcb790bc9ff76824e9f49ebb28e36facb072fd2e73c84ba1341e89e1e728aff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / s_adapterkit-0.2.0-py3-none-any.whl

Download URL s_adapterkit-0.2.0-py3-none-any.whl
Size 287.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c1ebe8f9a7315c0cb35aa6961a51db6f1dda8b5acbc75c0276b477a30841b5ca
BLAKE2b-256 checksum
How to use checksums
0e1d9011b352fbf677fcdb0fe38b8d26c732606f2e01fd2ebf73a5ed77e463c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.14

2 release files

0.1.13

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page