s-adapterkit 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. s_adapterkit-0.1.1/.gitignore +20 -0
  2. s_adapterkit-0.1.1/AGENTS.md +38 -0
  3. s_adapterkit-0.1.1/BACKLOG-nlm-migration.md +155 -0
  4. s_adapterkit-0.1.1/LICENSE +21 -0
  5. s_adapterkit-0.1.1/PKG-INFO +80 -0
  6. s_adapterkit-0.1.1/README.md +56 -0
  7. s_adapterkit-0.1.1/adapterkit/__init__.py +293 -0
  8. s_adapterkit-0.1.1/adapterkit/antibot.py +27 -0
  9. s_adapterkit-0.1.1/adapterkit/auth.py +42 -0
  10. s_adapterkit-0.1.1/adapterkit/base.py +337 -0
  11. s_adapterkit-0.1.1/adapterkit/browser.py +57 -0
  12. s_adapterkit-0.1.1/adapterkit/client.py +13 -0
  13. s_adapterkit-0.1.1/adapterkit/contract.py +170 -0
  14. s_adapterkit-0.1.1/adapterkit/errmap.py +32 -0
  15. s_adapterkit-0.1.1/adapterkit/errors.py +46 -0
  16. s_adapterkit-0.1.1/adapterkit/onboarding_contract.py +38 -0
  17. s_adapterkit-0.1.1/adapterkit/orchestration_api.py +241 -0
  18. s_adapterkit-0.1.1/adapterkit/pagination.py +31 -0
  19. s_adapterkit-0.1.1/adapterkit/registry.py +394 -0
  20. s_adapterkit-0.1.1/adapterkit/retry.py +23 -0
  21. s_adapterkit-0.1.1/adapterkit/sessions.py +86 -0
  22. s_adapterkit-0.1.1/adapterkit/testing/__init__.py +45 -0
  23. s_adapterkit-0.1.1/adapterkit/testing/contract.py +416 -0
  24. s_adapterkit-0.1.1/adapterkit/transport.py +13 -0
  25. s_adapterkit-0.1.1/pyproject.toml +66 -0
  26. s_adapterkit-0.1.1/tests/conftest.py +70 -0
  27. s_adapterkit-0.1.1/tests/test_antibot.py +311 -0
  28. s_adapterkit-0.1.1/tests/test_auth.py +329 -0
  29. s_adapterkit-0.1.1/tests/test_base.py +431 -0
  30. s_adapterkit-0.1.1/tests/test_browser.py +424 -0
  31. s_adapterkit-0.1.1/tests/test_client.py +333 -0
  32. s_adapterkit-0.1.1/tests/test_errmap.py +284 -0
  33. s_adapterkit-0.1.1/tests/test_orchestration_api.py +325 -0
  34. s_adapterkit-0.1.1/tests/test_pagination.py +410 -0
  35. s_adapterkit-0.1.1/tests/test_registry.py +400 -0
  36. s_adapterkit-0.1.1/tests/test_sessions.py +367 -0
  37. s_adapterkit-0.1.1/tests/test_testing_contract.py +141 -0
