apisdkopti24 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. apisdkopti24-0.0.1/LICENSE +21 -0
  2. apisdkopti24-0.0.1/PKG-INFO +422 -0
  3. apisdkopti24-0.0.1/README.md +381 -0
  4. apisdkopti24-0.0.1/pyproject.toml +128 -0
  5. apisdkopti24-0.0.1/setup.cfg +4 -0
  6. apisdkopti24-0.0.1/src/apisdkopti24/__init__.py +190 -0
  7. apisdkopti24-0.0.1/src/apisdkopti24/authentication.py +229 -0
  8. apisdkopti24-0.0.1/src/apisdkopti24/client.py +282 -0
  9. apisdkopti24-0.0.1/src/apisdkopti24/composition.py +77 -0
  10. apisdkopti24-0.0.1/src/apisdkopti24/config.py +236 -0
  11. apisdkopti24-0.0.1/src/apisdkopti24/contracts.py +40 -0
  12. apisdkopti24-0.0.1/src/apisdkopti24/credentials.py +206 -0
  13. apisdkopti24-0.0.1/src/apisdkopti24/downloads.py +87 -0
  14. apisdkopti24-0.0.1/src/apisdkopti24/endpoints.py +1528 -0
  15. apisdkopti24-0.0.1/src/apisdkopti24/env.py +39 -0
  16. apisdkopti24-0.0.1/src/apisdkopti24/environments.py +30 -0
  17. apisdkopti24-0.0.1/src/apisdkopti24/error_reporting.py +393 -0
  18. apisdkopti24-0.0.1/src/apisdkopti24/errors.py +464 -0
  19. apisdkopti24-0.0.1/src/apisdkopti24/execution_budget.py +52 -0
  20. apisdkopti24-0.0.1/src/apisdkopti24/executor.py +397 -0
  21. apisdkopti24-0.0.1/src/apisdkopti24/file_io.py +109 -0
  22. apisdkopti24-0.0.1/src/apisdkopti24/http_status.py +11 -0
  23. apisdkopti24-0.0.1/src/apisdkopti24/logger.py +202 -0
  24. apisdkopti24-0.0.1/src/apisdkopti24/modeling.py +108 -0
  25. apisdkopti24-0.0.1/src/apisdkopti24/models/__init__.py +58 -0
  26. apisdkopti24-0.0.1/src/apisdkopti24/models/auth.py +125 -0
  27. apisdkopti24-0.0.1/src/apisdkopti24/models/card_group.py +57 -0
  28. apisdkopti24-0.0.1/src/apisdkopti24/models/cards.py +285 -0
  29. apisdkopti24-0.0.1/src/apisdkopti24/models/common.py +3 -0
  30. apisdkopti24-0.0.1/src/apisdkopti24/models/contracts.py +247 -0
  31. apisdkopti24-0.0.1/src/apisdkopti24/models/dictionaries.py +440 -0
  32. apisdkopti24-0.0.1/src/apisdkopti24/models/ewallet.py +53 -0
  33. apisdkopti24-0.0.1/src/apisdkopti24/models/final_prices.py +46 -0
  34. apisdkopti24-0.0.1/src/apisdkopti24/models/invites.py +117 -0
  35. apisdkopti24-0.0.1/src/apisdkopti24/models/limits.py +191 -0
  36. apisdkopti24-0.0.1/src/apisdkopti24/models/region_limits.py +64 -0
  37. apisdkopti24-0.0.1/src/apisdkopti24/models/reports.py +137 -0
  38. apisdkopti24-0.0.1/src/apisdkopti24/models/request_parts.py +79 -0
  39. apisdkopti24-0.0.1/src/apisdkopti24/models/restrictions.py +89 -0
  40. apisdkopti24-0.0.1/src/apisdkopti24/models/templates.py +224 -0
  41. apisdkopti24-0.0.1/src/apisdkopti24/models/transactions.py +140 -0
  42. apisdkopti24-0.0.1/src/apisdkopti24/models/users.py +145 -0
  43. apisdkopti24-0.0.1/src/apisdkopti24/models/virtual_cards.py +162 -0
  44. apisdkopti24-0.0.1/src/apisdkopti24/operations.py +158 -0
  45. apisdkopti24-0.0.1/src/apisdkopti24/payloads.py +31 -0
  46. apisdkopti24-0.0.1/src/apisdkopti24/policies.py +120 -0
  47. apisdkopti24-0.0.1/src/apisdkopti24/py.typed +0 -0
  48. apisdkopti24-0.0.1/src/apisdkopti24/registry.py +100 -0
  49. apisdkopti24-0.0.1/src/apisdkopti24/request_metadata.py +642 -0
  50. apisdkopti24-0.0.1/src/apisdkopti24/requests.py +120 -0
  51. apisdkopti24-0.0.1/src/apisdkopti24/resilience.py +252 -0
  52. apisdkopti24-0.0.1/src/apisdkopti24/response.py +78 -0
  53. apisdkopti24-0.0.1/src/apisdkopti24/runtime.py +25 -0
  54. apisdkopti24-0.0.1/src/apisdkopti24/sanitization.py +100 -0
  55. apisdkopti24-0.0.1/src/apisdkopti24/service_base.py +265 -0
  56. apisdkopti24-0.0.1/src/apisdkopti24/service_groups.py +71 -0
  57. apisdkopti24-0.0.1/src/apisdkopti24/services/__init__.py +35 -0
  58. apisdkopti24-0.0.1/src/apisdkopti24/services/_shared.py +55 -0
  59. apisdkopti24-0.0.1/src/apisdkopti24/services/auth.py +111 -0
  60. apisdkopti24-0.0.1/src/apisdkopti24/services/card_group.py +132 -0
  61. apisdkopti24-0.0.1/src/apisdkopti24/services/cards.py +273 -0
  62. apisdkopti24-0.0.1/src/apisdkopti24/services/contract.py +183 -0
  63. apisdkopti24-0.0.1/src/apisdkopti24/services/dictionaries.py +194 -0
  64. apisdkopti24-0.0.1/src/apisdkopti24/services/ewallet.py +173 -0
  65. apisdkopti24-0.0.1/src/apisdkopti24/services/final_prices.py +91 -0
  66. apisdkopti24-0.0.1/src/apisdkopti24/services/invites.py +189 -0
  67. apisdkopti24-0.0.1/src/apisdkopti24/services/limits.py +112 -0
  68. apisdkopti24-0.0.1/src/apisdkopti24/services/region_limits.py +99 -0
  69. apisdkopti24-0.0.1/src/apisdkopti24/services/reports.py +230 -0
  70. apisdkopti24-0.0.1/src/apisdkopti24/services/restrictions.py +102 -0
  71. apisdkopti24-0.0.1/src/apisdkopti24/services/templates.py +508 -0
  72. apisdkopti24-0.0.1/src/apisdkopti24/services/transactions.py +259 -0
  73. apisdkopti24-0.0.1/src/apisdkopti24/services/users.py +227 -0
  74. apisdkopti24-0.0.1/src/apisdkopti24/services/virtual_cards.py +296 -0
  75. apisdkopti24-0.0.1/src/apisdkopti24/session.py +152 -0
  76. apisdkopti24-0.0.1/src/apisdkopti24/transport.py +393 -0
  77. apisdkopti24-0.0.1/src/apisdkopti24/utils.py +43 -0
  78. apisdkopti24-0.0.1/src/apisdkopti24/validation.py +227 -0
  79. apisdkopti24-0.0.1/src/apisdkopti24.egg-info/PKG-INFO +422 -0
  80. apisdkopti24-0.0.1/src/apisdkopti24.egg-info/SOURCES.txt +127 -0
  81. apisdkopti24-0.0.1/src/apisdkopti24.egg-info/dependency_links.txt +1 -0
  82. apisdkopti24-0.0.1/src/apisdkopti24.egg-info/requires.txt +15 -0
  83. apisdkopti24-0.0.1/src/apisdkopti24.egg-info/top_level.txt +1 -0
  84. apisdkopti24-0.0.1/tests/test_api_key_rotation.py +188 -0
  85. apisdkopti24-0.0.1/tests/test_auth.py +419 -0
  86. apisdkopti24-0.0.1/tests/test_card_groups.py +99 -0
  87. apisdkopti24-0.0.1/tests/test_cards.py +277 -0
  88. apisdkopti24-0.0.1/tests/test_client_initialization.py +174 -0
  89. apisdkopti24-0.0.1/tests/test_config_logger.py +388 -0
  90. apisdkopti24-0.0.1/tests/test_contract_placement.py +140 -0
  91. apisdkopti24-0.0.1/tests/test_contract_selection.py +388 -0
  92. apisdkopti24-0.0.1/tests/test_contracts.py +355 -0
  93. apisdkopti24-0.0.1/tests/test_dictionaries_service.py +61 -0
  94. apisdkopti24-0.0.1/tests/test_documentation_generator.py +336 -0
  95. apisdkopti24-0.0.1/tests/test_downloads.py +109 -0
  96. apisdkopti24-0.0.1/tests/test_environments.py +26 -0
  97. apisdkopti24-0.0.1/tests/test_error_security.py +244 -0
  98. apisdkopti24-0.0.1/tests/test_errors.py +315 -0
  99. apisdkopti24-0.0.1/tests/test_execution_budget.py +49 -0
  100. apisdkopti24-0.0.1/tests/test_executor.py +662 -0
  101. apisdkopti24-0.0.1/tests/test_file_io.py +85 -0
  102. apisdkopti24-0.0.1/tests/test_final_prices.py +213 -0
  103. apisdkopti24-0.0.1/tests/test_full_chain.py +380 -0
  104. apisdkopti24-0.0.1/tests/test_invites.py +254 -0
  105. apisdkopti24-0.0.1/tests/test_limits.py +183 -0
  106. apisdkopti24-0.0.1/tests/test_live_response_deviations.py +425 -0
  107. apisdkopti24-0.0.1/tests/test_method_examples.py +135 -0
  108. apisdkopti24-0.0.1/tests/test_mock_load_script.py +29 -0
  109. apisdkopti24-0.0.1/tests/test_modeling.py +82 -0
  110. apisdkopti24-0.0.1/tests/test_network_errors.py +249 -0
  111. apisdkopti24-0.0.1/tests/test_package_smoke.py +399 -0
  112. apisdkopti24-0.0.1/tests/test_payloads.py +23 -0
  113. apisdkopti24-0.0.1/tests/test_region_limits.py +97 -0
  114. apisdkopti24-0.0.1/tests/test_registry.py +346 -0
  115. apisdkopti24-0.0.1/tests/test_reports.py +210 -0
  116. apisdkopti24-0.0.1/tests/test_response.py +112 -0
  117. apisdkopti24-0.0.1/tests/test_response_validation_error.py +96 -0
  118. apisdkopti24-0.0.1/tests/test_service_validation.py +298 -0
  119. apisdkopti24-0.0.1/tests/test_session.py +104 -0
  120. apisdkopti24-0.0.1/tests/test_stream_execution.py +329 -0
  121. apisdkopti24-0.0.1/tests/test_templates.py +342 -0
  122. apisdkopti24-0.0.1/tests/test_transactions_sorting.py +103 -0
  123. apisdkopti24-0.0.1/tests/test_transport.py +846 -0
  124. apisdkopti24-0.0.1/tests/test_transport_budget.py +289 -0
  125. apisdkopti24-0.0.1/tests/test_users.py +331 -0
  126. apisdkopti24-0.0.1/tests/test_utils.py +36 -0
  127. apisdkopti24-0.0.1/tests/test_validation.py +35 -0
  128. apisdkopti24-0.0.1/tests/test_virtual_cards.py +522 -0
  129. apisdkopti24-0.0.1/tests/test_workflow_security.py +128 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrey Raspopov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,422 @@
