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.10.tar.gz (396.0 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.10-py3-none-any.whl (245.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: s_adapterkit-0.1.10.tar.gz
  • Upload date:
  • Size: 396.0 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.10.tar.gz
Algorithm Hash digest
SHA256 7226cf99f6f93cd0ce37e13c6803e7e6d46bf0808235f93b25d18730aa953c74
MD5 22a92ccb1502f6740fd69e1ba430ead5
BLAKE2b-256 1ce3c669f0ffb27ca9eae638578507aa7f43d343e92c8a60cbf7d1878b4b01fe

See more details on using hashes here.

File details

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

File metadata

  • Download URL: s_adapterkit-0.1.10-py3-none-any.whl
  • Upload date:
  • Size: 245.5 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.10-py3-none-any.whl
Algorithm Hash digest
SHA256 4e8d067206f4051c6dc2cae59198875cc56428b80c465eecb8f8aa9841a31359
MD5 744bf31a87972092ec4fa669a9b251de
BLAKE2b-256 2e0641a6160af592dd0b5e6cfdca16d0c638baa095da002b6c27a9ef88efe84c

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

0.1.11

2 files

This release

0.1.10 This release

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