funora 0.0.1.dev2__py3-none-any.whl

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 (87) hide show
  1. funora/__init__.py +265 -0
  2. funora/_account.py +661 -0
  3. funora/_aclient.py +1484 -0
  4. funora/_budget.py +579 -0
  5. funora/_calc.py +182 -0
  6. funora/_canonical.py +235 -0
  7. funora/_catalog.py +473 -0
  8. funora/_chat_history.py +367 -0
  9. funora/_chats.py +392 -0
  10. funora/_chips.py +403 -0
  11. funora/_classify.py +466 -0
  12. funora/_client.py +1490 -0
  13. funora/_currency_switch.py +130 -0
  14. funora/_cursor.py +120 -0
  15. funora/_delivered.py +233 -0
  16. funora/_diff.py +752 -0
  17. funora/_engine.py +4795 -0
  18. funora/_extract.py +124 -0
  19. funora/_field_schema.py +112 -0
  20. funora/_fileio.py +58 -0
  21. funora/_gate.py +69 -0
  22. funora/_hops.py +101 -0
  23. funora/_host.py +120 -0
  24. funora/_identity.py +284 -0
  25. funora/_json.py +31 -0
  26. funora/_listen.py +312 -0
  27. funora/_lot_form.py +331 -0
  28. funora/_market.py +406 -0
  29. funora/_matching.py +147 -0
  30. funora/_money.py +279 -0
  31. funora/_monitoring.py +396 -0
  32. funora/_observed.py +237 -0
  33. funora/_order.py +552 -0
  34. funora/_order_details.py +281 -0
  35. funora/_orders.py +807 -0
  36. funora/_outbound.py +453 -0
  37. funora/_own_lots.py +301 -0
  38. funora/_poll.py +410 -0
  39. funora/_price_audit.py +281 -0
  40. funora/_proxies.py +232 -0
  41. funora/_raise.py +150 -0
  42. funora/_refund.py +102 -0
  43. funora/_result.py +157 -0
  44. funora/_retry.py +213 -0
  45. funora/_review_write.py +138 -0
  46. funora/_reviews.py +584 -0
  47. funora/_runner.py +651 -0
  48. funora/_secret.py +385 -0
  49. funora/_showcase.py +362 -0
  50. funora/_signals.py +375 -0
  51. funora/_skeleton.py +752 -0
  52. funora/_snapshot.py +276 -0
  53. funora/_state.py +279 -0
  54. funora/_stock.py +32 -0
  55. funora/_thread.py +574 -0
  56. funora/_transport.py +1023 -0
  57. funora/_updates.py +292 -0
  58. funora/_verdicts.py +91 -0
  59. funora/_viewing.py +143 -0
  60. funora/_watch.py +825 -0
  61. funora/_watch_state.py +237 -0
  62. funora/_whoami.py +546 -0
  63. funora/bot/__init__.py +52 -0
  64. funora/bot/_delivery.py +341 -0
  65. funora/bot/_outbox.py +261 -0
  66. funora/bot/_runtime.py +447 -0
  67. funora/bot/_spool.py +534 -0
  68. funora/budget.py +311 -0
  69. funora/capabilities.py +297 -0
  70. funora/conformance.py +758 -0
  71. funora/contract.py +73 -0
  72. funora/errors.py +996 -0
  73. funora/events.py +189 -0
  74. funora/extraction.py +420 -0
  75. funora/observe.py +560 -0
  76. funora/operations.py +671 -0
  77. funora/py.typed +0 -0
  78. funora/reconciliation.py +51 -0
  79. funora/response_classes.py +155 -0
  80. funora/retry.py +238 -0
  81. funora/send_outcome.py +78 -0
  82. funora/skeleton_format.py +77 -0
  83. funora-0.0.1.dev2.dist-info/METADATA +294 -0
  84. funora-0.0.1.dev2.dist-info/RECORD +87 -0
  85. funora-0.0.1.dev2.dist-info/WHEEL +4 -0
  86. funora-0.0.1.dev2.dist-info/entry_points.txt +2 -0
  87. funora-0.0.1.dev2.dist-info/licenses/LICENSE +201 -0
