s-clientkit 0.0.1__tar.gz → 0.1.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 (36) hide show
  1. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/.gitignore +47 -47
  2. s_clientkit-0.1.0/.gitlab-ci.yml +101 -0
  3. s_clientkit-0.1.0/.importlinter +65 -0
  4. s_clientkit-0.1.0/.skillgateignore +12 -0
  5. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/AGENTS.md +45 -45
  6. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/CHANGELOG.md +21 -0
  7. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/PKG-INFO +5 -5
  8. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/README.md +2 -1
  9. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/clientkit/__init__.py +124 -120
  10. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/clientkit/_base.py +174 -160
  11. s_clientkit-0.1.0/clientkit/_version.py +38 -0
  12. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/clientkit/builder.py +2 -1
  13. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/clientkit/errors.py +12 -1
  14. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/clientkit/identity.py +249 -249
  15. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/clientkit/ports.py +20 -19
  16. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/docs/adr/0000-template.md +20 -20
  17. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/docs/adr/README.md +30 -30
  18. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/examples/hidden_service.toml +50 -50
  19. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/pyproject.toml +64 -61
  20. s_clientkit-0.1.0/scripts/check_dist_leaks.py +180 -0
  21. s_clientkit-0.1.0/scripts/release_guard.py +138 -0
  22. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/tests/test_hidden_holds_access.py +227 -227
  23. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/tests/test_identity.py +155 -155
  24. s_clientkit-0.1.0/tests/test_limit_gate.py +98 -0
  25. s_clientkit-0.1.0/tests/test_ports_transport.py +60 -0
  26. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/tests/test_same_call_both_kinds.py +292 -292
  27. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/uv.lock +7 -9
  28. s_clientkit-0.0.1/clientkit/_limit.py +0 -55
  29. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/LICENSE +0 -0
  30. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/clientkit/_candidates.py +0 -0
  31. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/clientkit/declaration.py +0 -0
  32. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/clientkit/hidden.py +0 -0
  33. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/clientkit/white.py +0 -0
  34. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/examples/white_service.toml +0 -0
  35. {s_clientkit-0.0.1 → s_clientkit-0.1.0}/tests/__init__.py +0 -0
  36. {s_clientkit-0.0.1 → s_clientkit-0.1.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 -->
@@ -1,5 +1,26 @@
1
1
  # Changelog — s-clientkit
2
2
 
3
+ ## [0.1.0] — 2026-09-06
4
+
5
+ Волна китов-оснований: транспорт только через netkit, свой `TokenBucket` удалён.
6
+
7
+ ### Breaking
8
+ - `s-netkit` теперь ОБЯЗАТЕЛЬНАЯ базовая зависимость (`>=0.1.1,<0.2`), extra
9
+ `net` убран — «поставил кит без netkit» больше не собирается вовсе.
10
+ - `clientkit._limit.TokenBucket` удалён. Гейт лимита в
11
+ `ServiceClientBase` — теперь `netkit.limit.RateLimiter` (тот же смысл:
12
+ «не чаще `rate` в секунду при ёмкости `burst`», объявленный в `[limits]`
13
+ декларации сервиса).
14
+ - `resolve_transport()` больше НЕ откатывается на голый `httpx.AsyncClient()`,
15
+ если под netkit что-то пошло не так, — молчаливая деградация с
16
+ `except Exception` заменена на `except ImportError`. Без netkit — отказ
17
+ `clientkit.errors.TransportUnavailable` с рецептом (`pip install s-netkit`
18
+ либо свой `transport=`), а не «тихо поехали на другом транспорте».
19
+
20
+ ### Changed
21
+ - `clientkit.errors` — новый `TransportUnavailable` (та же доменная иерархия,
22
+ код `TRANSPORT_UNAVAILABLE`).
23
+
3
24
  ## 0.0.1 — кит-сборщик (задача #1423)
4
25
 
5
26
  Первый выпуск.
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: s-clientkit
3
- Version: 0.0.1
3
+ Version: 0.1.0
4
4
  Summary: КИТ-СБОРЩИК: по ДЕКЛАРАЦИИ сервиса (TOML/dataclass) собирает готовый клиент из слоёв — белый API прямыми запросами, скрытый с удержанием доступа. Потребитель зовёт одинаково.
5
5
  Author: Dmitry
6
6
  License: MIT
7
7
  License-File: LICENSE
8
8
  Requires-Python: >=3.11
9
9
  Requires-Dist: s-corekit>=0.0.2
10
+ Requires-Dist: s-netkit<0.2,>=0.1.1
10
11
  Provides-Extra: dev
11
12
  Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
12
13
  Requires-Dist: pytest>=8; extra == 'dev'
@@ -14,11 +15,9 @@ Requires-Dist: ruff>=0.8; extra == 'dev'
14
15
  Provides-Extra: endpoints
15
16
  Requires-Dist: s-adapterkit>=0.1.8; extra == 'endpoints'
16
17
  Provides-Extra: hidden
17
- Requires-Dist: s-authkit-client>=0.0.2; extra == 'hidden'
18
+ Requires-Dist: s-authkit-client<0.2,>=0.0.2; extra == 'hidden'
18
19
  Provides-Extra: http
19
20
  Requires-Dist: httpx>=0.27; extra == 'http'
20
- Provides-Extra: net
21
- Requires-Dist: s-netkit>=0.0.2; extra == 'net'
22
21
  Description-Content-Type: text/markdown
23
22
 
24
23
  # s-clientkit — кит-сборщик клиентов
@@ -72,7 +71,8 @@ await hidden.call("me") # скрытый API с сессией и лестн
72
71
  ## Чего в ките нет
73
72
 
74
73
  * **своего HTTP** — «чем ходить» приходит аргументом (`RequestPort`), умолчание
75
- (netkit → httpx) резолвится лениво;
74
+ (только netkit; без него — `TransportUnavailable` с рецептом установки) резолвится
75
+ лениво;
76
76
  * **своего браузера** — чеканка приходит через порт слоя доступа;
77
77
  * **своей таксономии отказов** — диагноз ставит `corekit.diagnosis.access`, та же
78
78
  функция, что у живой пробы и у реестра эндпоинтов.
@@ -49,7 +49,8 @@ await hidden.call("me") # скрытый API с сессией и лестн
49
49
  ## Чего в ките нет
50
50
 
51
51
  * **своего HTTP** — «чем ходить» приходит аргументом (`RequestPort`), умолчание
52
- (netkit → httpx) резолвится лениво;
52
+ (только netkit; без него — `TransportUnavailable` с рецептом установки) резолвится
53
+ лениво;
53
54
  * **своего браузера** — чеканка приходит через порт слоя доступа;
54
55
  * **своей таксономии отказов** — диагноз ставит `corekit.diagnosis.access`, та же
55
56
  функция, что у живой пробы и у реестра эндпоинтов.