@@ -0,0 +1,20 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+
9
+ # venv / tooling
10
+ .venv/
11
+ .ruff_cache/
12
+ .pytest_cache/
13
+ .mypy_cache/
14
+
15
+ # uv
16
+ uv.lock
17
+
18
+ # OS
19
+ .DS_Store
20
+ Thumbs.db
@@ -0,0 +1,38 @@
1
+ # AGENTS.md — adapterkit
2
+
3
+ > Контекст для AI-ассистентов (Claude Code, ChatGPT, Cursor и т.п.), работающих
4
+ > над этим проектом.
5
+
6
+ ## Что это
7
+
8
+ Переиспользуемый SDK сетевых адаптеров: transport/auth/retry/errmap/pagination/sessions/browser/antibot, контракт плагинов; цель — onboarding+health как публичный API
9
+
10
+ ## Atlas
11
+
12
+ Проект зарегистрирован в Atlas-БД (NP-005). Карточка:
13
+
14
+ ```sh
15
+ atlas projects get adapterkit
16
+ ```
17
+
18
+ Любые изменения метаданных (приоритет, статус, теги) — через atlas CLI:
19
+
20
+ - `atlas projects update adapterkit --priority P0` — поменять приоритет
21
+ - `atlas add-tags adapterkit -t domain:<slug>` — добавить тег
22
+ - `atlas projects move adapterkit --to-type <type>` — конвертировать тип
23
+
24
+ ## Тип / Статус (на момент создания)
25
+
26
+ - type=`shared-infrastructure`, status=`experiment`, priority=`P0`
27
+
28
+ ## Правила работы
29
+
30
+ - Все исходные тексты, документы, код проекта — в этом репо.
31
+ - Чувствительные данные (`.env`, токены, ключи) — игнорируются `.gitignore`.
32
+ - AI-ассистенту разрешено: читать, генерировать, редактировать в этом репо.
33
+
34
+ ## Канонические команды
35
+
36
+ - `atlas projects get adapterkit` — карточка проекта
37
+ - `atlas pm-tasks list --project adapterkit` — задачи проекта (когда W7
38
+ волна будет реализована)
@@ -0,0 +1,155 @@
1
+ # BACKLOG — миграция NotebookLM на clikit + adapterkit (мультипротокол)
2
+
3
+ > SSOT для реализующей сессии. Эпики/задачи зеркалируются в Atlas
4
+ > (`atlas epic list --project adapterkit`, `atlas task list --project adapterkit`).
5
+ > Этот файл — глубина (файл:строка, подходы, решения), Atlas — трекинг.
6
+ >
7
+ > Источник: brainstorming-сессия 2026-06-22 (после фикса авто-refresh сессии
8
+ > notebooklm, ветка `fix/notebooklm-auto-headless-refresh` в репо notebooklm).
9
+
10
+ ## Цель
11
+
12
+ Сделать `nlm-tools` (обёртка над `notebooklm-py`) переиспользуемым
13
+ **adapterkit-адаптером**, подключаемым к другим проектам через
14
+ `AdapterRegistry`/entry-points, с единым `nlm` CLI на clikit. Попутно —
15
+ **расширить сам adapterkit до мультипротокольности** (REST/RPC/WS/др.), чтобы
16
+ NotebookLM-RPC и будущие WS-сервисы (MAX/Telegram) были first-class.
17
+
18
+ ## Решения пользователя (зафиксированы в brainstorming)
19
+
20
+ 1. **Форма адаптера** — НЕ duck-typed обход, а **расширить `BaseAdapter`/транспорт
21
+ adapterkit под мультипротокол** (REST/RPC/WS и др.) как first-class.
22
+ 2. **Канал подключения** — universal adapterkit entry-point (`adapterkit.adapters`),
23
+ переиспользуем в любом проекте; bublictr-мост — отложен (только если NLM реально
24
+ понадобится в кросспосте, потребует PR в bublictr core: `NetworkId.NOTEBOOKLM` + SP-3).
25
+ 3. **SessionStore** — интегрировать **сразу** (envelope-шифрование + единое
26
+ мультиаккаунт-хранилище), несмотря на конфликт владения файлом.
27
+ 4. **CLI Фаза 1** — готовые сценарии (sources/youtube/ask/transcripts/session) **+**
28
+ notebooks CRUD.
29
+
30
+ ## Ключевые факты из разбора кода (на чём строится дизайн)
31
+
32
+ - **adapterkit НЕ обязан владеть транспортом.** `BaseAdapter` зависит от
33
+ структурного `HttpClientLike` Protocol (`adapterkit/base.py:44-80,110-117`),
34
+ `_request` переопределяем (escape-hatch, base.py:18-22,103-104). Реестр —
35
+ структурный `NetworkAdapter` Protocol (`adapterkit/contract.py:284-338`),
36
+ наследование НЕ требуется. ENTRY_POINT_GROUP = `adapterkit.adapters`
37
+ (`adapterkit/registry.py:41`), ленивый `ep.load()` + ручной `register_adapter`
38
+ (registry.py:71-82,155-165).
39
+ - **`notebooklm-py` НАТИВНО async** (`.../notebooklm/client.py:74` "Async client",
40
+ свой httpx + RPC + пулы client.py:118-120). `await client.notebooks.list()`,
41
+ `async __aenter__/__aexit__` (455/472), `async refresh_auth()` (827),
42
+ `from_storage(path,profile,keepalive)` (719-733). → **`asyncio.to_thread` НЕ нужен**
43
+ (в отличие от RuTube, который оборачивает SYNC-клиент).
44
+ - **`notebooklm-py` сам владеет профилем/сессией:** `from_storage(profile=...)`,
45
+ путь `$NOTEBOOKLM_HOME/storage_state.json` или `~/.notebooklm`
46
+ (`_auth/cookies.py:266,326`); account-метаданные ВНУТРИ storage_state.json под
47
+ ключом `notebooklm` (`_auth/account.py:204,224,269`). Есть seam'ы
48
+ `cookie_saver`/`cookie_rotator` (client.py:332-333) — точка перехвата записи.
49
+ - **Эталон оборачивания чужого клиента** — `RuTubeAdapter` в bublictr: duck-typed,
50
+ клиент внутри, adapterkit HttpClient только для fallback-пинга
51
+ (`bublictr/.../rutube/__init__.py:167-177,553-557`).
52
+ - **Конфликт SessionStore ↔ notebooklm-py:** `SessionStore` шифрует (envelope,
53
+ `BUBLICTR_*` KEK), а notebooklm-py сам перезаписывает plaintext
54
+ `storage_state.json` в keepalive/rotate (`_auth/keepalive.py`). Два владельца
55
+ файла — нужно примирить через seam (см. E2).
56
+ - **clikit:** `build_root_app(brand=...)`, async-команды, `emit_data` json-дефолт,
57
+ `NlmConfig(AppConfig)`. **НЕ** использовать `clikit.transport.HttpClient`
58
+ (`clikit/transport.py:128` ждёт `httpx.AsyncClient`, а `NotebookLMClient` им не
59
+ является) и **НЕ** `clikit.SecretStore` (сессия — browser storage_state, не JWT).
60
+
61
+ ## Эпики и задачи
62
+
63
+ ### EPIC E1 — Мультипротокольный транспорт adapterkit (RPC/WS first-class)
64
+ **Цель:** транспорт-слой adapterkit перестаёт быть только httpx-REST; RPC и WS —
65
+ first-class, чтобы NotebookLM-RPC и будущие WS-сервисы строились на нём.
66
+ Текущее: `Transport` Protocol + `HttpxTransport` (request→`httpx.Response`,
67
+ transport.py), curl-cffi/CDP (antibot.py).
68
+
69
+ - **E1.T1 — Stream/протокол-абстракция для WS.** `Transport.request` (один
70
+ req→resp) не годится для WS (persistent send/recv). Ввести `StreamTransport`
71
+ Protocol (connect/send/recv/close) рядом с `Transport`. Файлы: `contract.py`
72
+ (Transport), новый stream-контракт.
73
+ - **E1.T2 — Codec-слой для RPC.** Обобщить `HttpClient.get_json/post_json`
74
+ (client.py:40) до pluggable codec (request/response сериализация ≠ JSON; пример —
75
+ NotebookLM `batchexecute`). Endpoint получает codec.
76
+ - **E1.T3 — Протокол-агностичный результат BaseAdapter.** Сейчас
77
+ `HttpClientLike.request` обязан вернуть `httpx.Response` (base.py:45). Ввести
78
+ абстрактный результат или параллельные choke-points (`RpcClient`/`WsClient`)
79
+ так, чтобы `BaseAdapter` не был привязан к httpx.
80
+ - **E1.T4 — Conformance/contract-тесты** для новых транспортов (`testing/contract.py`).
81
+ - **E1.T5 (опц) — WS reference-транспорт** (websockets/httpx-ws) под `StreamTransport` —
82
+ задел под MAX/Telegram. YAGNI-проверить: делать только если WS-сервис на горизонте.
83
+
84
+ ### EPIC E2 — SessionStore ↔ внешний клиент (owner reconciliation)
85
+ **Цель:** SessionStore хранит/шифрует сессии мультиаккаунт, НЕ конфликтуя с тем,
86
+ что notebooklm-py сам пишет storage_state.json.
87
+
88
+ - **E2.T1 — Перехват записи через seam.** Использовать `cookie_saver`/`cookie_rotator`
89
+ (notebooklm `client.py:332-333`), чтобы момент сохранения сессии шёл через
90
+ SessionStore (вариант C2: шифровать после записи; расшифровывать во временный
91
+ path перед open). Альтернатива C1 (SessionStore как индекс путей, plaintext) —
92
+ фиксировать как fallback.
93
+ - **E2.T2 — Нейтральный KEK-namespace.** Вынести env KEK из `BUBLICTR_*` в
94
+ `ADAPTERKIT_*`/конфиг (`adapterkit/sessions.py`), чтобы адаптер не тянул
95
+ bublictr-namespace.
96
+ - **E2.T3 — Маппинг (profile/social/account_id) ↔ profile notebooklm-py.**
97
+ account_id схлопывается в profile (один профиль = один аккаунт, метаданные
98
+ внутри storage_state, `_auth/account.py:204`).
99
+ - **E2.T4 — Тесты** SessionStore с внешним владельцем файла (keepalive/rotate).
100
+
101
+ ### EPIC E3 — NlmAdapter (первый RPC-адаптер на новом фундаменте)
102
+ **Цель:** подключаемый адаптер NotebookLM в namespace adapterkit.
103
+
104
+ - **E3.T1 — NlmAdapter под `NetworkAdapter` Protocol** (contract.py:284):
105
+ `api_version=1` (class-attr int, НЕ property), `service='notebooklm'`,
106
+ `capabilities`, `health_check`, `__aenter__/__aexit__`. Внутри — `open_client`→
107
+ `NotebookLMClient` как transport-слой.
108
+ - **E3.T2 — Порты content** (notebooks/sources/ask/transcripts) делегируют в async
109
+ sub-clients напрямую (БЕЗ `to_thread`). Перенести lib `nlm_tools/{notebooks,
110
+ sources,ask,transcripts}.py`.
111
+ - **E3.T3 — entry-point** в pyproject: `[project.entry-points."adapterkit.adapters"]
112
+ notebooklm = "nlm_tools.adapter:NlmAdapter"`.
113
+ - **E3.T4 — AdapterContractTests** с переопределённым `make_adapter`
114
+ (`adapterkit/testing/contract.py:263-285` — дефолт `cls(StubHttpClient())` не
115
+ сработает).
116
+ - **E3.T5 — Перенести session-слой** (session_refresh/nlm_refresh/nlm_keepalive из
117
+ репо notebooklm) внутрь адаптера; `map_error` → adapterkit errmap/доменные ошибки.
118
+ - **E3.T6 — Target-гранулярность.** Решить единицу: один аккаунт NLM (OWN_PROFILE)
119
+ vs каждый notebook = Target. Для standalone хватит одного; для bublictr-кросспоста
120
+ Target обязателен.
121
+
122
+ ### EPIC E4 — clikit `nlm` CLI (сценарии + CRUD + session)
123
+ **Цель:** единый `nlm` CLI вместо разрозненных `scripts/nlm_*.py`, с профилями и
124
+ единым sh-xray egress (закрывает остаточный момент egress-рассинхрона из фикса).
125
+
126
+ - **E4.T1 — Каркас:** `build_root_app(brand='nlm')`, `NlmConfig(AppConfig)`,
127
+ `--json` дефолт (`emit_data`). Без `clikit.transport.HttpClient`/`SecretStore`.
128
+ - **E4.T2 — notebooks CRUD** (create/list/delete/use) — lib `notebooks.py` готов,
129
+ команд-скриптов не было.
130
+ - **E4.T3 — source/ask/transcripts/generate** — перенести `scripts/nlm_add_sources,
131
+ nlm_add_youtube, nlm_ask_series, nlm_export_transcripts` в подкоманды.
132
+ - **E4.T4 — session-подкоманды** (login/refresh/keepalive/check) поверх
133
+ session-слоя; профиль `--profile/-P` → `NLM_PROFILE` → `open_client(profile=...)`.
134
+ - **E4.T5 — keepalive entrypoint** для Task Scheduler: решить — отдельный тонкий
135
+ entrypoint (без typer-overhead) vs `nlm session keepalive`. Логика
136
+ `session_refresh.py` переезжает без изменений.
137
+
138
+ ## Порядок / зависимости
139
+
140
+ ```
141
+ E1 (мультипротокол) ─┐
142
+ E2 (SessionStore) ─┴─► E3 (NlmAdapter) ─► E4 (clikit nlm CLI)
143
+ ```
144
+
145
+ E1 и E2 можно вести параллельно. E3 зависит от E1+E2. E4 зависит от E3.
146
+ Минимальный путь к рабочему `nlm` без полного E1: можно начать E3 в духе
147
+ duck-typed (RuTube-паттерн) и наполнять мультипротокол E1 итеративно — но
148
+ пользователь выбрал first-class мультипротокол, поэтому E1 — фундамент.
149
+
150
+ ## Что уже готово (фикс сессии, репо notebooklm)
151
+
152
+ Ветка `fix/notebooklm-auto-headless-refresh` (commit ef01ae2): `session_refresh.py`
153
+ (чистая логика, 25 тестов), `nlm_refresh.py` (headless re-login patchright→cdp),
154
+ `nlm_keepalive.py` (poke+heal), `open_client` (боевая ~/.notebooklm + REFRESH_CMD).
155
+ Этот session-слой переезжает в адаптер (E3.T5) и CLI (E4.T4) почти без изменений.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dmitry
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: s-adapterkit
3
+ Version: 0.1.1
4
+ Summary: Переиспользуемый SDK сетевых адаптеров: Transport/Auth/RetryPolicy/HttpClient, ErrorMap, пагинация, браузер/антибот-транспорты, SessionStore, BaseAdapter + контракт плагинов. Тонкий коннектор поверх librarykit.
5
+ Author: Dmitry
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: s-clikit
10
+ Requires-Dist: s-librarykit
11
+ Provides-Extra: antibot
12
+ Requires-Dist: curl-cffi>=0.7; extra == 'antibot'
13
+ Provides-Extra: browser
14
+ Requires-Dist: playwright>=1.40; extra == 'browser'
15
+ Provides-Extra: dev
16
+ Requires-Dist: hypothesis>=6; extra == 'dev'
17
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
18
+ Requires-Dist: pytest>=8.3; extra == 'dev'
19
+ Requires-Dist: respx>=0.21; extra == 'dev'
20
+ Requires-Dist: ruff>=0.8; extra == 'dev'
21
+ Provides-Extra: oauth
22
+ Requires-Dist: authlib>=1.3; extra == 'oauth'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # adapterkit
26
+
27
+ Переиспользуемый **SDK сетевых адаптеров** для соцсетей и любых REST/гибридных
28
+ сервисов. Пишется один раз, потребляется всеми адаптерами `bublictr` и
29
+ `reverse-factory` — чтобы HTTP/retry/auth/session/antibot не переписывались под
30
+ каждую сеть заново.
31
+
32
+ adapterkit **строится поверх `clikit`** и переиспользует его примитивы
33
+ (`CliError`-иерархию, `RetryPolicy`, `AppPaths`, `SecretStore`, transport-разбор
34
+ ошибок), не дублируя их.
35
+
36
+ ## Слои
37
+
38
+ ```
39
+ clikit CLI-методика + примитивы (errors, retry, paths, SecretStore, transport)
40
+ ▲ depends-on
41
+ adapterkit сетевой SDK: Transport/Auth/HttpClient/ErrorMap/Pagination/SessionStore/BaseAdapter/contract
42
+ ▲ depends-on
43
+ bublictr / reverse-factory домен/фабрика: endpoint-table + чистые мапперы + capabilities на сеть
44
+ ```
45
+
46
+ Принцип зависимостей — **только внутрь**. Адаптеры кодируются против стабильных
47
+ `typing.Protocol` из `adapterkit.contract` (структурный контракт, не наследование
48
+ от домена). Композиция конкретных реализаций — единственный composition root на
49
+ приложение.
50
+
51
+ ## Карта модулей
52
+
53
+ | Модуль | Назначение | Статус |
54
+ |--------|-----------|--------|
55
+ | `errors.py` | `AdapterError` + `TransportError/NotFound/Blocked/ServerError` поверх `clikit.errors`; `RETRYABLE` | ✅ spine |
56
+ | `contract.py` | граничные `Protocol` (`Transport/Auth/ErrorMapper/Paginator/SessionStoreProtocol/NetworkAdapter`) + DTO (`Endpoint/PaginationMode/SessionRef/Creds`) + `ADAPTER_API_VERSION` | ✅ spine |
57
+ | `retry.py` | `RetryPolicy` (header-driven: `Retry-After` / rate-limit) поверх `stamina`; `DEFAULT_RETRY` | ✅ spine |
58
+ | `transport.py` | `HttpxTransport(Transport)` — обёртка `httpx.AsyncClient` + `RetryPolicy`, per-instance | ✅ spine |
59
+ | `auth.py` | `TokenAuth / OAuth2Auth / CookieSessionAuth / BrowserLoginAuth` | ⏳ позже |
60
+ | `client.py` | `HttpClient` — единственный choke-point (owns Transport+Auth+RetryPolicy) | ⏳ позже |
61
+ | `errmap.py` | декларативная таблица `{status\|code\|body-predicate -> ErrorSubclass}` | ⏳ позже |
62
+ | `pagination.py` | `paginate(...)` обёртка: offset \| cursor \| page | ⏳ позже |
63
+ | `browser.py` | warm/cold-login, self-heal (transport CDP/Playwright) | ⏳ позже |
64
+ | `antibot.py` | Tier 0-4: выбор транспорта (curl-cffi JA3, real Edge CDP) | ⏳ позже |
65
+ | `sessions.py` | `SessionStore` — единое файловое хранилище сессий + индекс | ⏳ позже |
66
+ | `base.py` | `BaseAdapter` + `Resource` sub-services (Stripe-фасад) | ⏳ позже |
67
+ | `registry.py` | discovery через entry-points `adapterkit.adapters`, ленивая загрузка | ⏳ позже |
68
+ | `testing/contract.py` | параметризованный conformance-сьют | ⏳ позже |
69
+
70
+ Текущая ревизия — **фундамент (scaffold + spine)**: `errors`, `contract`,
71
+ `retry`, `transport`. Остальные модули реализуются в фазах интеграции; их
72
+ интерфейсы уже зафиксированы как `Protocol` в `contract.py`.
73
+
74
+ ## Установка (dev)
75
+
76
+ ```bash
77
+ uv sync --extra dev
78
+ ```
79
+
80
+ `clikit` подтягивается как editable path-зависимость (`../clikit`).
@@ -0,0 +1,56 @@
1
+ # adapterkit
2
+
3
+ Переиспользуемый **SDK сетевых адаптеров** для соцсетей и любых REST/гибридных
4
+ сервисов. Пишется один раз, потребляется всеми адаптерами `bublictr` и
5
+ `reverse-factory` — чтобы HTTP/retry/auth/session/antibot не переписывались под
6
+ каждую сеть заново.
7
+
8
+ adapterkit **строится поверх `clikit`** и переиспользует его примитивы
9
+ (`CliError`-иерархию, `RetryPolicy`, `AppPaths`, `SecretStore`, transport-разбор
10
+ ошибок), не дублируя их.
11
+
12
+ ## Слои
13
+
14
+ ```
15
+ clikit CLI-методика + примитивы (errors, retry, paths, SecretStore, transport)
16
+ ▲ depends-on
17
+ adapterkit сетевой SDK: Transport/Auth/HttpClient/ErrorMap/Pagination/SessionStore/BaseAdapter/contract
18
+ ▲ depends-on
19
+ bublictr / reverse-factory домен/фабрика: endpoint-table + чистые мапперы + capabilities на сеть
20
+ ```
21
+
22
+ Принцип зависимостей — **только внутрь**. Адаптеры кодируются против стабильных
23
+ `typing.Protocol` из `adapterkit.contract` (структурный контракт, не наследование
24
+ от домена). Композиция конкретных реализаций — единственный composition root на
25
+ приложение.
26
+
27
+ ## Карта модулей
28
+
29
+ | Модуль | Назначение | Статус |
30
+ |--------|-----------|--------|
31
+ | `errors.py` | `AdapterError` + `TransportError/NotFound/Blocked/ServerError` поверх `clikit.errors`; `RETRYABLE` | ✅ spine |
32
+ | `contract.py` | граничные `Protocol` (`Transport/Auth/ErrorMapper/Paginator/SessionStoreProtocol/NetworkAdapter`) + DTO (`Endpoint/PaginationMode/SessionRef/Creds`) + `ADAPTER_API_VERSION` | ✅ spine |
33
+ | `retry.py` | `RetryPolicy` (header-driven: `Retry-After` / rate-limit) поверх `stamina`; `DEFAULT_RETRY` | ✅ spine |
34
+ | `transport.py` | `HttpxTransport(Transport)` — обёртка `httpx.AsyncClient` + `RetryPolicy`, per-instance | ✅ spine |
35
+ | `auth.py` | `TokenAuth / OAuth2Auth / CookieSessionAuth / BrowserLoginAuth` | ⏳ позже |
36
+ | `client.py` | `HttpClient` — единственный choke-point (owns Transport+Auth+RetryPolicy) | ⏳ позже |
37
+ | `errmap.py` | декларативная таблица `{status\|code\|body-predicate -> ErrorSubclass}` | ⏳ позже |
38
+ | `pagination.py` | `paginate(...)` обёртка: offset \| cursor \| page | ⏳ позже |
39
+ | `browser.py` | warm/cold-login, self-heal (transport CDP/Playwright) | ⏳ позже |
40
+ | `antibot.py` | Tier 0-4: выбор транспорта (curl-cffi JA3, real Edge CDP) | ⏳ позже |
41
+ | `sessions.py` | `SessionStore` — единое файловое хранилище сессий + индекс | ⏳ позже |
42
+ | `base.py` | `BaseAdapter` + `Resource` sub-services (Stripe-фасад) | ⏳ позже |
43
+ | `registry.py` | discovery через entry-points `adapterkit.adapters`, ленивая загрузка | ⏳ позже |
44
+ | `testing/contract.py` | параметризованный conformance-сьют | ⏳ позже |
45
+
46
+ Текущая ревизия — **фундамент (scaffold + spine)**: `errors`, `contract`,
47
+ `retry`, `transport`. Остальные модули реализуются в фазах интеграции; их
48
+ интерфейсы уже зафиксированы как `Protocol` в `contract.py`.
49
+
50
+ ## Установка (dev)
51
+
52
+ ```bash
53
+ uv sync --extra dev
54
+ ```
55
+
56
+ `clikit` подтягивается как editable path-зависимость (`../clikit`).
@@ -0,0 +1,293 @@
1
+ """adapterkit — переиспользуемый SDK сетевых адаптеров (поверх clikit).
2
+
3
+ Сетевой слой, который пишется один раз и потребляется всеми адаптерами
4
+ `bublictr` и `reverse-factory`: транспорт + retry + auth + session + antibot,
5
+ декларативный контракт плагинов (`typing.Protocol` + `ADAPTER_API_VERSION`) и
6
+ базовый фасад адаптера. adapterkit зависит ТОЛЬКО от `clikit` (и стандартных
7
+ сетевых библиотек) — без импортов домена, чтобы reverse-factory брал тот же SDK.
8
+
9
+ Публичный API сгруппирован по слоям (как в `clikit.__init__`): ошибки → контракт
10
+ → retry → transport → client → auth → errmap → pagination → sessions → base →
11
+ registry → antibot. Браузерный слой (`adapterkit.browser`) тащит опциональный
12
+ Playwright и здесь НЕ ре-экспортируется на верхний уровень — импортируется явно
13
+ из подмодуля (``from adapterkit.browser import maximized_chromium``), чтобы
14
+ верхнеуровневый импорт пакета не зависел от extra `browser`.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ __version__ = "0.1.1"
19
+
20
+ # Импорты сгруппированы по слоям, но отсортированы по алфавиту (isort/ruff):
21
+ # заголовки-комментарии ниже маркируют слой каждого подмодуля, а __all__ в конце
22
+ # держит исходную смысловую группировку (ошибки → контракт → транспорт → ...).
23
+
24
+ # --------------------------------------------------------------------------- #
25
+ # antibot — выбор транспорта (curl-cffi JA3 / реальный браузер по CDP) #
26
+ # --------------------------------------------------------------------------- #
27
+ from adapterkit.antibot import (
28
+ CdpBrowserBackend,
29
+ CdpTransport,
30
+ CurlCffiTransport,
31
+ pick_chrome_impersonate,
32
+ )
33
+
34
+ # --------------------------------------------------------------------------- #
35
+ # auth — стратегии авторизации + персистентность сессии #
36
+ # --------------------------------------------------------------------------- #
37
+ from adapterkit.auth import (
38
+ BrowserLoginAuth,
39
+ CookieSessionAuth,
40
+ OAuth2Auth,
41
+ TokenAuth,
42
+ decrypt_settings,
43
+ dump_settings,
44
+ encrypt_settings,
45
+ load_settings,
46
+ )
47
+
48
+ # --------------------------------------------------------------------------- #
49
+ # base — базовый адаптер + ресурс-под-сервисы (Stripe-фасад) #
50
+ # --------------------------------------------------------------------------- #
51
+ from adapterkit.base import (
52
+ BaseAdapter,
53
+ CommentsResource,
54
+ ContentResource,
55
+ HttpClientLike,
56
+ MetricsResource,
57
+ Resource,
58
+ SearchResource,
59
+ )
60
+
61
+ # --------------------------------------------------------------------------- #
62
+ # client — единый choke-point HTTP-вызовов #
63
+ # --------------------------------------------------------------------------- #
64
+ from adapterkit.client import HttpClient
65
+
66
+ # --------------------------------------------------------------------------- #
67
+ # contract — Protocol-«талия» + версии контракта + DTO/enum #
68
+ # --------------------------------------------------------------------------- #
69
+ from adapterkit.contract import (
70
+ ADAPTER_API_VERSION,
71
+ MIN_SUPPORTED_API_VERSION,
72
+ Auth,
73
+ AuthMode,
74
+ Creds,
75
+ Endpoint,
76
+ ErrorMapper,
77
+ NetworkAdapter,
78
+ PaginationMode,
79
+ Paginator,
80
+ Refreshable,
81
+ SessionRef,
82
+ SessionStoreProtocol,
83
+ Transport,
84
+ TransportKind,
85
+ )
86
+
87
+ # --------------------------------------------------------------------------- #
88
+ # errmap — декларативный маппинг ответа в доменную ошибку #
89
+ # --------------------------------------------------------------------------- #
90
+ from adapterkit.errmap import (
91
+ DEFAULT_ERROR_MAP,
92
+ AuthExpired,
93
+ BodyRule,
94
+ ErrorMap,
95
+ ErrorMapBuilder,
96
+ ExcFactory,
97
+ build_error_map,
98
+ )
99
+
100
+ # --------------------------------------------------------------------------- #
101
+ # errors — единая иерархия (реэкспорт clikit + сетевые подклассы) #
102
+ # --------------------------------------------------------------------------- #
103
+ from adapterkit.errors import (
104
+ RETRYABLE,
105
+ AdapterError,
106
+ AuthRequired,
107
+ Blocked,
108
+ CliError,
109
+ NotFound,
110
+ RateLimited,
111
+ ServerError,
112
+ SessionExpired,
113
+ TransportError,
114
+ ValidationError,
115
+ )
116
+
117
+ # --------------------------------------------------------------------------- #
118
+ # onboarding_contract — опциональные онбординг/health-контракты (api_version 2) #
119
+ # --------------------------------------------------------------------------- #
120
+ from adapterkit.onboarding_contract import (
121
+ HealthProtocol,
122
+ HealthState,
123
+ HealthStatus,
124
+ InteractiveFlow,
125
+ LoginMode,
126
+ LoginRequirements,
127
+ OnboardingProtocol,
128
+ )
129
+
130
+ # --------------------------------------------------------------------------- #
131
+ # orchestration_api — тонкий registry-driven API онбординга/health (W5) #
132
+ # --------------------------------------------------------------------------- #
133
+ from adapterkit.orchestration_api import (
134
+ OnboardReport,
135
+ OnboardResult,
136
+ health_check_all,
137
+ onboard_all,
138
+ )
139
+
140
+ # --------------------------------------------------------------------------- #
141
+ # pagination — три стратегии листания + tweepy-обёртки #
142
+ # --------------------------------------------------------------------------- #
143
+ from adapterkit.pagination import (
144
+ DEFAULT_PAGE_PARAMS,
145
+ CursorExtractor,
146
+ CursorPaginator,
147
+ ItemsExtractor,
148
+ PageParams,
149
+ default_extract_cursor,
150
+ default_extract_items,
151
+ with_page_params,
152
+ )
153
+
154
+ # --------------------------------------------------------------------------- #
155
+ # registry — discovery плагинов + ручная регистрация #
156
+ # --------------------------------------------------------------------------- #
157
+ from adapterkit.registry import (
158
+ ENTRY_POINT_GROUP,
159
+ AdapterClass,
160
+ AdapterRegistry,
161
+ default_registry,
162
+ discover_adapters,
163
+ get_adapter_class,
164
+ get_health_adapters,
165
+ get_onboarding_adapters,
166
+ register_adapter,
167
+ )
168
+
169
+ # --------------------------------------------------------------------------- #
170
+ # retry — header-driven политика повторов #
171
+ # --------------------------------------------------------------------------- #
172
+ from adapterkit.retry import DEFAULT_RETRY, RetryPolicy
173
+
174
+ # --------------------------------------------------------------------------- #
175
+ # sessions — единое хранилище сессий (каталог + SQLite-индекс) #
176
+ # --------------------------------------------------------------------------- #
177
+ from adapterkit.sessions import (
178
+ KekUnavailableError,
179
+ SessionStore,
180
+ SessionStoreError,
181
+ resolve_kek,
182
+ )
183
+
184
+ # --------------------------------------------------------------------------- #
185
+ # transport — конкретный httpx-транспорт #
186
+ # --------------------------------------------------------------------------- #
187
+ from adapterkit.transport import HttpxTransport
188
+
189
+ __all__ = [
190
+ "__version__",
191
+ # errors (единая иерархия поверх clikit)
192
+ "CliError",
193
+ "AuthRequired",
194
+ "SessionExpired",
195
+ "RateLimited",
196
+ "ValidationError",
197
+ "AdapterError",
198
+ "TransportError",
199
+ "NotFound",
200
+ "Blocked",
201
+ "ServerError",
202
+ "RETRYABLE",
203
+ # contract: версии + Protocol-«талия» + DTO/enum
204
+ "ADAPTER_API_VERSION",
205
+ "MIN_SUPPORTED_API_VERSION",
206
+ "Transport",
207
+ "Auth",
208
+ "Refreshable",
209
+ "ErrorMapper",
210
+ "Paginator",
211
+ "SessionStoreProtocol",
212
+ "NetworkAdapter",
213
+ "Endpoint",
214
+ "SessionRef",
215
+ "Creds",
216
+ "PaginationMode",
217
+ "AuthMode",
218
+ "TransportKind",
219
+ # onboarding/health контракты (api_version 2, опциональные)
220
+ "LoginMode",
221
+ "LoginRequirements",
222
+ "InteractiveFlow",
223
+ "OnboardingProtocol",
224
+ "HealthState",
225
+ "HealthStatus",
226
+ "HealthProtocol",
227
+ # retry
228
+ "RetryPolicy",
229
+ "DEFAULT_RETRY",
230
+ # transport
231
+ "HttpxTransport",
232
+ # client (choke-point)
233
+ "HttpClient",
234
+ # auth: стратегии + персистентность сессии
235
+ "TokenAuth",
236
+ "CookieSessionAuth",
237
+ "OAuth2Auth",
238
+ "BrowserLoginAuth",
239
+ "dump_settings",
240
+ "load_settings",
241
+ "encrypt_settings",
242
+ "decrypt_settings",
243
+ # errmap
244
+ "ErrorMap",
245
+ "ErrorMapBuilder",
246
+ "BodyRule",
247
+ "ExcFactory",
248
+ "AuthExpired",
249
+ "build_error_map",
250
+ "DEFAULT_ERROR_MAP",
251
+ # pagination
252
+ "CursorPaginator",
253
+ "PageParams",
254
+ "DEFAULT_PAGE_PARAMS",
255
+ "ItemsExtractor",
256
+ "CursorExtractor",
257
+ "default_extract_items",
258
+ "default_extract_cursor",
259
+ "with_page_params",
260
+ # sessions
261
+ "SessionStore",
262
+ "SessionStoreError",
263
+ "KekUnavailableError",
264
+ "resolve_kek",
265
+ # base (адаптер + ресурсы)
266
+ "BaseAdapter",
267
+ "Resource",
268
+ "ContentResource",
269
+ "CommentsResource",
270
+ "MetricsResource",
271
+ "SearchResource",
272
+ "HttpClientLike",
273
+ # registry
274
+ "AdapterRegistry",
275
+ "AdapterClass",
276
+ "ENTRY_POINT_GROUP",
277
+ "default_registry",
278
+ "register_adapter",
279
+ "get_adapter_class",
280
+ "discover_adapters",
281
+ "get_onboarding_adapters",
282
+ "get_health_adapters",
283
+ # orchestration_api (тонкий registry-driven онбординг/health поверх librarykit)
284
+ "onboard_all",
285
+ "health_check_all",
286
+ "OnboardResult",
287
+ "OnboardReport",
288
+ # antibot (выбор транспорта)
289
+ "CurlCffiTransport",
290
+ "CdpTransport",
291
+ "CdpBrowserBackend",
292
+ "pick_chrome_impersonate",
293
+ ]