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.
- s_adapterkit-0.1.1/.gitignore +20 -0
- s_adapterkit-0.1.1/AGENTS.md +38 -0
- s_adapterkit-0.1.1/BACKLOG-nlm-migration.md +155 -0
- s_adapterkit-0.1.1/LICENSE +21 -0
- s_adapterkit-0.1.1/PKG-INFO +80 -0
- s_adapterkit-0.1.1/README.md +56 -0
- s_adapterkit-0.1.1/adapterkit/__init__.py +293 -0
- s_adapterkit-0.1.1/adapterkit/antibot.py +27 -0
- s_adapterkit-0.1.1/adapterkit/auth.py +42 -0
- s_adapterkit-0.1.1/adapterkit/base.py +337 -0
- s_adapterkit-0.1.1/adapterkit/browser.py +57 -0
- s_adapterkit-0.1.1/adapterkit/client.py +13 -0
- s_adapterkit-0.1.1/adapterkit/contract.py +170 -0
- s_adapterkit-0.1.1/adapterkit/errmap.py +32 -0
- s_adapterkit-0.1.1/adapterkit/errors.py +46 -0
- s_adapterkit-0.1.1/adapterkit/onboarding_contract.py +38 -0
- s_adapterkit-0.1.1/adapterkit/orchestration_api.py +241 -0
- s_adapterkit-0.1.1/adapterkit/pagination.py +31 -0
- s_adapterkit-0.1.1/adapterkit/registry.py +394 -0
- s_adapterkit-0.1.1/adapterkit/retry.py +23 -0
- s_adapterkit-0.1.1/adapterkit/sessions.py +86 -0
- s_adapterkit-0.1.1/adapterkit/testing/__init__.py +45 -0
- s_adapterkit-0.1.1/adapterkit/testing/contract.py +416 -0
- s_adapterkit-0.1.1/adapterkit/transport.py +13 -0
- s_adapterkit-0.1.1/pyproject.toml +66 -0
- s_adapterkit-0.1.1/tests/conftest.py +70 -0
- s_adapterkit-0.1.1/tests/test_antibot.py +311 -0
- s_adapterkit-0.1.1/tests/test_auth.py +329 -0
- s_adapterkit-0.1.1/tests/test_base.py +431 -0
- s_adapterkit-0.1.1/tests/test_browser.py +424 -0
- s_adapterkit-0.1.1/tests/test_client.py +333 -0
- s_adapterkit-0.1.1/tests/test_errmap.py +284 -0
- s_adapterkit-0.1.1/tests/test_orchestration_api.py +325 -0
- s_adapterkit-0.1.1/tests/test_pagination.py +410 -0
- s_adapterkit-0.1.1/tests/test_registry.py +400 -0
- s_adapterkit-0.1.1/tests/test_sessions.py +367 -0
- s_adapterkit-0.1.1/tests/test_testing_contract.py +141 -0
|
@@ -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
|
+
]
|