s-clientkit 0.1.0__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 (46) hide show
  1. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/CHANGELOG.md +26 -0
  2. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/PKG-INFO +10 -6
  3. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/README.md +6 -1
  4. s_clientkit-0.2.0/clientkit/__init__.py +126 -0
  5. s_clientkit-0.2.0/clientkit/_base.py +9 -0
  6. s_clientkit-0.2.0/clientkit/_candidates.py +9 -0
  7. s_clientkit-0.2.0/clientkit/builder.py +9 -0
  8. s_clientkit-0.2.0/clientkit/declaration.py +31 -0
  9. s_clientkit-0.2.0/clientkit/errors.py +14 -0
  10. s_clientkit-0.2.0/clientkit/hidden.py +9 -0
  11. s_clientkit-0.2.0/clientkit/identity.py +31 -0
  12. s_clientkit-0.2.0/clientkit/ports.py +21 -0
  13. s_clientkit-0.2.0/clientkit/white.py +9 -0
  14. s_clientkit-0.2.0/pyproject.toml +79 -0
  15. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/tests/test_limit_gate.py +4 -1
  16. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/tests/test_same_call_both_kinds.py +22 -9
  17. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/uv.lock +60 -24
  18. s_clientkit-0.1.0/clientkit/__init__.py +0 -124
  19. s_clientkit-0.1.0/clientkit/_base.py +0 -174
  20. s_clientkit-0.1.0/clientkit/_candidates.py +0 -150
  21. s_clientkit-0.1.0/clientkit/builder.py +0 -66
  22. s_clientkit-0.1.0/clientkit/declaration.py +0 -338
  23. s_clientkit-0.1.0/clientkit/errors.py +0 -48
  24. s_clientkit-0.1.0/clientkit/hidden.py +0 -194
  25. s_clientkit-0.1.0/clientkit/identity.py +0 -249
  26. s_clientkit-0.1.0/clientkit/ports.py +0 -71
  27. s_clientkit-0.1.0/clientkit/white.py +0 -84
  28. s_clientkit-0.1.0/pyproject.toml +0 -64
  29. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/.gitignore +0 -0
  30. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/.gitlab-ci.yml +0 -0
  31. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/.importlinter +0 -0
  32. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/.skillgateignore +0 -0
  33. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/AGENTS.md +0 -0
  34. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/LICENSE +0 -0
  35. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/clientkit/_version.py +0 -0
  36. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/docs/adr/0000-template.md +0 -0
  37. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/docs/adr/README.md +0 -0
  38. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/examples/hidden_service.toml +0 -0
  39. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/examples/white_service.toml +0 -0
  40. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/scripts/check_dist_leaks.py +0 -0
  41. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/scripts/release_guard.py +0 -0
  42. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/tests/__init__.py +0 -0
  43. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/tests/test_declaration.py +0 -0
  44. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/tests/test_hidden_holds_access.py +0 -0
  45. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/tests/test_identity.py +0 -0
  46. {s_clientkit-0.1.0 → s_clientkit-0.2.0}/tests/test_ports_transport.py +0 -0
@@ -1,5 +1,31 @@
1
1
  # Changelog — s-clientkit
2
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
+
3
29
  ## [0.1.0] — 2026-09-06
4
30
 
5
31
  Волна китов-оснований: транспорт только через netkit, свой `TokenBucket` удалён.
@@ -1,26 +1,30 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: s-clientkit
3
- Version: 0.1.0
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
- Requires-Dist: s-netkit<0.2,>=0.1.1
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
13
13
  Requires-Dist: pytest>=8; extra == 'dev'
14
14
  Requires-Dist: ruff>=0.8; extra == 'dev'
15
15
  Provides-Extra: endpoints
16
- Requires-Dist: s-adapterkit>=0.1.8; extra == 'endpoints'
17
16
  Provides-Extra: hidden
