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

Всё, что помечено «реэкспорт», — тонкий 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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

s_adapterkit-0.1.11.tar.gz (398.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

s_adapterkit-0.1.11-py3-none-any.whl (245.9 kB view details)

Uploaded Python 3

File details

Details for the file s_adapterkit-0.1.11.tar.gz.

File metadata

  • Download URL: s_adapterkit-0.1.11.tar.gz
  • Upload date:
  • Size: 398.1 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for s_adapterkit-0.1.11.tar.gz
Algorithm Hash digest
SHA256 bb7466d49ffcda25835c6de93ce1d10a2753749dc01955c4f6c2355a757e00ec
MD5 da71dfa7be637b08eb217e5136615ba4
BLAKE2b-256 09d2fac14cf71598527f9a7e50ff208239d79fa7686289c1f52a6de13069e520

See more details on using hashes here.

File details

Details for the file s_adapterkit-0.1.11-py3-none-any.whl.

File metadata

  • Download URL: s_adapterkit-0.1.11-py3-none-any.whl
  • Upload date:
  • Size: 245.9 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for s_adapterkit-0.1.11-py3-none-any.whl
Algorithm Hash digest
SHA256 e67ebbc56427bdec52576d9ffdc934dafccb6a4fd75c9d097a59afeede35064b
MD5 dd1243fd1e8242539c1f5aa9abd9cf93
BLAKE2b-256 0d351ab5477f77b9ffeb233fd6266703910df39e1c487b3f3654d4645ba6b5f4

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.15

2 files

0.1.14

2 files

0.1.13

2 files

0.1.12

2 files

This release

0.1.11 This release

2 files

0.1.10

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 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