funora/budget.py ADDED
@@ -0,0 +1,311 @@
1
+ r"""Числа бюджета запросов.
2
+
3
+ Файл порождён из спецификации, править его руками нельзя: правка исчезнет при
4
+ следующей сборке. Источник - spec/runtime/budget.yaml в репозитории Funora-spec.
5
+ Перестроить: .venv\Scripts\python.exe tools/codegen.py
6
+
7
+ Числа помечены в спецификации провизорными: измерять настоящие пороги
8
+ площадки означало бы намеренно их превышать. Поэтому они подобраны
9
+ консервативно и будут уточняться наблюдением, а не подбором.
10
+
11
+ Сетевые вёдра расходуют запросы, включая повторы и переходы по
12
+ редиректам. Считать только логические операции нельзя: тогда шторм
13
+ повторов оказывается бесплатным ровно в тот момент, когда площадке
14
+ хуже всего.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+ from enum import StrEnum
21
+ from typing import Final
22
+
23
+ __all__ = [
24
+ "BucketLimits",
25
+ "BUCKETS",
26
+ "MAX_WAIT_MS",
27
+ "WAIT_ATTEMPTS",
28
+ "WAIT_GUARD_MS",
29
+ "BURST_WINDOW_MS",
30
+ "RequestClass",
31
+ "ON_REFUSAL",
32
+ "FLOOR_SHARE",
33
+ "DEMAND_WINDOW_MS",
34
+ "COUNTS_RETRIES",
35
+ "COUNTS_REDIRECTS",
36
+ "MIN_HEALTH_INTERVAL_MS",
37
+ "OUTBOUND_MESSAGES_PER_HOUR",
38
+ "OUTBOUND_UNIQUE_RECIPIENTS_PER_HOUR",
39
+ "OUTBOUND_MIN_INTERVAL_PER_CHAT_MS",
40
+ "OUTBOUND_WINDOW_MS",
41
+ "OUTBOUND_WARMING_EVENTS",
42
+ "COLD_OUTREACH_QUOTA_PER_HOUR",
43
+ "COLD_OUTREACH_WINDOW_MS",
44
+ "MAX_QUEUE_DEPTH_PER_KEY",
45
+ "MAX_CONCURRENT_HANDLERS",
46
+ "HANDLER_TIMEOUT_MS",
47
+ "MAX_CONNECTIONS_PER_HOST",
48
+ "MAX_RESPONSE_BYTES",
49
+ "MAX_DECOMPRESSED_BYTES",
50
+ "MAX_REDIRECTS",
51
+ "RateLimitResponse",
52
+ "RATE_LIMIT_RESPONSE",
53
+ "Scheduling",
54
+ "SCHEDULING",
55
+ "PROVISIONAL",
56
+ "MARKET_INTERVAL_MS",
57
+ "MARKET_ABSENCES",
58
+ "MARKET_HISTORY_LIMIT",
59
+ ]
60
+
61
+
62
+ @dataclass(frozen=True, slots=True)
63
+ class BucketLimits:
64
+ """Ёмкость и скорость пополнения одного ведра.
65
+
66
+ Attributes:
67
+ name (str): Имя ведра.
68
+ capacity (int): Сколько единиц помещается всего.
69
+ refill_per_second (float): Сколько восстанавливается за секунду.
70
+ burst (int): Сколько можно потратить залпом.
71
+ unit (str): requests либо actions_per_hour для логических записей.
72
+ """
73
+
74
+ name: str
75
+ capacity: int
76
+ refill_per_second: float
77
+ burst: int
78
+ unit: str = "requests"
79
+
80
+
81
+ #: Вёдра бюджета. Вложены: запрос расходует сначала общее, потом ведро
82
+ #: аккаунта. Порядок нормативен, иначе при нескольких аккаунтах в одном
83
+ #: процессе общий предел обходится.
84
+ BUCKETS: Final[dict[str, BucketLimits]] = {
85
+ "host": BucketLimits(
86
+ name="host",
87
+ capacity=60,
88
+ refill_per_second=1.0,
89
+ burst=10,
90
+ ),
91
+ "account": BucketLimits(
92
+ name="account",
93
+ capacity=30,
94
+ refill_per_second=0.5,
95
+ burst=5,
96
+ ),
97
+ "write": BucketLimits(
98
+ name="write",
99
+ capacity=60,
100
+ refill_per_second=0.0167,
101
+ burst=5,
102
+ unit="actions_per_hour",
103
+ ),
104
+ }
105
+
106
+ #: Сколько ждать освобождения бюджета, прежде чем отказать.
107
+
108
+ #: Ограничитель исходящих сообщений. Не ведро токенов.
109
+ #:
110
+ #: Три предела из четырёх ведром невыразимы: множество различных
111
+ #: адресатов, пауза на отдельную переписку и условная квота. Ведро
112
+ #: отвечает «сколько ждать», а здесь ждать нельзя - пределы часовые
113
+ #: при пределе ожидания в пять секунд.
114
+ OUTBOUND_MESSAGES_PER_HOUR: Final[int] = 30
115
+ OUTBOUND_UNIQUE_RECIPIENTS_PER_HOUR: Final[int] = 15
116
+ OUTBOUND_MIN_INTERVAL_PER_CHAT_MS: Final[int] = 30000
117
+ OUTBOUND_WINDOW_MS: Final[int] = 3600000
118
+ COLD_OUTREACH_QUOTA_PER_HOUR: Final[int] = 3
119
+ COLD_OUTREACH_WINDOW_MS: Final[int] = 86400000
120
+
121
+ #: События, которые ГРЕЮТ переписку. Только входящие.
122
+ #:
123
+ #: Счётчик непрочитанного сюда не входит нарочно: он меняется и от
124
+ #: НАШЕЙ отправки, и ограничитель на нём отменял бы сам себя.
125
+ OUTBOUND_WARMING_EVENTS: Final[tuple[str, ...]] = ("message.created",)
126
+ MAX_WAIT_MS: Final[int] = 5000
127
+
128
+ #: Сколько попыток занять бюджет делается всего.
129
+ #:
130
+ #: Одна пауза и одна повторная попытка. Цикл ожидания превратил бы
131
+ #: предел ожидания в пожелание: каждая итерация ждала бы «не дольше
132
+ #: предела», а вызов снаружи стал бы неотличим от зависшего процесса.
133
+ WAIT_ATTEMPTS: Final[int] = 2
134
+
135
+ #: Сколько миллисекунд прибавляется к вычисленной паузе.
136
+ #:
137
+ #: Пауза округляется вверх и строго больше точной величины. Вровень
138
+ #: привело бы повторную попытку ровно на границу, где запаса ещё нет
139
+ #: из-за последнего бита деления, - и вызов отказал бы, прождав всё
140
+ #: положенное.
141
+ WAIT_GUARD_MS: Final[int] = 1
142
+
143
+ #: Окно, за которое считается право на залп.
144
+ #:
145
+ #: Ёмкость и залп ограничивают разное. Ёмкость - запас: она
146
+ #: копится в простое. Залп - темп: сколько можно отправить подряд,
147
+ #: не переводя дыхания, независимо от накопленного.
148
+ #:
149
+ #: Без второго предела клиент, простоявший минуту, выпускает
150
+ #: шестьдесят запросов в одну секунду - и первым от собственного
151
+ #: залпа страдает сам аккаунт.
152
+ BURST_WINDOW_MS: Final[int] = 1000
153
+
154
+
155
+ class RequestClass(StrEnum):
156
+ """Класс запроса.
157
+
158
+ Определяет, кого вытесняют при нехватке ёмкости. Проставляет его
159
+ служба, а не пользователь: пользователь не знает, чем его вызов
160
+ мешает соседнему.
161
+ """
162
+
163
+ INTERACTIVE = "interactive"
164
+ AUTOMATION = "automation"
165
+ POLL = "poll"
166
+ MONITORING = "monitoring"
167
+
168
+
169
+ #: Что делать с запросом, которого ёмкость не пускает.
170
+ #:
171
+ #: "wait" - ждать пополнения, "refuse" - отказать немедленно.
172
+ #: Отказать можно только тому, кого спецификация объявила
173
+ #: отменяемым: ответ покупателю, не отправленный из-за собственного
174
+ #: мониторинга продавца, - худший исход, какой этот раздел даёт.
175
+ ON_REFUSAL: Final[dict[RequestClass, str]] = {
176
+ RequestClass.INTERACTIVE: "wait",
177
+ RequestClass.AUTOMATION: "wait",
178
+ RequestClass.POLL: "wait",
179
+ RequestClass.MONITORING: "refuse",
180
+ }
181
+
182
+ #: Гарантированная доля ёмкости для каждого класса.
183
+ FLOOR_SHARE: Final[dict[RequestClass, float]] = {
184
+ RequestClass.INTERACTIVE: 0.3,
185
+ RequestClass.AUTOMATION: 0.3,
186
+ RequestClass.POLL: 0.25,
187
+ RequestClass.MONITORING: 0.15,
188
+ }
189
+
190
+ #: Сколько класс считается претендующим после обращения.
191
+ #:
192
+ #: Порог складывается только из долей претендующих. Вытеснять
193
+ #: некого, когда никто не претендует, и запрещать циклу обновлений
194
+ #: брать больше своей доли на пустой площадке значило бы наказывать
195
+ #: его за чужое бездействие.
196
+ DEMAND_WINDOW_MS: Final[int] = 60000
197
+
198
+ #: Расходуют ли бюджет повторы.
199
+ COUNTS_RETRIES: Final[bool] = True
200
+
201
+ #: Расходуют ли бюджет переходы по редиректам.
202
+ COUNTS_REDIRECTS: Final[bool] = True
203
+
204
+ #: Предел числа переходов на один запрос.
205
+
206
+ #: Раньше какого срока проверка сессии возвращает кэш, миллисекунды.
207
+ MIN_HEALTH_INTERVAL_MS: Final[int] = 60000
208
+
209
+ #: Сколько событий помещается в очередь одного ключа упорядочивания, штук.
210
+ MAX_QUEUE_DEPTH_PER_KEY: Final[int] = 128
211
+
212
+ #: Сколько обработчиков выполняется одновременно, штук.
213
+ MAX_CONCURRENT_HANDLERS: Final[int] = 8
214
+
215
+ #: Сколько ждать обработчик, прежде чем счесть его зависшим, миллисекунды.
216
+ HANDLER_TIMEOUT_MS: Final[int] = 30000
217
+
218
+ #: Сколько соединений с одним хостом держать одновременно, штук.
219
+ MAX_CONNECTIONS_PER_HOST: Final[int] = 4
220
+
221
+ #: Предел размера полученного тела, байты.
222
+ MAX_RESPONSE_BYTES: Final[int] = 8388608
223
+
224
+ #: Предел размера тела после распаковки, байты.
225
+ MAX_DECOMPRESSED_BYTES: Final[int] = 33554432
226
+
227
+ #: Предел числа переходов при ручном следовании, штук.
228
+ MAX_REDIRECTS: Final[int] = 5
229
+
230
+
231
+ @dataclass(frozen=True, slots=True)
232
+ class RateLimitResponse:
233
+ """Как источник отвечает на ограничение частоты.
234
+
235
+ Восстановление медленнее падения намеренно. Симметричное
236
+ восстановление даёт автоколебания: система отступает, тут же
237
+ возвращается к прежней частоте, получает ограничение снова и так
238
+ по кругу.
239
+
240
+ Attributes:
241
+ capacity_multiplier (float): На сколько умножается ёмкость.
242
+ min_capacity_factor (float): Ниже этой доли ёмкость не падает.
243
+ cooldown_ms (int): Остывание за первое ограничение, мс.
244
+ window_ms (int): Окно учёта ограничений, мс.
245
+ successes_per_step (int): Успехов подряд на шаг восстановления.
246
+ recovery_multiplier (float): Во сколько раз растёт ёмкость.
247
+ """
248
+
249
+ capacity_multiplier: float
250
+ min_capacity_factor: float
251
+ cooldown_ms: int
252
+ window_ms: int
253
+ successes_per_step: int
254
+ recovery_multiplier: float
255
+
256
+
257
+ #: Реакция на ограничение частоты.
258
+ #:
259
+ #: Раздел долго называл ступени именами и не давал ни одного числа:
260
+ #: реализация не могла его выполнить, даже захотев.
261
+ RATE_LIMIT_RESPONSE: Final[RateLimitResponse] = RateLimitResponse(
262
+ capacity_multiplier=0.5,
263
+ min_capacity_factor=0.125,
264
+ cooldown_ms=60000,
265
+ window_ms=900000,
266
+ successes_per_step=20,
267
+ recovery_multiplier=1.1,
268
+ )
269
+
270
+
271
+ @dataclass(frozen=True, slots=True)
272
+ class Scheduling:
273
+ """Числа расписания опроса.
274
+
275
+ Attributes:
276
+ active_interval_ms (int): Интервал при активном аккаунте.
277
+ idle_step_multiplier (float): Во сколько раз растёт интервал в покое.
278
+ max_interval_ms (int): Потолок интервала.
279
+ activity_window_ms (int): Окно, в котором аккаунт считается активным.
280
+ min_floor_ms (int): Нижний предел интервала.
281
+ """
282
+
283
+ active_interval_ms: int
284
+ idle_step_multiplier: float
285
+ max_interval_ms: int
286
+ activity_window_ms: int
287
+ min_floor_ms: int
288
+
289
+
290
+ #: Расписание опроса.
291
+ #:
292
+ #: Нижний предел интервала обычной настройкой не понижается. Это
293
+ #: единственное число, которое защищает площадку от слишком уверенного
294
+ #: пользователя, а аккаунт пользователя - от него самого.
295
+ SCHEDULING: Final[Scheduling] = Scheduling(
296
+ active_interval_ms=3000,
297
+ idle_step_multiplier=1.5,
298
+ max_interval_ms=120000,
299
+ activity_window_ms=300000,
300
+ min_floor_ms=2000,
301
+ )
302
+
303
+ #: Признак того, что числа подобраны, а не измерены.
304
+ #:
305
+ #: Снимается только тогда, когда пороги станут известны из наблюдений.
306
+ #: Измерять их намеренным превышением нельзя.
307
+ PROVISIONAL: Final[bool] = True
308
+
309
+ MARKET_INTERVAL_MS: Final[int] = 120000
310
+ MARKET_ABSENCES: Final[int] = 2
311
+ MARKET_HISTORY_LIMIT: Final[int] = 100000
funora/capabilities.py ADDED
@@ -0,0 +1,297 @@
1
+ r"""Возможности адаптера и их состояния.
2
+
3
+ Файл порождён из спецификации, править его руками нельзя: правка исчезнет при
4
+ следующей сборке. Источник - spec/capabilities.yaml в репозитории Funora-spec.
5
+ Перестроить: .venv\Scripts\python.exe tools/codegen.py
6
+
7
+ Состояний пять, и разница между ними не косметическая. Вызов блокируется
8
+ только при unsupported, то есть при позитивном свидетельстве отсутствия.
9
+ При unknown вызов выполняется оптимистично: неудачная проверка не
10
+ доказывает, что возможности нет, и блокировать по ней значило бы
11
+ запрещать работу из-за собственной неуверенности.
12
+
13
+ Признаки usable и opt_in_required вынесены в код намеренно. Именно из них
14
+ выводится решение «звать или отказать», и если каждая из шести реализаций
15
+ выведет его сама, они разойдутся в поведении, оставаясь согласными в
16
+ названиях.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from enum import StrEnum
22
+ from typing import Final, NoReturn
23
+
24
+ __all__ = ["CapabilityState", "Capability", "CAPABILITY_SOURCE", "CAPABILITY_INITIAL"]
25
+
26
+
27
+ class CapabilityState(StrEnum):
28
+ """Состояние возможности в текущем сеансе.
29
+
30
+ supported:
31
+ Возможность подтверждена и доступна.
32
+ unsupported:
33
+ Возможность отсутствует по позитивному свидетельству. Вызов завершается
34
+ UnsupportedCapabilityError.
35
+ experimental:
36
+ Возможность реализована, но контракт может измениться. Требует явного включения;
37
+ без него вызов завершается ExperimentalCapabilityError.
38
+ degraded:
39
+ Возможность работает, но адаптер наблюдает частичную деградацию извлечения на
40
+ связанной странице. Вызов разрешён, результат может быть неполным, и это
41
+ отражено в поле completeness результата. Состояние существует, чтобы
42
+ отличить «работает не полностью» от «не работает»: без него оба случая
43
+ пришлось бы кодировать одним значением, и на такое состояние невозможно
44
+ написать тест.
45
+ unknown:
46
+ Состояние не установлено. Вызов НЕ блокируется: выполняется оптимистично, и при
47
+ отказе пользователь получает типизированную ошибку. Молчаливый уход в
48
+ else-ветку невозможен по построению.
49
+ """
50
+
51
+ SUPPORTED = "supported"
52
+ UNSUPPORTED = "unsupported"
53
+ EXPERIMENTAL = "experimental"
54
+ DEGRADED = "degraded"
55
+ UNKNOWN = "unknown"
56
+
57
+ @property
58
+ def usable(self) -> bool:
59
+ """Сообщает, можно ли звать возможность без дополнительных условий.
60
+
61
+ Отвечает на вопрос «можно ли звать прямо сейчас», а не «работает ли
62
+ возможность вообще». У состояния experimental возможность работает,
63
+ но звать её без явного включения нельзя, поэтому здесь False.
64
+
65
+ Returns:
66
+ bool: True, если вызов разрешён без включения.
67
+ """
68
+ return self in _USABLE
69
+
70
+ @property
71
+ def opt_in_required(self) -> bool:
72
+ """Сообщает, требуется ли явное согласие вызывающего.
73
+
74
+ Returns:
75
+ bool: True, если без включения вызов отклоняется.
76
+ """
77
+ return self in _OPT_IN
78
+
79
+ def allows_call(self, *, opted_in: bool) -> bool:
80
+ """Решает, разрешён ли вызов в этом состоянии.
81
+
82
+ Правило взято из predicates в spec/capabilities.yaml, где оно
83
+ объявлено нормативным. Выводить его заново в каждой реализации
84
+ нельзя: шесть SDK выведут шесть разных решений, оставаясь
85
+ согласными в названиях состояний.
86
+
87
+ Args:
88
+ opted_in (bool): Включил ли вызывающий возможность явно.
89
+
90
+ Returns:
91
+ bool: True, если вызов разрешён.
92
+ """
93
+ return self in _USABLE or (opted_in and self in _OPT_IN)
94
+
95
+ def __bool__(self) -> NoReturn:
96
+ """Запрещает приведение к булеву значению.
97
+
98
+ Состояний пять, и к двум они не сводятся. Запись
99
+ ``if client.capability(cap): call()`` выглядит настолько
100
+ естественно, что её пишут не задумываясь, - а состояние это
101
+ строка, и любая непустая строка истинна. Проверка пропускала
102
+ вызов при unsupported: ровно тот случай, ради которого она и
103
+ написана.
104
+
105
+ Тот же запрет стоит у Observed и по той же причине.
106
+
107
+ Raises:
108
+ TypeError: Всегда.
109
+ """
110
+ raise TypeError(
111
+ "CapabilityState нельзя привести к булеву значению: состояний "
112
+ "пять, а не два, и unsupported истинно как непустая строка. "
113
+ "Спросите allows_call(opted_in=...), если нужен факт "
114
+ "допустимости вызова, либо сравните состояние с нужным"
115
+ )
116
+
117
+
118
+ #: Состояния, в которых вызов разрешён без дополнительных условий.
119
+ _USABLE: Final[frozenset[CapabilityState]] = frozenset(
120
+ {
121
+ CapabilityState.SUPPORTED,
122
+ CapabilityState.DEGRADED,
123
+ CapabilityState.UNKNOWN,
124
+ }
125
+ )
126
+
127
+ #: Состояния, требующие явного включения вызывающим.
128
+ _OPT_IN: Final[frozenset[CapabilityState]] = frozenset(
129
+ {
130
+ CapabilityState.EXPERIMENTAL,
131
+ }
132
+ )
133
+
134
+
135
+ class Capability(StrEnum):
136
+ """Возможность адаптера.
137
+
138
+ Значение члена совпадает с именем возможности в спецификации, поэтому
139
+ перечисление пригодно и для сравнения, и для записи в журнал.
140
+ """
141
+
142
+ #: Чтение профиля аккаунта, от имени которого работает клиент.
143
+ ACCOUNT_PROFILE = "account.profile"
144
+ #: Чтение операций по счёту.
145
+ ACCOUNT_TRANSACTIONS = "account.transactions"
146
+ #: Смена валюты, в которой площадка показывает суммы.
147
+ ACCOUNT_SWITCH_CURRENCY = "account.switch_currency"
148
+ #: Чтение баланса аккаунта.
149
+ ACCOUNT_BALANCE = "account.balance"
150
+ #: Чтение дерева разделов каталога.
151
+ CATALOG_CATEGORIES = "catalog.categories"
152
+ #: Поиск игр и их разделов по названию.
153
+ CATALOG_SEARCH = "catalog.search"
154
+ #: Чтение схемы полей конкретной подкатегории.
155
+ CATALOG_FIELD_SCHEMA = "catalog.field_schema"
156
+ #: Чтение публичных предложений по подкатегории.
157
+ MARKET_OFFERS = "market.offers"
158
+ #: Снимок рынка для сравнения и мониторинга.
159
+ MARKET_SNAPSHOT = "market.snapshot"
160
+ #: Чтение публичной витрины продавца с его профиля.
161
+ LOTS_SHOWCASE = "lots.showcase"
162
+ #: Чтение публичного списка предложений раздела чипов.
163
+ CHIPS_OFFERS = "chips.offers"
164
+ #: Чтение формы правки одного предложения.
165
+ LOTS_FORM = "lots.form"
166
+ #: Чтение собственных предложений продавца.
167
+ LOTS_LIST_OWN = "lots.list_own"
168
+ #: Расчёт цены покупателя по цене продавца.
169
+ LOTS_CALCULATE_PRICES = "lots.calculate_prices"
170
+ #: Расчёт цены покупателя на рынке по количеству.
171
+ CHIPS_CALCULATE_PRICES = "chips.calculate_prices"
172
+ #: Включение лота в выдачу.
173
+ LOTS_ACTIVATE = "lots.activate"
174
+ #: Снятие лота с выдачи.
175
+ LOTS_DEACTIVATE = "lots.deactivate"
176
+ #: Изменение цены лота.
177
+ LOTS_UPDATE_PRICE = "lots.update_price"
178
+ #: Поднятие лота в выдаче.
179
+ LOTS_PROMOTE = "lots.promote"
180
+ #: Чтение списка заказов.
181
+ ORDERS_LIST = "orders.list"
182
+ #: Возврат средств покупателю по заказу.
183
+ ORDERS_REFUND = "orders.refund"
184
+ #: Структурное чтение подробностей заказов пачкой.
185
+ ORDERS_DETAILS = "orders.details"
186
+ #: Чтение конкретного заказа.
187
+ ORDERS_GET = "orders.get"
188
+ #: Распознавание событий заказа из канала обновлений.
189
+ ORDERS_EVENTS = "orders.events"
190
+ #: Чтение списка личных переписок.
191
+ CHATS_LIST = "chats.list"
192
+ #: Чтение недавней истории переписки.
193
+ CHATS_HISTORY = "chats.history"
194
+ #: Догрузка более старых сообщений через курсор.
195
+ CHATS_HISTORY_PAGINATION = "chats.history_pagination"
196
+ #: Отправка текстового сообщения.
197
+ CHATS_SEND_TEXT = "chats.send_text"
198
+ #: Отправка изображения или вложения.
199
+ CHATS_SEND_IMAGE = "chats.send_image"
200
+ #: Что покупатель смотрит прямо сейчас.
201
+ CHATS_BUYER_VIEWING = "chats.buyer_viewing"
202
+ #: Отметка переписки прочитанной.
203
+ CHATS_MARK_READ = "chats.mark_read"
204
+ #: Написание отзыва к заказу либо ответа на чужой.
205
+ REVIEWS_LEAVE = "reviews.leave"
206
+ #: Снятие своего отзыва либо ответа.
207
+ REVIEWS_REMOVE = "reviews.remove"
208
+ #: Чтение отзывов с профиля продавца.
209
+ REVIEWS_GET = "reviews.get"
210
+ #: Умеет ли адаптер распознавать системные тексты на той локали, которую площадка отдаёт
211
+ #: этому аккаунту.
212
+ PROTOCOL_LOCALE = "protocol.locale"
213
+
214
+
215
+ #: Откуда берётся состояние возможности.
216
+ #:
217
+ #: static - известно из спецификации, probe - выясняется проверкой,
218
+ #: derived - выводится из состояния других возможностей.
219
+ CAPABILITY_SOURCE: Final[dict[Capability, str]] = {
220
+ Capability.ACCOUNT_PROFILE: "static",
221
+ Capability.ACCOUNT_TRANSACTIONS: "probe",
222
+ Capability.ACCOUNT_SWITCH_CURRENCY: "probe",
223
+ Capability.ACCOUNT_BALANCE: "probe",
224
+ Capability.CATALOG_CATEGORIES: "static",
225
+ Capability.CATALOG_SEARCH: "static",
226
+ Capability.CATALOG_FIELD_SCHEMA: "probe",
227
+ Capability.MARKET_OFFERS: "static",
228
+ Capability.MARKET_SNAPSHOT: "derived",
229
+ Capability.LOTS_SHOWCASE: "probe",
230
+ Capability.CHIPS_OFFERS: "probe",
231
+ Capability.LOTS_FORM: "probe",
232
+ Capability.LOTS_LIST_OWN: "static",
233
+ Capability.LOTS_CALCULATE_PRICES: "probe",
234
+ Capability.CHIPS_CALCULATE_PRICES: "probe",
235
+ Capability.LOTS_ACTIVATE: "probe",
236
+ Capability.LOTS_DEACTIVATE: "probe",
237
+ Capability.LOTS_UPDATE_PRICE: "probe",
238
+ Capability.LOTS_PROMOTE: "probe",
239
+ Capability.ORDERS_LIST: "static",
240
+ Capability.ORDERS_REFUND: "probe",
241
+ Capability.ORDERS_DETAILS: "probe",
242
+ Capability.ORDERS_GET: "static",
243
+ Capability.ORDERS_EVENTS: "probe",
244
+ Capability.CHATS_LIST: "static",
245
+ Capability.CHATS_HISTORY: "static",
246
+ Capability.CHATS_HISTORY_PAGINATION: "probe",
247
+ Capability.CHATS_SEND_TEXT: "static",
248
+ Capability.CHATS_SEND_IMAGE: "probe",
249
+ Capability.CHATS_BUYER_VIEWING: "probe",
250
+ Capability.CHATS_MARK_READ: "probe",
251
+ Capability.REVIEWS_LEAVE: "probe",
252
+ Capability.REVIEWS_REMOVE: "probe",
253
+ Capability.REVIEWS_GET: "probe",
254
+ Capability.PROTOCOL_LOCALE: "static",
255
+ }
256
+
257
+ #: Состояние возможности до первой проверки.
258
+ #:
259
+ #: Начальное значение unknown означает «ещё не выяснено», а не «нет».
260
+ #: Разница определяет, будет вызов выполнен или отклонён.
261
+ CAPABILITY_INITIAL: Final[dict[Capability, CapabilityState]] = {
262
+ Capability.ACCOUNT_PROFILE: CapabilityState.SUPPORTED,
263
+ Capability.ACCOUNT_TRANSACTIONS: CapabilityState.UNKNOWN,
264
+ Capability.ACCOUNT_SWITCH_CURRENCY: CapabilityState.UNKNOWN,
265
+ Capability.ACCOUNT_BALANCE: CapabilityState.UNKNOWN,
266
+ Capability.CATALOG_CATEGORIES: CapabilityState.SUPPORTED,
267
+ Capability.CATALOG_SEARCH: CapabilityState.SUPPORTED,
268
+ Capability.CATALOG_FIELD_SCHEMA: CapabilityState.UNKNOWN,
269
+ Capability.MARKET_OFFERS: CapabilityState.SUPPORTED,
270
+ Capability.MARKET_SNAPSHOT: CapabilityState.SUPPORTED,
271
+ Capability.LOTS_SHOWCASE: CapabilityState.UNKNOWN,
272
+ Capability.CHIPS_OFFERS: CapabilityState.UNKNOWN,
273
+ Capability.LOTS_FORM: CapabilityState.UNKNOWN,
274
+ Capability.LOTS_LIST_OWN: CapabilityState.SUPPORTED,
275
+ Capability.LOTS_CALCULATE_PRICES: CapabilityState.UNKNOWN,
276
+ Capability.CHIPS_CALCULATE_PRICES: CapabilityState.UNKNOWN,
277
+ Capability.LOTS_ACTIVATE: CapabilityState.UNKNOWN,
278
+ Capability.LOTS_DEACTIVATE: CapabilityState.UNKNOWN,
279
+ Capability.LOTS_UPDATE_PRICE: CapabilityState.UNKNOWN,
280
+ Capability.LOTS_PROMOTE: CapabilityState.UNKNOWN,
281
+ Capability.ORDERS_LIST: CapabilityState.SUPPORTED,
282
+ Capability.ORDERS_REFUND: CapabilityState.UNKNOWN,
283
+ Capability.ORDERS_DETAILS: CapabilityState.UNKNOWN,
284
+ Capability.ORDERS_GET: CapabilityState.SUPPORTED,
285
+ Capability.ORDERS_EVENTS: CapabilityState.UNKNOWN,
286
+ Capability.CHATS_LIST: CapabilityState.SUPPORTED,
287
+ Capability.CHATS_HISTORY: CapabilityState.SUPPORTED,
288
+ Capability.CHATS_HISTORY_PAGINATION: CapabilityState.UNKNOWN,
289
+ Capability.CHATS_SEND_TEXT: CapabilityState.SUPPORTED,
290
+ Capability.CHATS_SEND_IMAGE: CapabilityState.UNKNOWN,
291
+ Capability.CHATS_BUYER_VIEWING: CapabilityState.UNKNOWN,
292
+ Capability.CHATS_MARK_READ: CapabilityState.UNKNOWN,
293
+ Capability.REVIEWS_LEAVE: CapabilityState.UNKNOWN,
294
+ Capability.REVIEWS_REMOVE: CapabilityState.UNKNOWN,
295
+ Capability.REVIEWS_GET: CapabilityState.UNKNOWN,
296
+ Capability.PROTOCOL_LOCALE: CapabilityState.SUPPORTED,
297
+ }