18
- Requires-Dist: s-authkit-client<0.2,>=0.0.2; extra == 'hidden'
17
+ Requires-Dist: s-adapterkit[client-hidden]<0.3,>=0.2.0; extra == 'hidden'
19
18
  Provides-Extra: http
20
19
  Requires-Dist: httpx>=0.27; extra == 'http'
21
20
  Description-Content-Type: text/markdown
22
21
 
23
- # s-clientkit — кит-сборщик клиентов
22
+ # s-clientkit — тонкий алиас поверх adapterkit.clientkit
23
+
24
+ > **Волна 5, задача 2 (Atlas #2710):** живой код переехал в
25
+ > `adapterkit.clientkit` — здесь остались тонкие модули-реэкспорты. Публичная
26
+ > поверхность (всё, что описано ниже) не изменилась; изменился только адрес
27
+ > кода под ней.
24
28
 
25
29
  Готовый клиент сервиса **собирается по декларации**, а не пишется заново.
26
30
 
@@ -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
 
@@ -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"]
@@ -0,0 +1,9 @@
1
+ """Тонкий алиас — код переехал в ``adapterkit.clientkit.builder`` (волна 5,
2
+ задача 2, Atlas #2710). Реэкспорт ради back-compat: ``from clientkit.builder
3
+ import build_client`` работает идентично.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from adapterkit.clientkit.builder import build_client, build_from_toml
8
+
9
+ __all__ = ["build_client", "build_from_toml"]
@@ -0,0 +1,31 @@
1
+ """Тонкий алиас — код переехал в ``adapterkit.clientkit.declaration`` (волна 5,
2
+ задача 2, Atlas #2710). Реэкспорт ради back-compat: ``from clientkit.declaration
3
+ import ServiceDecl`` и т.п. работают идентично.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from adapterkit.clientkit.declaration import (
8
+ AccessRecipe,
9
+ AuthDecl,
10
+ AuthScheme,
11
+ EndpointDecl,
12
+ LimitDecl,
13
+ ServiceDecl,
14
+ ServiceKind,
15
+ decl_from_data,
16
+ decl_to_data,
17
+ load_toml,
18
+ )
19
+
20
+ __all__ = [
21
+ "AccessRecipe",
22
+ "AuthDecl",
23
+ "AuthScheme",
24
+ "EndpointDecl",
25
+ "LimitDecl",
26
+ "ServiceDecl",
27
+ "ServiceKind",
28
+ "decl_from_data",
29
+ "decl_to_data",
30
+ "load_toml",
31
+ ]
@@ -0,0 +1,14 @@
1
+ """Тонкий алиас — код переехал в ``adapterkit.clientkit.errors`` (волна 5,
2
+ задача 2, Atlas #2710). Реэкспорт ради back-compat: ``from clientkit.errors
3
+ import AccessUnavailable`` и т.п. работают идентично.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from adapterkit.clientkit.errors import (
8
+ AccessUnavailable,
9
+ ClientBuildError,
10
+ OperationFailed,
11
+ TransportUnavailable,
12
+ )
13
+
14
+ __all__ = ["AccessUnavailable", "ClientBuildError", "OperationFailed", "TransportUnavailable"]
@@ -0,0 +1,9 @@
1
+ """Тонкий алиас — код переехал в ``adapterkit.clientkit.hidden`` (волна 5,
2
+ задача 2, Atlas #2710). Реэкспорт ради back-compat: ``from clientkit.hidden
3
+ import HiddenApiClient`` работает идентично.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from adapterkit.clientkit.hidden import HiddenApiClient
8
+
9
+ __all__ = ["HiddenApiClient"]
@@ -0,0 +1,31 @@
1
+ """Тонкий алиас — код переехал в ``adapterkit.clientkit.identity`` (волна 5,
2
+ задача 2, Atlas #2710). Реэкспорт ради back-compat: ``from clientkit.identity
3
+ import CATALOG, ServiceIdentity, UnknownService, canonical`` — единственный
4
+ путь, которым сегодня ходит reverse-factory — работает идентично, БЕЗ каких-либо
5
+ extra-флагов.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from adapterkit.clientkit.identity import (
10
+ CATALOG,
11
+ Instancing,
12
+ ServiceIdentity,
13
+ UnknownService,
14
+ canonical,
15
+ resolve,
16
+ resolve_service,
17
+ suggest,
18
+ suggest_service,
19
+ )
20
+
21
+ __all__ = [
22
+ "CATALOG",
23
+ "resolve_service",
24
+ "suggest_service",
25
+ "Instancing",
26
+ "ServiceIdentity",
27
+ "UnknownService",
28
+ "canonical",
29
+ "resolve",
30
+ "suggest",
31
+ ]
@@ -0,0 +1,21 @@
1
+ """Тонкий алиас — код переехал в ``adapterkit.clientkit.ports`` (волна 5,
2
+ задача 2, Atlas #2710). Реэкспорт ради back-compat: ``from clientkit.ports
3
+ import RequestPort`` и т.п. работают идентично.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from adapterkit.clientkit.ports import (
8
+ RequestPort,
9
+ ResponseLike,
10
+ SecretLookup,
11
+ TransportUnavailable,
12
+ resolve_transport,
13
+ )
14
+
15
+ __all__ = [
16
+ "RequestPort",
17
+ "ResponseLike",
18
+ "SecretLookup",
19
+ "TransportUnavailable",
20
+ "resolve_transport",
21
+ ]
@@ -0,0 +1,9 @@
1
+ """Тонкий алиас — код переехал в ``adapterkit.clientkit.white`` (волна 5,
2
+ задача 2, Atlas #2710). Реэкспорт ради back-compat: ``from clientkit.white
3
+ import WhiteApiClient`` работает идентично.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from adapterkit.clientkit.white import WhiteApiClient
8
+
9
+ __all__ = ["WhiteApiClient"]
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "s-clientkit"
7
+ version = "0.2.0"
8
+ description = "КИТ-АЛИАС: код-сборщик клиента переехал в adapterkit.clientkit (волна 5, задача 2, Atlas #2710) — этот пакет тонко реэкспортирует его модули под прежними именами (clientkit.identity и остальные)."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.11"
12
+ authors = [{ name = "Dmitry" }]
13
+ # СЛИЯНИЕ В ADAPTERKIT (волна 5, задача 2, Atlas #2710). Живой код (сборщик
14
+ # клиента по декларации, каталог имён сервисов, адреса-кандидаты) переехал в
15
+ # `adapterkit.clientkit` — там же, где уже жил `EndpointRegistry`, которым
16
+ # кандидаты и раньше пользовались (extra `endpoints`). Этот пакет остался
17
+ # ТОНКИМ АЛИАСОМ: каждый модуль `clientkit.*` — реэкспорт одноимённого модуля
18
+ # `adapterkit.clientkit.*`. `s-adapterkit` поэтому — ОБЯЗАТЕЛЬНАЯ зависимость
19
+ # (не extra): даже самый частый путь потребителя, `from clientkit.identity
20
+ # import CATALOG` (reverse-factory), теперь физически лежит в adapterkit, и
21
+ # без него алиас не соберётся, никакой extra тут не спасает.
22
+ #
23
+ # netkit остаётся ОБЯЗАТЕЛЬНЫМ по той же причине, что и раньше: белый путь
24
+ # (`WhiteApiClient`, за ним ходит и `build_client`/`build_from_toml`) поднимает
25
+ # транспорт и лимиты только через netkit — но теперь это `adapterkit[client]`.
26
+ dependencies = [
27
+ "s-corekit>=0.0.2",
28
+ "s-adapterkit[client]>=0.2.0,<0.3",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ # Скрытый API: сессии, чеканка через порт, лестница деградации — теперь
33
+ # `adapterkit[client-hidden]` (сам модуль `adapterkit.clientkit.hidden`).
34
+ hidden = ["s-adapterkit[client-hidden]>=0.2.0,<0.3"]
35
+ # Back-compat no-op: адреса-кандидаты (`clientkit._candidates` ->
36
+ # `adapterkit.clientkit._candidates`) поверх `EndpointRegistry` теперь ВСЕГДА
37
+ # доступны — оба модуля живут в одном пакете `s-adapterkit`, отдельно ставить
38
+ # больше нечего. Extra оставлен ПУСТЫМ (а не удалён), чтобы старый
39
+ # `pip install s-clientkit[endpoints]` не падал `ExtraNotFound`.
40
+ endpoints = []
41
+ http = ["httpx>=0.27"]
42
+ dev = ["pytest>=8", "pytest-asyncio>=0.24", "ruff>=0.8"]
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["clientkit"]
46
+
47
+ [tool.ruff]
48
+ line-length = 100
49
+ target-version = "py311"
50
+ src = ["."]
51
+
52
+ [tool.ruff.lint]
53
+ select = ["E", "F", "I", "B", "UP", "N", "SIM", "RUF"]
54
+ ignore = ["RUF002", "RUF003", "RUF022"]
55
+
56
+ [tool.ruff.lint.per-file-ignores]
57
+ # Русская проза В СТРОКАХ (тексты отказов и сообщения провалов): RUF001 ловит
58
+ # там «двусмысленную» латиницу пачками, а защищать нечего — маркеры-улики живут
59
+ # в corekit.diagnosis и проходятся его тестами.
60
+ "tests/*.py" = ["RUF001"]
61
+ "clientkit/declaration.py" = ["RUF001"]
62
+ "clientkit/hidden.py" = ["RUF001"]
63
+ "clientkit/_base.py" = ["RUF001"]
64
+ "clientkit/ports.py" = ["RUF001"]
65
+ "clientkit/identity.py" = ["RUF001"]
66
+ "clientkit/white.py" = ["RUF001"]
67
+ # Ленивый фасад (PEP 562): `__all__` собирается из `_LAZY_NAMES` в рантайме,
68
+ # статически ruff его не видит и считает TYPE_CHECKING-реэкспорты «неиспользуемыми».
69
+ "clientkit/__init__.py" = ["F401"]
70
+ # scripts/release_guard.py и scripts/check_dist_leaks.py копируются буквально
71
+ # из общего канона tools/kits/template/ (devcontour) — правка точечно в ките
72
+ # сломает сверку `apply_template.py --check` у всех китов экосистемы. Русская
73
+ # проза в их строках — та же намеренная кириллица, что и выше.
74
+ "scripts/*.py" = ["RUF001"]
75
+
76
+ [tool.pytest.ini_options]
77
+ asyncio_mode = "auto"
78
+ testpaths = ["tests"]
79
+ pythonpath = ["."]
@@ -86,7 +86,10 @@ async def test_burst_passes_first_calls_then_gate_waits(monkeypatch: pytest.Monk
86
86
  waits.append(seconds)
87
87
  clock.now += seconds # «поспали» — часы гейта должны это увидеть на следующем acquire
88
88
 
89
- monkeypatch.setattr("clientkit._base.asyncio.sleep", fake_sleep)
89
+ # `clientkit._base` — тонкий алиас (волна 5, задача 2, Atlas #2710): сам
90
+ # объект `ServiceClientBase` живёт в `adapterkit.clientkit._base`, и именно
91
+ # там смотрит на `asyncio.sleep` (тот же класс, что импортирован выше).
92
+ monkeypatch.setattr("adapterkit.clientkit._base.asyncio.sleep", fake_sleep)
90
93
 
91
94
  await client.call("me") # в пределах burst — без ожидания
92
95
  assert waits == []
@@ -200,13 +200,26 @@ async def test_placeholders_and_method_come_from_the_declaration():
200
200
  # --------------------------------------------------------------------------- #
201
201
  # 2. ЦЕНА ВЫЗОВА: белый путь ничего лишнего не поднимает #
202
202
  # --------------------------------------------------------------------------- #
203
+ # После слияния clientkit -> adapterkit.clientkit (волна 5, задача 2, Atlas
204
+ # #2710) `import adapterkit` (а с ним и `librarykit.contract`) стал БАЗОВОЙ
205
+ # ценой любого вызова `clientkit.build_from_toml` — сборщик теперь физически
206
+ # лежит внутри adapterkit. Это НЕ то же самое, что «поднять оркестратор
207
+ # целиком»: `adapterkit`/`librarykit.contract` — тонкие DTO/Protocol-модули без
208
+ # сети, браузера и антибота (см. ЯДРО-контракты `[tool.importlinter]` в
209
+ # adapterkit/pyproject.toml). Настоящая цена, которую белый путь платить НЕ
210
+ # должен, — слой доступа скрытого API и всё тяжёлое окружение ниже.
211
+ #
212
+ # `authkit_client.sessions` (хранилище сессий) сюда НЕ входит: он приезжает
213
+ # ЕЩЁ РАНЬШЕ, самим `librarykit.sessions` (self-alias на `authkit_client.sessions`
214
+ # в `librarykit/__init__.py`) — то есть уже базовой ценой `import adapterkit`,
215
+ # а не решением клиента-сборщика. `authkit_client.ports` (порт ЧЕКАНКИ) —
216
+ # другое дело: librarykit его eager не тянет, и он должен остаться СВЯЗАН
217
+ # именно со скрытым путём (см. `adapterkit.clientkit.hidden._do_mint`).
203
218
  FORBIDDEN_ON_WHITE = (
204
- "authkit_client.sessions", # хранилище сессий
205
219
  "authkit_client.ports", # порт чеканки
206
- "clientkit.hidden", # весь скрытый путь
207
- "clientkit._candidates",
208
- "adapterkit", # реестр адресов (тянет оркестратор целиком)
209
- "librarykit",
220
+ "adapterkit.clientkit.hidden", # весь скрытый путь
221
+ "adapterkit.clientkit._candidates",
222
+ "adapterkit.endpoints", # реестр адресов-кандидатов (не нужен белому)
210
223
  "browserkit",
211
224
  "playwright",
212
225
  "nodriver",
@@ -263,8 +276,8 @@ def test_white_path_initialises_nothing_it_does_not_need():
263
276
  )
264
277
  assert snap["loaded"] == [], (
265
278
  f"белый путь поднял лишнее: {snap['loaded']}. Ищи импорт на уровне модуля "
266
- "в clientkit/builder.py или clientkit/white.py — скрытая машинерия обязана "
267
- "подтягиваться ВНУТРИ ветки HIDDEN."
279
+ "в adapterkit/clientkit/builder.py или adapterkit/clientkit/white.py — "
280
+ "скрытая машинерия обязана подтягиваться ВНУТРИ ветки HIDDEN."
268
281
  )
269
282
  assert snap["requests"] == 1, (
270
283
  f"белый вызов стоил {snap['requests']} запросов вместо одного — "
@@ -278,9 +291,9 @@ def test_hidden_path_does_load_the_access_layer():
278
291
  Если бы «ничего не загружено» было верно и для скрытого, предыдущий тест
279
292
  доказывал бы лишь то, что мы разучились импортировать.
280
293
  """
281
- snap = _measure(_BUILD_HIDDEN, ("clientkit.hidden", "authkit_client.ports"))
294
+ snap = _measure(_BUILD_HIDDEN, ("adapterkit.clientkit.hidden", "authkit_client.ports"))
282
295
  assert snap["result"] == PAYLOAD
283
- assert "clientkit.hidden" in snap["loaded"], (
296
+ assert "adapterkit.clientkit.hidden" in snap["loaded"], (
284
297
  f"скрытый путь не поднял свой модуль: {snap['loaded']}"
285
298
  )
286
299