s-clientkit 0.0.1__tar.gz → 0.2.0__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 (48) hide show
  1. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/.gitignore +47 -47
  2. s_clientkit-0.2.0/.gitlab-ci.yml +101 -0
  3. s_clientkit-0.2.0/.importlinter +65 -0
  4. s_clientkit-0.2.0/.skillgateignore +12 -0
  5. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/AGENTS.md +45 -45
  6. s_clientkit-0.2.0/CHANGELOG.md +71 -0
  7. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/PKG-INFO +12 -8
  8. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/README.md +8 -2
  9. s_clientkit-0.2.0/clientkit/__init__.py +126 -0
  10. s_clientkit-0.2.0/clientkit/_base.py +9 -0
  11. s_clientkit-0.2.0/clientkit/_candidates.py +9 -0
  12. s_clientkit-0.2.0/clientkit/_version.py +38 -0
  13. s_clientkit-0.2.0/clientkit/builder.py +9 -0
  14. s_clientkit-0.2.0/clientkit/declaration.py +31 -0
  15. s_clientkit-0.2.0/clientkit/errors.py +14 -0
  16. s_clientkit-0.2.0/clientkit/hidden.py +9 -0
  17. s_clientkit-0.2.0/clientkit/identity.py +31 -0
  18. s_clientkit-0.2.0/clientkit/ports.py +21 -0
  19. s_clientkit-0.2.0/clientkit/white.py +9 -0
  20. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/docs/adr/0000-template.md +20 -20
  21. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/docs/adr/README.md +30 -30
  22. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/examples/hidden_service.toml +50 -50
  23. s_clientkit-0.2.0/pyproject.toml +79 -0
  24. s_clientkit-0.2.0/scripts/check_dist_leaks.py +180 -0
  25. s_clientkit-0.2.0/scripts/release_guard.py +138 -0
  26. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/tests/test_hidden_holds_access.py +227 -227
  27. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/tests/test_identity.py +155 -155
  28. s_clientkit-0.2.0/tests/test_limit_gate.py +101 -0
  29. s_clientkit-0.2.0/tests/test_ports_transport.py +60 -0
  30. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/tests/test_same_call_both_kinds.py +305 -292
  31. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/uv.lock +62 -28
  32. s_clientkit-0.0.1/CHANGELOG.md +0 -24
  33. s_clientkit-0.0.1/clientkit/__init__.py +0 -120
  34. s_clientkit-0.0.1/clientkit/_base.py +0 -160
  35. s_clientkit-0.0.1/clientkit/_candidates.py +0 -150
  36. s_clientkit-0.0.1/clientkit/_limit.py +0 -55
  37. s_clientkit-0.0.1/clientkit/builder.py +0 -65
  38. s_clientkit-0.0.1/clientkit/declaration.py +0 -338
  39. s_clientkit-0.0.1/clientkit/errors.py +0 -37
  40. s_clientkit-0.0.1/clientkit/hidden.py +0 -194
  41. s_clientkit-0.0.1/clientkit/identity.py +0 -249
  42. s_clientkit-0.0.1/clientkit/ports.py +0 -70
  43. s_clientkit-0.0.1/clientkit/white.py +0 -84
  44. s_clientkit-0.0.1/pyproject.toml +0 -61
  45. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/LICENSE +0 -0
  46. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/examples/white_service.toml +0 -0
  47. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/tests/__init__.py +0 -0
  48. {s_clientkit-0.0.1 → s_clientkit-0.2.0}/tests/test_declaration.py +0 -0