1
+ Metadata-Version: 2.4
2
+ Name: apisdkopti24
3
+ Version: 0.0.1
4
+ Summary: Асинхронный Python SDK для Opti24 API — API топливных карт АЗС
5
+ Author-email: Andrey Raspopov <andrey.raspopov@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/raspopovaa/apisdkopti24
8
+ Project-URL: Documentation, https://raspopovaa.github.io/apisdkopti24/
9
+ Project-URL: Repository, https://github.com/raspopovaa/apisdkopti24
10
+ Project-URL: Issues, https://github.com/raspopovaa/apisdkopti24/issues
11
+ Keywords: opti24,opti24-api,fuel-cards,fuel-card-api,api-client,asyncio,httpx,sdk,API топливных карт,топливные карты,ОПТИ 24
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: <3.15,>=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: httpx<1.0,>=0.27.0
27
+ Requires-Dist: pydantic<3.0,>=2.13.4
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2.2; extra == "dev"
30
+ Requires-Dist: ruff>=0.5.0; extra == "dev"
31
+ Requires-Dist: black>=24.3.0; extra == "dev"
32
+ Requires-Dist: mkdocs<2.0,>=1.6.1; extra == "dev"
33
+ Requires-Dist: mkdocs-material<10.0,>=9.7.7; extra == "dev"
34
+ Requires-Dist: mike<3.0,>=2.2.0; extra == "dev"
35
+ Requires-Dist: mypy>=1.10.0; extra == "dev"
36
+ Requires-Dist: pyyaml<7.0,>=6.0.2; extra == "dev"
37
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
38
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
39
+ Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
40
+ Dynamic: license-file
41
+
42
+ <div align="center">
43
+
44
+ # apisdkopti24
45
+
46
+ **Асинхронный Python SDK для Opti24 API — корпоративного API топливных карт АЗС (ОПТИ 24)**
47
+
48
+ Карты, договоры, транзакции, отчёты, лимиты и оплата по QR-коду: типизированно,
49
+ безопасно для повторов и без ручной работы с сессией.
50
+
51
+ [![CI](https://github.com/raspopovaa/apisdkopti24/actions/workflows/ci.yml/badge.svg)](https://github.com/raspopovaa/apisdkopti24/actions/workflows/ci.yml)
52
+ [![PyPI](https://img.shields.io/pypi/v/apisdkopti24?color=0f766e)](https://pypi.org/project/apisdkopti24/)
53
+ [![Python](https://img.shields.io/badge/Python-3.11%20%E2%80%93%203.14-3776ab?logo=python&logoColor=white)](https://www.python.org/)
54
+ [![Документация](https://img.shields.io/badge/docs-GitHub%20Pages-0f766e)](https://raspopovaa.github.io/apisdkopti24/)
55
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/raspopovaa/apisdkopti24/blob/main/LICENSE)
56
+
57
+ [Документация](https://raspopovaa.github.io/apisdkopti24/) ·
58
+ [Каталог методов](https://raspopovaa.github.io/apisdkopti24/latest/methods/) ·
59
+ [Учебные примеры](https://raspopovaa.github.io/apisdkopti24/latest/examples/) ·
60
+ [Сообщить об ошибке](https://github.com/raspopovaa/apisdkopti24/issues)
61
+
62
+ <img src="https://raw.githubusercontent.com/raspopovaa/apisdkopti24/main/.github/assets/readme-demo.svg" alt="Демо: три строки кода получают список карт, а SDK проверяет параметры, открывает сессию, соблюдает лимит частоты, разбирает ответ в модель и пишет событие аудита" width="820">
63
+
64
+ </div>
65
+
66
+ > [!IMPORTANT]
67
+ > Проект в разработке и ещё не использовался в production. Проверьте интеграцию
68
+ > на DEMO-стенде, прежде чем подключать рабочий договор.
69
+
70
+ ## О SDK
71
+
72
+ `apisdkopti24` — клиентская библиотека для приложений и сервисов, которые работают
73
+ с корпоративным договором топливных карт ОПТИ 24 через API. Она берёт на себя
74
+ транспорт, сессию, выбор договора, повторы и разбор ответов, чтобы код
75
+ приложения оставался на уровне предметных действий: «получить карты»,
76
+ «заблокировать карту», «заказать отчёт».
77
+
78
+ **Что покрывает.** 89 операций корпоративного API (спецификация 1.1.60) и
79
+ QR API (1.0.4): авторизация и выбор договора, топливные и виртуальные карты,
80
+ группы карт, продуктовые лимиты, региональные ограничения и ограничители
81
+ обслуживания, договоры и документы, транзакции, отчёты, пользователи и
82
+ приглашения, электронный кошелёк, справочники и АЗС, расчёт итоговой стоимости,
83
+ выпуск мобильного профиля карты (МПК) и оплата по QR-коду.
84
+
85
+ **Как устроен.** Один асинхронный `APIClient` объединяет 16 доменных сервисов:
86
+ `client.cards`, `client.transactions`, `client.reports` и другие. Параметры
87
+ проверяются до отправки запроса, ответы превращаются в типизированные
88
+ Pydantic-модели. Маршрут, HTTP-метод, тарификация, доступность на DEMO, класс
89
+ timeout и допустимость повтора каждой операции хранятся в едином реестре,
90
+ сверенном со спецификацией, а не разбросаны по коду.
91
+
92
+ **Где работает.** Корпоративный API доступен на DEMO и в рабочей среде, QR API —
93
+ на своём тестовом стенде и в рабочей среде. DEMO общий для всех клиентов, не
94
+ тарифицируется и поддерживает только часть методов — какие именно, указано в
95
+ [каталоге методов](https://raspopovaa.github.io/apisdkopti24/latest/methods/).
96
+
97
+ **Чего SDK не делает.** Роли, IP-ограничения, квоты, тарифы и видимость объектов
98
+ проверяет сервер. SDK не пытается их обойти, а превращает отказ в понятное
99
+ исключение с очищенным текстом ответа.
100
+
101
+ ## Возможности
102
+
103
+ ### Предметные методы вместо HTTP
104
+
105
+ Один `APIClient` открывает 16 сервисов и 89 операций: `client.cards`,
106
+ `client.transactions`, `client.reports` и другие. При первом вызове SDK сам
107
+ открывает сессию и подставляет договор, а параметры проверяет до отправки
108
+ запроса. Ответ приходит Pydantic-моделью: поля подсказывает IDE, ошибки типов
109
+ находит mypy.
110
+
111
+ ```python
112
+ cards = await client.cards.get_cards_v2(onpage=20)
113
+ for card in cards.result:
114
+ print(card.number, card.status)
115
+ ```
116
+
117
+ ### Сбой сети не превращается в дубль операции
118
+
119
+ - Автоматически повторяются только операции, которые каталог помечает
120
+ безопасными. Перевод средств или блокировка карты после неясного сбоя не
121
+ повторяются. Число попыток и паузы задаёт `RetryPolicy`
122
+ ([как настроить](https://raspopovaa.github.io/apisdkopti24/latest/errors/#retry)).
123
+ - На каждую операцию действует общий срок и лимит попыток; повторы и повторная
124
+ авторизация их не обнуляют.
125
+ - Ответ `401` вызывает одну повторную авторизацию на все параллельные запросы, а
126
+ `409` превращается в `DuplicateConflictError`, а не в успех.
127
+ - Лимит частоты включён по умолчанию: 1 запрос/с. Спецификация заявляет 2 запроса/с
128
+ на DEMO и 5 в рабочей среде, но сервер отвечает `509` уже при 2 запросах/с;
129
+ более высокую частоту по условиям договора задайте через
130
+ `API_REQUESTS_PER_SECOND`
131
+ ([как настроить](https://raspopovaa.github.io/apisdkopti24/latest/configuration/#rate-limit)).
132
+
133
+ Подробнее — в разделе [«Ошибки»](#ошибки).
134
+
135
+ ### Журналы без секретов
136
+
137
+ По умолчанию SDK ничего не пишет. Если включить аудит, на каждую операцию
138
+ приходится ровно одно JSONL-событие с кодом результата, числом попыток и
139
+ временем выполнения. API key, пароли, идентификаторы карт и договоров и тела
140
+ ответов в журнал не попадают, а текст исключений короткий и очищенный.
141
+ Подробнее — в разделе [«Журналирование и аудит»](#журналирование-и-аудит).
142
+
143
+ ### Отчёты и оплата по QR-коду
144
+
145
+ - `download_report_file_to()` пишет отчёт в файл потоком и заменяет файл
146
+ атомарно: большой отчёт не держится в памяти, а недописанный файл не появится.
147
+ - Ответы, которые читаются в память, ограничены по размеру; пределы задаются
148
+ переменными `API_MAX_JSON_RESPONSE_BYTES`, `API_MAX_IN_MEMORY_RESPONSE_BYTES` и
149
+ `API_MAX_ERROR_RESPONSE_BYTES` или полями `max_json_response_bytes`,
150
+ `max_in_memory_response_bytes` и `max_error_response_bytes` в `ConnectionSettings`.
151
+ - Мобильный профиль карты выпускается с подтверждением по SMS
152
+ (`init_mpc` → `confirm_mpc`), а платёжная строка для QR приходит со сроком
153
+ действия `end_date` и счётчиками `tries` и `transaction_count` от сервера.
154
+ Подробнее — в [описании QR-платежей](https://raspopovaa.github.io/apisdkopti24/latest/qr-payments/).
155
+
156
+ ## Установка
157
+
158
+ ```bash
159
+ pip install apisdkopti24==0.0.1
160
+ ```
161
+
162
+ или с [uv](https://docs.astral.sh/uv/):
163
+
164
+ ```bash
165
+ uv add apisdkopti24==0.0.1
166
+ ```
167
+
168
+ Проект в разработке, поэтому закрепляйте проверенную версию явно.
169
+
170
+ ## Быстрый старт
171
+
172
+ Создайте рядом со скриптом файл `.env` и не добавляйте его в Git. Параметры
173
+ DEMO-стенда приведены в
174
+ [спецификации API](https://cdn.opti-24.ru/upload/upload/vip-api/api_specification.docx).
175
+
176
+ ```env
177
+ API_BASE_URL=https://api.example.ru/vip/
178
+ API_KEY=your_api_key
179
+ API_LOGIN=your_login
180
+ API_PASSWORD=your_password
181
+ ```
182
+
183
+ ```python
184
+ import asyncio
185
+ from pathlib import Path
186
+
187
+ from apisdkopti24 import (
188
+ APIClient,
189
+ ConnectionSettings,
190
+ ContractSelectionError,
191
+ EnvironmentCredentialsProvider,
192
+ )
193
+
194
+
195
+ async def main() -> None:
196
+ env_file = Path(__file__).with_name(".env")
197
+ settings = ConnectionSettings.from_env(env_file=env_file)
198
+ credentials = EnvironmentCredentialsProvider.from_env(env_file=env_file)
199
+
200
+ async with APIClient(settings=settings, credentials_provider=credentials) as client:
201
+ try:
202
+ await client.auth.auth_user()
203
+ except ContractSelectionError as exc:
204
+ # Несколько договоров: SDK не выбирает первый без подтверждения.
205
+ for contract_id, contract_number in exc.available_contracts:
206
+ print(contract_id, contract_number)
207
+ await client.auth.auth_user(contract_id=input("ID договора: ").strip())
208
+
209
+ try:
210
+ cards = await client.cards.get_cards_v2(page=1, onpage=5)
211
+ print("Карт найдено:", cards.total_count)
212
+ finally:
213
+ await client.auth.logoff()
214
+
215
+
216
+ if __name__ == "__main__":
217
+ asyncio.run(main())
218
+ ```
219
+
220
+ Создавайте один `APIClient` на всё время жизни приложения, а не на каждый запрос.
221
+ Каждый вызов метода API тарифицируется, если в
222
+ [каталоге методов](https://raspopovaa.github.io/apisdkopti24/latest/methods/)
223
+ не указано обратное; повторы и повторная авторизация тоже расходуют запросы.
224
+ Подробнее — в разделе
225
+ [«Начало работы»](https://raspopovaa.github.io/apisdkopti24/latest/getting-started/).
226
+
227
+ ## Что важно по спецификации
228
+
229
+ - **Договор выбирается явно:** большинство методов используют договор сессии;
230
+ переданный `contract_id` имеет приоритет.
231
+ - **QR-платёж — отдельный контур API.** `generate_payment_qr` возвращает строку
232
+ BER-TLV, а не изображение QR; срок действия нужно проверять по `end_date` перед
233
+ показом. Корпоративный DEMO не проверяет QR-методы.
234
+ - **Суммы переводов и счетов** (`move_to_card`, `move_to_contract`, `order_invoice`)
235
+ SDK передаёт строкой из `Decimal`, без округления; суммы лимитов — числом, как в
236
+ спецификации. Состав запроса может отличаться от привычного REST: например,
237
+ `update_template` использует `POST` с `_method=PUT`.
238
+ - **Тарификация и повторы заданы для каждой операции отдельно.** Не выводите их
239
+ из HTTP-метода: SDK автоматически повторяет только операции, которые каталог
240
+ помечает безопасными, и не повторяет изменение данных после неясного сбоя.
241
+
242
+ Подробности и ограничения — в [описании QR-платежей](https://raspopovaa.github.io/apisdkopti24/latest/qr-payments/),
243
+ [сопоставлении со спецификацией](https://raspopovaa.github.io/apisdkopti24/latest/spec-compatibility/)
244
+ и [каталоге операций](https://raspopovaa.github.io/apisdkopti24/latest/methods/).
245
+
246
+ ## Ошибки
247
+
248
+ SDK проверяет и HTTP-статус, и `status.code` в теле ответа: успешный код в теле не
249
+ скроет неуспешный HTTP-ответ, и наоборот. Ошибки API наследуют `APIError`:
250
+
251
+ | Код | Исключение | Что делает SDK | Что делать приложению |
252
+ |---|---|---|---|
253
+ | `400` | `ValidationError` | — | Исправить параметры запроса |
254
+ | `401` | `NotAuthenticatedError` | Один раз переавторизуется и повторяет запрос | Проверить credentials, если ошибка осталась |
255
+ | `403` | `AccessDeniedError` | Не переавторизуется | Проверить роль, API key, IP, квоту и договор; читать текст сообщения |
256
+ | `404` | `NotFoundError` | — | Проверить идентификатор объекта |
257
+ | `409` | `DuplicateConflictError` | Не повторяет | Считать признаком дубля, а не успехом |
258
+ | `429`, `509` | `RateLimitError` | Повторяет только бесплатное чтение | Снизить частоту: без `509` устойчиво около 1 запроса/с |
259
+ | `5xx` | `ServerError` | Не повторяет | Повторить чтение позже; изменение повторять только после проверки состояния чтением |
260
+
261
+ Локальные сбои не выдают себя за ответ сервера и не наследуют `APIError`:
262
+ `OperationTimeoutError` и `RetryBudgetExceededError` (исчерпан общий бюджет
263
+ времени или попыток), `RequestValidationError` (неверный формат или диапазон
264
+ дат), `ResponseValidationError` (ответ не совпал с моделью),
265
+ `ContractSelectionError` (нужно выбрать договор), `APIConnectionError` (сервер
266
+ недоступен), `APIResponseTimeoutError` и `APINetworkError` (сервер не ответил;
267
+ изменение могло выполниться — проверьте состояние чтением).
268
+
269
+ ```python
270
+ from apisdkopti24 import AccessDeniedError, APIError, OperationTimeoutError
271
+
272
+ try:
273
+ cards = await client.cards.get_cards_v2(page=1, onpage=20)
274
+ except AccessDeniedError as exc:
275
+ # Причина отказа часто есть только в тексте сообщения сервера.
276
+ print("Доступ запрещён:", exc.server_messages)
277
+ except APIError as exc:
278
+ print("Ошибка API:", exc.http_status_code, exc.api_status_code)
279
+ except OperationTimeoutError:
280
+ # Сервер мог получить запрос: это не доказательство, что операция не выполнена.
281
+ print("Истёк общий deadline операции")
282
+ ```
283
+
284
+ `str(exc)` — короткая однострочная строка: email, телефоны, пары
285
+ `ключ=значение` и упоминания PIN или паролей из неё вырезаются. Полный ответ
286
+ сервера доступен только явно через `exc.get_raw_payload()`; не журналируйте его.
287
+
288
+ ## Журналирование и аудит
289
+
290
+ По умолчанию SDK ничего не пишет на диск и не выводит в консоль. Чтобы видеть
291
+ сообщения, подключите обработчик к логгеру `apisdkopti24`:
292
+
293
+ ```python
294
+ import logging
295
+
296
+ logging.getLogger("apisdkopti24").addHandler(logging.StreamHandler())
297
+ logging.getLogger("apisdkopti24").setLevel(logging.INFO)
298
+ ```
299
+
300
+ Файлы журнала включаются явно — в `.env` или в `ConnectionSettings`
301
+ (`logger_file`, `request_log_file`):
302
+
303
+ ```env
304
+ LOGGER_FILE=logs/sdk.log
305
+ REQUEST_LOG_FILE=logs/audit.jsonl
306
+ ```
307
+
308
+ `REQUEST_LOG_FILE` — JSONL-аудит: на каждую операцию приходится ровно одно
309
+ итоговое событие `completed`, `failed` или `cancelled`. Пример события
310
+ (сокращено, значения условные):
311
+
312
+ ```json
313
+ {"event": "failed", "operation": "get_cards_v2", "operation_id": "3f9c…", "sdk_error_code": "api_access_denied", "http_status_code": 403, "api_status_code": 403, "attempts_used": 1, "elapsed_ms": 184, "transient": false, "retry_allowed": false}
314
+ ```
315
+
316
+ - `operation_id` — случайный локальный идентификатор, связывающий события одной
317
+ операции;
318
+ - `sdk_error_code` — стабильный символьный код; у локального timeout это
319
+ `operation_timeout`, а HTTP-код остаётся `null`, а не придумывается;
320
+ - `retry_allowed` уже учитывает идемпотентность метода — решайте о повторе по
321
+ нему, а не по `transient`.
322
+
323
+ В журналы и аудит не попадают API key, пароли, session ID, телефоны, email,
324
+ идентификаторы карт и договоров, PIN, данные МПК, URL с идентификаторами, тела запросов и ответов, а
325
+ также текст сообщений сервера. Ожидаемые отказы (`4xx`, лимит частоты, локальная
326
+ валидация) пишутся с уровнем `WARNING`, серверные и сетевые сбои — `ERROR`,
327
+ отмена задачи — `INFO`.
328
+
329
+ Полный список полей и кодов — в разделе
330
+ [«Ошибки и retry»](https://raspopovaa.github.io/apisdkopti24/latest/errors/), настройка
331
+ журналов — в [«Конфигурации»](https://raspopovaa.github.io/apisdkopti24/latest/configuration/).
332
+
333
+ ## Методы по задачам
334
+
335
+ В SDK **89 операций в 16 сервисах**. Ниже — карта методов; ссылки ведут к полным
336
+ описаниям параметров, тарификации и примерам.
337
+
338
+ | Задача | Сервисы и примеры методов |
339
+ |---|---|
340
+ | **Доступ и пользователи** | [`auth`](https://raspopovaa.github.io/apisdkopti24/latest/methods/auth/) — `auth_user`, `logoff`; [`users`](https://raspopovaa.github.io/apisdkopti24/latest/methods/users/) — `get_users`, `create_user`, `attach_card`; [`invites`](https://raspopovaa.github.io/apisdkopti24/latest/methods/invites/) — `create_invite`, `resend_invite` |
341
+ | **Топливные карты** | [`cards`](https://raspopovaa.github.io/apisdkopti24/latest/methods/cards/) — `get_cards_v2`, `block_card`, `reset_pin`; [`card_groups`](https://raspopovaa.github.io/apisdkopti24/latest/methods/card_groups/) — `get_card_groups`, `set_card_group` |
342
+ | **Договоры и расчёты** | [`contracts`](https://raspopovaa.github.io/apisdkopti24/latest/methods/contracts/) — `get_contract_data`, `get_payments`, `order_invoice`; [`ewallet`](https://raspopovaa.github.io/apisdkopti24/latest/methods/ewallet/) — `move_to_card`, `set_card_product`; [`final_prices`](https://raspopovaa.github.io/apisdkopti24/latest/methods/final_prices/) — `get_final_prices`, `check_purchase` |
343
+ | **Лимиты и шаблоны карт** | [`templates`](https://raspopovaa.github.io/apisdkopti24/latest/methods/templates/) — `create_template`, `create_template_limit`, ограничения; [`limits`](https://raspopovaa.github.io/apisdkopti24/latest/methods/limits/), [`region_limits`](https://raspopovaa.github.io/apisdkopti24/latest/methods/region_limits/), [`restrictions`](https://raspopovaa.github.io/apisdkopti24/latest/methods/restrictions/) |
344
+ | **Операции и отчётность** | [`transactions`](https://raspopovaa.github.io/apisdkopti24/latest/methods/transactions/) — `get_transactions_v2`, `get_transaction_detail`; [`reports`](https://raspopovaa.github.io/apisdkopti24/latest/methods/reports/) — `order_report`, `download_report_file` |
345
+ | **QR и справочники** | [`virtual_cards`](https://raspopovaa.github.io/apisdkopti24/latest/methods/virtual_cards/) — `init_mpc`, `generate_payment_qr`, `get_mpc_qr_list`; [`dictionaries`](https://raspopovaa.github.io/apisdkopti24/latest/methods/dictionaries/) — `get_dictionary`, `get_azs_list_v2` |
346
+
347
+ ## Как проходит запрос
348
+
349
+ Один вызов метода сервиса проходит через восемь шагов. Каждый отвечает за одну
350
+ задачу и лежит в своём модуле.
351
+
352
+ <img src="https://raw.githubusercontent.com/raspopovaa/apisdkopti24/main/.github/assets/readme-request-flow.svg" alt="Путь вызова client.cards.get_cards_v2: 1 — сервис проверяет параметры моделью CardsV2Query (services/cards.py); 2 — создаются общий срок, лимит попыток и событие аудита (executor.py); 3 — при отсутствии сессии выполняется один authUser на все параллельные вызовы (session.py, authentication.py); 4 — по OperationSpec собирается запрос GET v2/cards с заголовками api_key, session_id и contract_id (executor.py, endpoints.py); 5 — запрос занимает один из слотов одновременных запросов, выдерживает интервал лимита частоты и списывает попытку из бюджета, а при 429, 509 или сбое сети повторяется, только если каталог это разрешает (resilience.py); 6 — httpx отправляет запрос без редиректов с timeout, равным остатку срока операции (transport.py); 7 — проверяются HTTP-статус и status.code, при 401 выполняется одна повторная авторизация и повтор (response.py, errors.py); 8 — ответ разбирается в CardsV2Response и пишется итоговое событие аудита (models/cards.py, error_reporting.py)" width="820">
353
+
354
+ Что важно на этом пути:
355
+
356
+ - **Повтор решает каталог операции, а не HTTP-метод.** Пауза и повтор после
357
+ `429`, `509` или сбоя сети возможны только у операций, которые каталог помечает
358
+ безопасными. Перевод средств или блокировка карты не повторяются.
359
+ - **Срок и попытки общие на всю операцию.** Ожидание слота, повторы и повторная
360
+ авторизация расходуют один бюджет, а не начинают отсчёт заново.
361
+ - **Сессия восстанавливается один раз.** Если несколько запросов одновременно
362
+ получили `401`, повторная авторизация выполняется один раз для всех.
363
+
364
+ ## Структура репозитория
365
+
366
+ <img src="https://raw.githubusercontent.com/raspopovaa/apisdkopti24/main/.github/assets/readme-structure.svg" alt="Структура репозитория: слева папки src/apisdkopti24 (код SDK), specifications (каталог операций и контракты API), examples (примеры вызовов), docs (сайт документации), scripts (генераторы и сверка контрактов), tests, tools/spec_contract (аудит спецификации), typecheck и .github/workflows; справа слои кода SDK сверху вниз — публичный фасад (client.py, service_groups.py, composition.py), сервисы и модели (services/, models/, service_base.py, validation.py), реестр операций (endpoints.py, request_metadata.py, registry.py, policies.py), выполнение операции (executor.py, session.py, authentication.py, execution_budget.py), транспорт (transport.py, resilience.py, response.py, downloads.py, file_io.py) и сквозные модули (errors.py, error_reporting.py, logger.py, sanitization.py, config.py, credentials.py)" width="820">
367
+
368
+ <details>
369
+ <summary>Структура текстом</summary>
370
+
371
+ ```text
372
+ src/apisdkopti24/ SDK: клиент, сервисы, модели, транспорт и повторы
373
+ specifications/ каталог 89 операций и контракты API 1.1.60 / QR 1.0.4
374
+ examples/methods/ исполняемые примеры вызовов API
375
+ tests/ модульные проверки и сверка контрактов
376
+ tools/spec_contract/ инструменты валидации спецификаций
377
+ scripts/ генерация и служебные задачи проекта
378
+ typecheck/ проверки типов публичного интерфейса
379
+ docs/ руководство, справочник и разбор совместимости
380
+ .github/workflows/ CI и публикационные процессы
381
+ ```
382
+
383
+ </details>
384
+
385
+ Каталог операций задаёт параметры запросов, тарификацию, идемпотентность и
386
+ политику повтора; он служит источником метаданных SDK и связан с тестами контрактов.
387
+ Подробнее — в [обзоре архитектуры](https://raspopovaa.github.io/apisdkopti24/latest/architecture/).
388
+
389
+ ## Документация
390
+
391
+ | Раздел | Что внутри |
392
+ |---|---|
393
+ | [Начало работы](https://raspopovaa.github.io/apisdkopti24/latest/getting-started/) | Установка, `.env` и первый запрос |
394
+ | [Конфигурация](https://raspopovaa.github.io/apisdkopti24/latest/configuration/) | Timeout, retry, лимит частоты, внедрение зависимостей |
395
+ | [Учебные примеры](https://raspopovaa.github.io/apisdkopti24/latest/examples/) | Пример, HTTP-запрос, ответ и ошибки для каждого из 89 методов |
396
+ | [Оплата по QR-коду](https://raspopovaa.github.io/apisdkopti24/latest/qr-payments/) | Выпуск МПК, срок жизни платёжной строки, блокировки |
397
+ | [Ошибки и retry](https://raspopovaa.github.io/apisdkopti24/latest/errors/) | Исключения, поля аудита и правила безопасных повторов |
398
+ | [Безопасность](https://raspopovaa.github.io/apisdkopti24/latest/security/) | Credentials, журналирование и транспорт |
399
+
400
+ ## Разработка
401
+
402
+ <details>
403
+ <summary>Проверки перед изменением</summary>
404
+
405
+ ```bash
406
+ git clone https://github.com/raspopovaa/apisdkopti24.git && cd apisdkopti24
407
+ uv sync --frozen --all-extras
408
+
409
+ uv run pytest
410
+ uv run ruff check src tests scripts tools typecheck
411
+ uv run black --check src tests scripts tools typecheck
412
+ uv run mypy src/apisdkopti24 typecheck
413
+ ```
414
+
415
+ При изменении API-контрактов выполните дополнительные проверки из
416
+ [руководства по версиям и контрактам](https://raspopovaa.github.io/apisdkopti24/latest/versioning/).
417
+
418
+ </details>
419
+
420
+ ## Лицензия
421
+
422
+ [MIT](https://github.com/raspopovaa/apisdkopti24/blob/main/LICENSE)