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.9.tar.gz (386.6 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.9-py3-none-any.whl (239.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: s_adapterkit-0.1.9.tar.gz
  • Upload date:
  • Size: 386.6 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.9.tar.gz
Algorithm Hash digest
SHA256 68d0b0c9fac2c3e86799435695d1638c34582ed187c962c04e514b6f83a742e6
MD5 8b21a78bdd27479d0dfba760ff41d2c3
BLAKE2b-256 bbc2c249dac13edb493e1f253add7e091b61876b74317bab5f214e714e1fd510

See more details on using hashes here.

File details

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

File metadata

  • Download URL: s_adapterkit-0.1.9-py3-none-any.whl
  • Upload date:
  • Size: 239.3 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.9-py3-none-any.whl
Algorithm Hash digest
SHA256 4efd8d85f17a20c977b01062c0f492e6e5a49c40c5f2dcb18083f4a4f3dea5db
MD5 e5d2f8fd2624559a78bce398dccbeafc
BLAKE2b-256 4af07dc422d12b8d3d78d12bbbefeb8c04677c0886c0b53b1b1d1f6f5d63fe4e

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

0.1.10

2 files

This release

0.1.9 This release

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