@@ -1,47 +1,47 @@
1
- # === atlas universal gitignore ===
2
-
3
- # OS / IDE
4
- .DS_Store
5
- Thumbs.db
6
- .vscode/
7
- .idea/
8
- *.swp
9
- *.swo
10
-
11
- # Sensitive
12
- .env
13
- .env.local
14
- *.key
15
- *.pem
16
- secrets/
17
- private/
18
-
19
- # Python
20
- __pycache__/
21
- *.py[cod]
22
- .venv/
23
- venv/
24
- .pytest_cache/
25
- .ruff_cache/
26
- *.egg-info/
27
-
28
- # Node / JS
29
- node_modules/
30
- .next/
31
- dist/
32
- build/
33
-
34
- # Temporary / large
35
- *.log
36
- *.tmp
37
- nul
38
- NUL
39
- *.zip
40
- *.rar
41
- *.7z
42
-
43
- # Media (selectively unignore via !path/*.ext if needed for fixtures)
44
- *.mp4
45
- *.mov
46
- *.avi
47
- *.mkv
1
+ # === atlas universal gitignore ===
2
+
3
+ # OS / IDE
4
+ .DS_Store
5
+ Thumbs.db
6
+ .vscode/
7
+ .idea/
8
+ *.swp
9
+ *.swo
10
+
11
+ # Sensitive
12
+ .env
13
+ .env.local
14
+ *.key
15
+ *.pem
16
+ secrets/
17
+ private/
18
+
19
+ # Python
20
+ __pycache__/
21
+ *.py[cod]
22
+ .venv/
23
+ venv/
24
+ .pytest_cache/
25
+ .ruff_cache/
26
+ *.egg-info/
27
+
28
+ # Node / JS
29
+ node_modules/
30
+ .next/
31
+ dist/
32
+ build/
33
+
34
+ # Temporary / large
35
+ *.log
36
+ *.tmp
37
+ nul
38
+ NUL
39
+ *.zip
40
+ *.rar
41
+ *.7z
42
+
43
+ # Media (selectively unignore via !path/*.ext if needed for fixtures)
44
+ *.mp4
45
+ *.mov
46
+ *.avi
47
+ *.mkv
@@ -0,0 +1,101 @@
1
+ # СГЕНЕРИРОВАНО из devcontour/tools/kits/template/gitlab-ci.yml — не правь здесь.
2
+ # Правка идёт в шаблон, затем `python tools/kits/apply_template.py <кит>`.
3
+ # Сверка: `python tools/kits/apply_template.py <кит> --check` (код 1 при расхождении).
4
+ #
5
+ # Автопубликация на PyPI по семвер-тегу vX.Y.Z через Trusted Publishing (OIDC).
6
+ # Три стадии, и это не украшение: версии на PyPI неизменяемы, и шесть китов
7
+ # уезжали туда без единого теста (browserkit 0.0.18, netkit ≤0.0.12 — оба в бою).
8
+ # gates — тесты, линтер, слои, doctor: на каждый push и на тег
9
+ # release-guard — тег == версия во всех местах (pyproject/uv.lock/CHANGELOG)
10
+ # publish-pypi — только после двух первых; версия, уже лежащая на PyPI, не
11
+ # публикуется повторно (тег «догоняет» выпуск, а не роняет CI);
12
+ # после сборки и до обмена токена — check_dist_leaks.py
13
+ # (личные данные разработчика в собранном дистрибутиве)
14
+ # Токены НИГДЕ не хранятся: GitLab выдаёт короткоживущий OIDC-токен (PYPI_ID_TOKEN),
15
+ # он одноразово обменивается на PyPI API-токен. Издатель настраивается на pypi.org:
16
+ # Manage -> Publishing -> GitLab (namespace=cifropro1/products, project=clientkit,
17
+ # top-level pipeline file=.gitlab-ci.yml, environment — пусто).
18
+ # Дистрибутив на PyPI: s-clientkit.
19
+ stages: [test, guard, publish]
20
+
21
+ .uv:
22
+ image: ghcr.io/astral-sh/uv:python3.12-bookworm
23
+
24
+ gates:
25
+ extends: .uv
26
+ stage: test
27
+ rules:
28
+ - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
29
+ - if: $CI_PIPELINE_SOURCE == "push"
30
+ script:
31
+ # dev-зависимости у китов объявлены двумя способами — extra `dev` и группа
32
+ # `[dependency-groups] dev`; --all-extras покрывает первое, группу добавляем
33
+ # только если она объявлена, иначе uv падает «Group dev is not defined».
34
+ - uv sync --all-extras $(python3 -c "import tomllib;print('--group dev' if 'dev' in tomllib.load(open('pyproject.toml','rb')).get('dependency-groups',{}) else '')")
35
+ - uv run pytest tests/ -q
36
+ # Линтер по ВСЕМУ пакету, а не по списку файлов: сужение до «файлов волны 0»
37
+ # держалось девять китов и ни разу не было расширено — сужение приживается,
38
+ # расширение нет.
39
+ - uv run ruff check .
40
+ # Границы слоёв: контракт в .importlinter (ставится тем же шаблоном).
41
+ # — доп. пакеты через --with (пусто у большинства
42
+ # китов; adapterkit — пример кита, которому нужен сосед на прогоне
43
+ # lint-imports, см. apply_template.py --extra-with).
44
+ - uv run --with import-linter lint-imports
45
+ # Гейт 1, часть «согласованность локальных venv» (kitsctl doctor). Скрипт
46
+ # раздаётся шаблоном не всем: в раннере видно ОДИН репозиторий, соседних
47
+ # venv'ов там нет, — поэтому шаг условный и молча пропускается, когда
48
+ # инструмента у кита нет. Настоящее место doctor — локальный прогон перед
49
+ # тегом; см. docs/kits/SAFETY-NET.md, «Известные ограничения».
50
+ - 'if [ -f scripts/kitsctl.py ]; then python3 scripts/kitsctl.py doctor; else echo "kitsctl doctor — инструмента нет в репозитории, пропуск (см. SAFETY-NET.md)"; fi'
51
+
52
+ release-guard:
53
+ extends: .uv
54
+ stage: guard
55
+ needs: [gates]
56
+ rules:
57
+ - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
58
+ script:
59
+ - python3 scripts/release_guard.py "$CI_COMMIT_TAG"
60
+
61
+ publish-pypi:
62
+ extends: .uv
63
+ stage: publish
64
+ needs: [release-guard]
65
+ rules:
66
+ - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
67
+ id_tokens:
68
+ PYPI_ID_TOKEN:
69
+ aud: pypi
70
+ script:
71
+ - |
72
+ DIST=$(python3 -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['name'])")
73
+ VER="${CI_COMMIT_TAG#v}"
74
+ if python3 - "$DIST" "$VER" <<'PY'
75
+ import json, sys, urllib.request
76
+ dist, ver = sys.argv[1], sys.argv[2]
77
+ try:
78
+ data = json.load(urllib.request.urlopen(f"https://pypi.org/pypi/{dist}/json"))
79
+ except Exception:
80
+ sys.exit(1)
81
+ sys.exit(0 if ver in data.get("releases", {}) else 1)
82
+ PY
83
+ then echo "версия $VER уже на PyPI — публикацию пропускаем"; exit 0; fi
84
+ - uv build
85
+ # Последний рубеж перед необратимым шагом (версии на PyPI неизменяемы,
86
+ # yank — не удаление). Прецедент — leasekit: реальный email и домашний
87
+ # путь разработчика уехали в публичный индекс из тестовых фикстур. Ловит
88
+ # по классам, не по строкам; см. docs/kits/SAFETY-NET.md.
89
+ - python3 scripts/check_dist_leaks.py dist/
90
+ - |
91
+ export UV_PUBLISH_TOKEN=$(python3 - <<'PY'
92
+ import json, os, urllib.request
93
+ req = urllib.request.Request(
94
+ "https://pypi.org/_/oidc/mint-token",
95
+ data=json.dumps({"token": os.environ["PYPI_ID_TOKEN"]}).encode(),
96
+ headers={"Content-Type": "application/json"},
97
+ )
98
+ print(json.load(urllib.request.urlopen(req))["token"])
99
+ PY
100
+ )
101
+ - uv publish
@@ -0,0 +1,65 @@
1
+ # СГЕНЕРИРОВАНО из devcontour/tools/kits/template/importlinter.ini — не правь
2
+ # здесь. Правка идёт в шаблон, затем `python tools/kits/apply_template.py <кит>`.
3
+ #
4
+ # Заготовка контракта слоёв. Запуск: `uv run --with import-linter lint-imports`.
5
+ #
6
+ # Контракт ниже — минимум, который годится ЛЮБОМУ киту и не требует ручной
7
+ # настройки: «clientkit не импортирует те киты экосистемы, которых нет в его
8
+ # объявленных зависимостях». Скрытая зависимость — это кит, который работает у
9
+ # автора (сосед стоит в venv) и разваливается у потребителя на первом импорте;
10
+ # именно так ломались pypi-publish и gemini-chat.
11
+ #
12
+ # Список forbidden_modules подставлен установщиком из реестра китов на момент
13
+ # установки: все известные пакеты китов МИНУС собственный МИНУС объявленные в
14
+ # pyproject зависимости. Появилась новая честная зависимость — объяви её в
15
+ # pyproject и переустанови шаблон, а не правь этот файл.
16
+ #
17
+ # Собственные контракты кита (layers/eager_forbidden/declared_dependencies,
18
+ # как у adapterkit) дописываются НИЖЕ отдельными секциями прямо в этом файле.
19
+ # Установщик сверяет файл целиком, поэтому `--check` после такой правки
20
+ # закономерно покажет расхождение с каноном — это ожидаемо и осознанно
21
+ # игнорируется в отчёте ревью, см. docs/kits/SAFETY-NET.md, «Что делать при
22
+ # расхождении» (п.2).
23
+
24
+ [importlinter]
25
+ # include_external_packages нужен: forbidden_modules перечисляет пакеты,
26
+ # внешние относительно root_packages.
27
+ include_external_packages = True
28
+ root_packages =
29
+ clientkit
30
+ exclude_type_checking_imports = True
31
+
32
+ [importlinter:contract:no-undeclared-kits]
33
+ name = clientkit не импортирует киты, которых нет в его зависимостях
34
+ type = forbidden
35
+ source_modules =
36
+ clientkit
37
+ forbidden_modules =
38
+ accountpoolkit
39
+ agentskit
40
+ aichatkit
41
+ authkit_contracts
42
+ authkit_server
43
+ b24kit
44
+ browserkit
45
+ chatkit
46
+ clikit
47
+ emojikit
48
+ gitkit
49
+ installerkit
50
+ iokit
51
+ issuekit
52
+ leasekit
53
+ librarykit
54
+ maxkit
55
+ modelkit
56
+ ormkit
57
+ persistkit
58
+ servicekit
59
+ sessionkit
60
+ skillkit
61
+ socialkit
62
+ storagekit
63
+ telemetrykit
64
+ totp_client
65
+ worker_service
@@ -0,0 +1,12 @@
1
+ # Исключения для секрет-скана s-skillgate перед push. Глобы — от корня репо.
2
+ #
3
+ # scripts/check_dist_leaks.py — сам гейт утечки личных данных (сетка
4
+ # безопасности китов, docs/kits/SAFETY-NET.md в devcontour), копия из
5
+ # tools/kits/template/check_dist_leaks.py. Ловит по классам, не по строкам:
6
+ # windows-путь с числовым именем пользователя, личный ящик на публичном
7
+ # провайдере, юникс-домашний путь. Regex-паттерны и пример подсказки
8
+ # (windows-путь с числовым account, «/home/user») по определению совпадают
9
+ # с собственным денилистом — иначе гейт не проверял бы то, ради чего написан (та же
10
+ # ситуация, что и с tests/golden/ у самого check_dist_leaks.py). Файл
11
+ # копируется буквально из канона — правки идут в шаблон, не сюда.
12
+ scripts/check_dist_leaks.py
@@ -1,45 +1,45 @@
1
- # AGENTS.md — clientkit
2
-
3
- > Контекст для AI-ассистентов (Claude Code, ChatGPT, Cursor и т.п.), работающих
4
- > над этим проектом.
5
-
6
- ## Что это
7
-
8
- (заполнить one-line)
9
-
10
- ## Atlas
11
-
12
- Проект зарегистрирован в Atlas-БД (Atlas). Карточка:
13
-
14
- ```sh
15
- atlas project get clientkit
16
- ```
17
-
18
- Любые изменения метаданных (приоритет, статус, теги) — через atlas CLI:
19
-
20
- - `atlas project update clientkit --priority P0` — поменять приоритет
21
- - `atlas project tag add clientkit -t domain:<slug>` — добавить тег
22
- - `atlas project move clientkit --to-type <type>` — конвертировать тип
23
-
24
- ## Тип / Статус (на момент создания)
25
-
26
- - type=`kit`, status=`active`, priority=`P2`
27
-
28
- ## Правила работы
29
-
30
- - Все исходные тексты, документы, код проекта — в этом репо.
31
- - Чувствительные данные (`.env`, токены, ключи) — игнорируются `.gitignore`.
32
- - AI-ассистенту разрешено: читать, генерировать, редактировать в этом репо.
33
-
34
- ## Канонические команды
35
-
36
- - `atlas project get clientkit` — карточка проекта
37
- - `atlas task list --project clientkit` — задачи проекта (когда W7
38
- волна будет реализована)
39
-
40
- <!-- atlas:usage:start -->
41
- ## Управление проектом — через Atlas
42
-
43
- Этот проект ведётся в Atlas (личная PM-система портфеля). Для задач/проектов/эпиков/бэкапов
44
- используй CLI `atlas` и вызывай навык `atlas` — вся логика и роутинг внутри навыка.
45
- <!-- atlas:usage:end -->
1
+ # AGENTS.md — clientkit
2
+
3
+ > Контекст для AI-ассистентов (Claude Code, ChatGPT, Cursor и т.п.), работающих
4
+ > над этим проектом.
5
+
6
+ ## Что это
7
+
8
+ (заполнить one-line)
9
+
10
+ ## Atlas
11
+
12
+ Проект зарегистрирован в Atlas-БД (Atlas). Карточка:
13
+
14
+ ```sh
15
+ atlas project get clientkit
16
+ ```
17
+
18
+ Любые изменения метаданных (приоритет, статус, теги) — через atlas CLI:
19
+
20
+ - `atlas project update clientkit --priority P0` — поменять приоритет
21
+ - `atlas project tag add clientkit -t domain:<slug>` — добавить тег
22
+ - `atlas project move clientkit --to-type <type>` — конвертировать тип
23
+
24
+ ## Тип / Статус (на момент создания)
25
+
26
+ - type=`kit`, status=`active`, priority=`P2`
27
+
28
+ ## Правила работы
29
+
30
+ - Все исходные тексты, документы, код проекта — в этом репо.
31
+ - Чувствительные данные (`.env`, токены, ключи) — игнорируются `.gitignore`.
32
+ - AI-ассистенту разрешено: читать, генерировать, редактировать в этом репо.
33
+
34
+ ## Канонические команды
35
+
36
+ - `atlas project get clientkit` — карточка проекта
37
+ - `atlas task list --project clientkit` — задачи проекта (когда W7
38
+ волна будет реализована)
39
+
40
+ <!-- atlas:usage:start -->
41
+ ## Управление проектом — через Atlas
42
+
43
+ Этот проект ведётся в Atlas (личная PM-система портфеля). Для задач/проектов/эпиков/бэкапов
44
+ используй CLI `atlas` и вызывай навык `atlas` — вся логика и роутинг внутри навыка.
45
+ <!-- atlas:usage:end -->
@@ -0,0 +1,71 @@
1
+ # Changelog — s-clientkit
2
+
3
+ ## [0.2.0] — 2026-09-08
4
+
5
+ Слияние в adapterkit (волна 5, задача 2, Atlas #2710): у кита не набралось
6
+ живой массы под отдельный релизный цикл — единственный внешний потребитель
7
+ (reverse-factory) ходит только за `clientkit.identity`, а адреса-кандидаты уже
8
+ были тонкой обёрткой над `adapterkit.endpoints.EndpointRegistry`.
9
+
10
+ ### Breaking
11
+
12
+ - Весь код (декларация сервиса, сборка белого/скрытого клиента, каталог имён
13
+ сервисов, адреса-кандидаты) переехал в `adapterkit.clientkit`. Пакет
14
+ `clientkit` — ТЕПЕРЬ ТОНКИЙ АЛИАС: каждый модуль (`clientkit.identity`,
15
+ `clientkit.declaration`, `clientkit.builder`, `clientkit.hidden`,
16
+ `clientkit.white`, `clientkit.ports`, `clientkit.errors`, `clientkit._base`,
17
+ `clientkit._candidates`) реэкспортирует одноимённый модуль
18
+ `adapterkit.clientkit.*`. Публичная поверхность (`from clientkit import
19
+ build_from_toml`, `from clientkit.identity import CATALOG`, …) сохранена 1:1.
20
+ - `s-adapterkit` — теперь ОБЯЗАТЕЛЬНАЯ базовая зависимость (`>=0.2.0,<0.3`,
21
+ extra `client` внутри неё тянет `s-netkit`): даже `clientkit.identity` физически
22
+ лежит в `adapterkit.clientkit.identity`.
23
+ - Extra `endpoints` — теперь no-op (`s-adapterkit` и так обязателен, ставить
24
+ отдельно больше нечего). Оставлен пустым, а не удалён, чтобы старый
25
+ `pip install s-clientkit[endpoints]` не падал `ExtraNotFound`.
26
+ - Extra `hidden` теперь форвардит `s-adapterkit[client-hidden]` вместо прямого
27
+ `s-authkit-client` (тот же пакет приезжает транзитивно).
28
+
29
+ ## [0.1.0] — 2026-09-06
30
+
31
+ Волна китов-оснований: транспорт только через netkit, свой `TokenBucket` удалён.
32
+
33
+ ### Breaking
34
+ - `s-netkit` теперь ОБЯЗАТЕЛЬНАЯ базовая зависимость (`>=0.1.1,<0.2`), extra
35
+ `net` убран — «поставил кит без netkit» больше не собирается вовсе.
36
+ - `clientkit._limit.TokenBucket` удалён. Гейт лимита в
37
+ `ServiceClientBase` — теперь `netkit.limit.RateLimiter` (тот же смысл:
38
+ «не чаще `rate` в секунду при ёмкости `burst`», объявленный в `[limits]`
39
+ декларации сервиса).
40
+ - `resolve_transport()` больше НЕ откатывается на голый `httpx.AsyncClient()`,
41
+ если под netkit что-то пошло не так, — молчаливая деградация с
42
+ `except Exception` заменена на `except ImportError`. Без netkit — отказ
43
+ `clientkit.errors.TransportUnavailable` с рецептом (`pip install s-netkit`
44
+ либо свой `transport=`), а не «тихо поехали на другом транспорте».
45
+
46
+ ### Changed
47
+ - `clientkit.errors` — новый `TransportUnavailable` (та же доменная иерархия,
48
+ код `TRANSPORT_UNAVAILABLE`).
49
+
50
+ ## 0.0.1 — кит-сборщик (задача #1423)
51
+
52
+ Первый выпуск.
53
+
54
+ - `clientkit.declaration` — декларация сервиса как ДАННЫЕ (`ServiceDecl` и
55
+ соседи, frozen dataclass'ы, round-trip в обычный dict, загрузка из TOML,
56
+ валидация при загрузке).
57
+ - `clientkit.builder` — `build_client` / `build_from_toml`: единственная развилка
58
+ кита, и она смотрит на `decl.kind`.
59
+ - `clientkit.white` — белый API: заголовок авторизации (`api_key`/`bearer`/
60
+ `oauth` с проактивным TTL) + прямой запрос. Ни сессий, ни чеканки, ни антибота.
61
+ - `clientkit.hidden` — скрытый API: лестница доступа из декларации, чеканка через
62
+ порт слоя доступа, ротация адресов-кандидатов, разбор отказов через
63
+ `corekit.diagnosis.access`.
64
+ - `clientkit._candidates` — книга адресов поверх `adapterkit.EndpointRegistry`
65
+ либо равнозначная встроенная (совпадение проверено тестом).
66
+ - `clientkit.ports` — `RequestPort` / `SecretLookup`; умолчание транспорта
67
+ (netkit → httpx) резолвится лениво.
68
+
69
+ Главное свойство: потребитель зовёт `await client.call("me")` одинаково в обоих
70
+ мирах, при этом белый путь не поднимает ни сессий, ни чеканки, ни антибота и
71
+ делает ровно один запрос.
@@ -1,27 +1,30 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: s-clientkit
3
- Version: 0.0.1
4
- Summary: КИТ-СБОРЩИК: по ДЕКЛАРАЦИИ сервиса (TOML/dataclass) собирает готовый клиент из слоёв — белый API прямыми запросами, скрытый с удержанием доступа. Потребитель зовёт одинаково.
3
+ Version: 0.2.0
4
+ Summary: КИТ-АЛИАС: код-сборщик клиента переехал в adapterkit.clientkit (волна 5, задача 2, Atlas #2710) — этот пакет тонко реэкспортирует его модули под прежними именами (clientkit.identity и остальные).
5
5
  Author: Dmitry
6
6
  License: MIT
7
7
  License-File: LICENSE
8
8
  Requires-Python: >=3.11
9
+ Requires-Dist: s-adapterkit[client]<0.3,>=0.2.0
9
10
  Requires-Dist: s-corekit>=0.0.2
10
11
  Provides-Extra: dev
11
12
  Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
12
13
  Requires-Dist: pytest>=8; extra == 'dev'
13
14
  Requires-Dist: ruff>=0.8; extra == 'dev'
14
15
  Provides-Extra: endpoints
15
- Requires-Dist: s-adapterkit>=0.1.8; extra == 'endpoints'
16
16
  Provides-Extra: hidden
17
- Requires-Dist: s-authkit-client>=0.0.2; extra == 'hidden'
17
+ Requires-Dist: s-adapterkit[client-hidden]<0.3,>=0.2.0; extra == 'hidden'
18
18
  Provides-Extra: http
19
19
  Requires-Dist: httpx>=0.27; extra == 'http'
20
- Provides-Extra: net
21
- Requires-Dist: s-netkit>=0.0.2; extra == 'net'
22
20
  Description-Content-Type: text/markdown
23
21
 
24
- # s-clientkit — кит-сборщик клиентов
22
+ # s-clientkit — тонкий алиас поверх adapterkit.clientkit
23
+
24
+ > **Волна 5, задача 2 (Atlas #2710):** живой код переехал в
25
+ > `adapterkit.clientkit` — здесь остались тонкие модули-реэкспорты. Публичная
26
+ > поверхность (всё, что описано ниже) не изменилась; изменился только адрес
27
+ > кода под ней.
25
28
 
26
29
  Готовый клиент сервиса **собирается по декларации**, а не пишется заново.
27
30
 
@@ -72,7 +75,8 @@ await hidden.call("me") # скрытый API с сессией и лестн
72
75
  ## Чего в ките нет
73
76
 
74
77
  * **своего HTTP** — «чем ходить» приходит аргументом (`RequestPort`), умолчание
75
- (netkit → httpx) резолвится лениво;
78
+ (только netkit; без него — `TransportUnavailable` с рецептом установки) резолвится
79
+ лениво;
76
80
  * **своего браузера** — чеканка приходит через порт слоя доступа;
77
81
  * **своей таксономии отказов** — диагноз ставит `corekit.diagnosis.access`, та же
78
82
  функция, что у живой пробы и у реестра эндпоинтов.
@@ -1,4 +1,9 @@
1
- # s-clientkit — кит-сборщик клиентов
1
+ # s-clientkit — тонкий алиас поверх adapterkit.clientkit
2
+
3
+ > **Волна 5, задача 2 (Atlas #2710):** живой код переехал в
4
+ > `adapterkit.clientkit` — здесь остались тонкие модули-реэкспорты. Публичная
5
+ > поверхность (всё, что описано ниже) не изменилась; изменился только адрес
6
+ > кода под ней.
2
7
 
3
8
  Готовый клиент сервиса **собирается по декларации**, а не пишется заново.
4
9
 
@@ -49,7 +54,8 @@ await hidden.call("me") # скрытый API с сессией и лестн
49
54
  ## Чего в ките нет
50
55
 
51
56
  * **своего HTTP** — «чем ходить» приходит аргументом (`RequestPort`), умолчание
52
- (netkit → httpx) резолвится лениво;
57
+ (только netkit; без него — `TransportUnavailable` с рецептом установки) резолвится
58
+ лениво;
53
59
  * **своего браузера** — чеканка приходит через порт слоя доступа;
54
60
  * **своей таксономии отказов** — диагноз ставит `corekit.diagnosis.access`, та же
55
61
  функция, что у живой пробы и у реестра эндпоинтов.
@@ -0,0 +1,126 @@
1
+ """clientkit — ТОНКИЙ АЛИАС поверх ``adapterkit.clientkit`` (волна 5, задача 2,
2
+ Atlas #2710).
3
+
4
+ БЫЛО СВОИМ КИТОМ-СБОРЩИКОМ, СТАЛО АЛИАСОМ. Живой код (декларация сервиса,
5
+ сборка белого/скрытого клиента, каталог имён сервисов, адреса-кандидаты)
6
+ переехал в :mod:`adapterkit.clientkit` — рядом с реестром адресов
7
+ (:class:`adapterkit.endpoints.EndpointRegistry`), которым адреса-кандидаты и
8
+ раньше пользовались (см. историю ``clientkit._candidates``). Единственный
9
+ известный внешний потребитель на момент переноса — reverse-factory, и он ходит
10
+ ТОЛЬКО за :mod:`clientkit.identity` (``CATALOG``/``ServiceIdentity``/
11
+ ``UnknownService``/``canonical``): этот путь работает как раньше, включая
12
+ установку без extra-флагов.
13
+
14
+ Каждый модуль здесь (``clientkit.identity``, ``clientkit.declaration``,
15
+ ``clientkit.builder``, ``clientkit.hidden``, ``clientkit.white``,
16
+ ``clientkit.ports``, ``clientkit.errors``, ``clientkit._base``,
17
+ ``clientkit._candidates``) — тонкий файл-реэкспорт одноимённого модуля
18
+ ``adapterkit.clientkit.*``: ``import clientkit.builder`` и т.п. продолжает
19
+ работать, даже если что-то в экосистеме импортирует их напрямую, а не через
20
+ корень пакета.
21
+
22
+ ОДИН ВЫЗОВ НА ОБА МИРА (без изменений)::
23
+
24
+ from clientkit import build_from_toml
25
+
26
+ white = build_from_toml("examples/white_service.toml", secrets=os.environ.get)
27
+ hidden = build_from_toml("examples/hidden_service.toml", session_store=store)
28
+
29
+ await white.call("me") # белый REST с api-ключом
30
+ await hidden.call("me") # скрытый API с сессией и лестницей деградации
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import importlib
35
+ from typing import TYPE_CHECKING
36
+
37
+ if TYPE_CHECKING: # pragma: no cover — для тайпчекера/IDE
38
+ from adapterkit.clientkit._base import ServiceClient
39
+ from adapterkit.clientkit.builder import build_client, build_from_toml
40
+ from adapterkit.clientkit.declaration import (
41
+ AccessRecipe,
42
+ AuthDecl,
43
+ AuthScheme,
44
+ EndpointDecl,
45
+ LimitDecl,
46
+ ServiceDecl,
47
+ ServiceKind,
48
+ decl_from_data,
49
+ decl_to_data,
50
+ load_toml,
51
+ )
52
+ from adapterkit.clientkit.errors import AccessUnavailable, ClientBuildError, OperationFailed
53
+ from adapterkit.clientkit.hidden import HiddenApiClient
54
+ from adapterkit.clientkit.identity import (
55
+ CATALOG,
56
+ Instancing,
57
+ ServiceIdentity,
58
+ UnknownService,
59
+ canonical,
60
+ resolve_service,
61
+ suggest_service,
62
+ )
63
+ from adapterkit.clientkit.ports import RequestPort, SecretLookup
64
+ from adapterkit.clientkit.white import WhiteApiClient
65
+
66
+ #: имя -> модуль-владелец В adapterkit.clientkit (не в этом пакете — этот
67
+ #: пакет сам ничего не реализует). `hidden` в этой таблице стоит РЯДОМ с
68
+ #: `white`, но цена у них разная: обращение к `HiddenApiClient` подтягивает
69
+ #: слой доступа, а к `WhiteApiClient` — нет. Ленивость здесь и защищает эту
70
+ #: разницу.
71
+ _LAZY_NAMES: dict[str, str] = {
72
+ "build_client": "adapterkit.clientkit.builder",
73
+ "build_from_toml": "adapterkit.clientkit.builder",
74
+ "ServiceClient": "adapterkit.clientkit._base",
75
+ # ИМЯ сервиса — тоже данные, и притом нужные тем, кто базы не держит:
76
+ # навыку назвать свою сессию, записи трафика — положить снимок.
77
+ "ServiceIdentity": "adapterkit.clientkit.identity",
78
+ "Instancing": "adapterkit.clientkit.identity",
79
+ "UnknownService": "adapterkit.clientkit.identity",
80
+ "CATALOG": "adapterkit.clientkit.identity",
81
+ "canonical": "adapterkit.clientkit.identity",
82
+ "resolve_service": "adapterkit.clientkit.identity",
83
+ "suggest_service": "adapterkit.clientkit.identity",
84
+ # декларация — данные
85
+ "ServiceDecl": "adapterkit.clientkit.declaration",
86
+ "ServiceKind": "adapterkit.clientkit.declaration",
87
+ "AuthDecl": "adapterkit.clientkit.declaration",
88
+ "AuthScheme": "adapterkit.clientkit.declaration",
89
+ "LimitDecl": "adapterkit.clientkit.declaration",
90
+ "EndpointDecl": "adapterkit.clientkit.declaration",
91
+ "AccessRecipe": "adapterkit.clientkit.declaration",
92
+ "decl_from_data": "adapterkit.clientkit.declaration",
93
+ "decl_to_data": "adapterkit.clientkit.declaration",
94
+ "load_toml": "adapterkit.clientkit.declaration",
95
+ # порты
96
+ "RequestPort": "adapterkit.clientkit.ports",
97
+ "SecretLookup": "adapterkit.clientkit.ports",
98
+ # клиенты (обычно не нужны напрямую — собирает build_client)
99
+ "WhiteApiClient": "adapterkit.clientkit.white",
100
+ "HiddenApiClient": "adapterkit.clientkit.hidden",
101
+ # ошибки
102
+ "ClientBuildError": "adapterkit.clientkit.errors",
103
+ "AccessUnavailable": "adapterkit.clientkit.errors",
104
+ "OperationFailed": "adapterkit.clientkit.errors",
105
+ }
106
+
107
+ __all__ = ["__version__", *sorted(_LAZY_NAMES)]
108
+
109
+
110
+ def __getattr__(name: str):
111
+ if name == "__version__":
112
+ from ._version import resolve_version
113
+
114
+ value = resolve_version()
115
+ globals()["__version__"] = value
116
+ return value
117
+ module_name = _LAZY_NAMES.get(name)
118
+ if module_name is None:
119
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
120
+ value = getattr(importlib.import_module(module_name), name)
121
+ globals()[name] = value
122
+ return value
123
+
124
+
125
+ def __dir__() -> list[str]:
126
+ return sorted({*globals(), *_LAZY_NAMES})
@@ -0,0 +1,9 @@
1
+ """Тонкий алиас — код переехал в ``adapterkit.clientkit._base`` (волна 5,
2
+ задача 2, Atlas #2710). Реэкспорт ради back-compat: ``from clientkit._base
3
+ import ServiceClient`` работает идентично.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from adapterkit.clientkit._base import ServiceClient, ServiceClientBase
8
+
9
+ __all__ = ["ServiceClient", "ServiceClientBase"]
@@ -0,0 +1,9 @@
1
+ """Тонкий алиас — код переехал в ``adapterkit.clientkit._candidates`` (волна 5,
2
+ задача 2, Atlas #2710). Реэкспорт ради back-compat: ``from clientkit._candidates
3
+ import CandidateBook`` работает идентично.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from adapterkit.clientkit._candidates import CandidateBook
8
+
9
+ __all__ = ["CandidateBook"]