gengineapi 0.1.0__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.
- gengineapi/__init__.py +43 -0
- gengineapi/client.py +121 -0
- gengineapi/config.py +242 -0
- gengineapi/exceptions.py +125 -0
- gengineapi/http.py +540 -0
- gengineapi/modules/__init__.py +23 -0
- gengineapi/modules/auth.py +46 -0
- gengineapi/modules/base.py +196 -0
- gengineapi/modules/currencies.py +54 -0
- gengineapi/modules/finances.py +81 -0
- gengineapi/modules/payments.py +115 -0
- gengineapi/modules/transactions.py +70 -0
- gengineapi/modules/users.py +100 -0
- gengineapi-0.1.0.dist-info/METADATA +274 -0
- gengineapi-0.1.0.dist-info/RECORD +17 -0
- gengineapi-0.1.0.dist-info/WHEEL +5 -0
- gengineapi-0.1.0.dist-info/top_level.txt +1 -0
gengineapi/http.py
ADDED
@@ -0,0 +1,540 @@
|
|
1
|
+
"""
|
2
|
+
Модуль, содержащий асинхронный HTTP-клиент для взаимодействия с API G-Engine.
|
3
|
+
|
4
|
+
Поддерживает JWT аутентификацию, обработку различных HTTP-методов,
|
5
|
+
автоматическое форматирование URL, механизм повторных попыток и подробное логирование.
|
6
|
+
Также поддерживает работу через прокси, включая SOCKS5.
|
7
|
+
"""
|
8
|
+
import asyncio
|
9
|
+
import json
|
10
|
+
import logging
|
11
|
+
import time
|
12
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
13
|
+
|
14
|
+
import aiohttp
|
15
|
+
from aiohttp import ClientResponse, ClientSession, ClientTimeout, TCPConnector
|
16
|
+
|
17
|
+
from .exceptions import (
|
18
|
+
ApiConnectionError,
|
19
|
+
ApiError,
|
20
|
+
ApiParsingError,
|
21
|
+
ApiTimeoutError,
|
22
|
+
create_api_error,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
class AsyncHttpClient:
|
27
|
+
"""
|
28
|
+
Асинхронный HTTP-клиент для выполнения запросов к API.
|
29
|
+
|
30
|
+
Attributes:
|
31
|
+
base_url: Базовый URL для API
|
32
|
+
jwt_token: JWT токен для аутентификации
|
33
|
+
timeout: Таймаут для запросов в секундах
|
34
|
+
max_retries: Максимальное количество повторных попыток при ошибках
|
35
|
+
retry_statuses: Список кодов статуса для повторных попыток
|
36
|
+
retry_exceptions: Список исключений для повторных попыток
|
37
|
+
logger: Логгер для записи информации о запросах и ответах
|
38
|
+
proxy: Прокси для запросов (например, 'socks5://user:pass@host:port')
|
39
|
+
"""
|
40
|
+
|
41
|
+
def __init__(
|
42
|
+
self,
|
43
|
+
base_url: str,
|
44
|
+
jwt_token: Optional[str] = None,
|
45
|
+
timeout: int = 30,
|
46
|
+
max_retries: int = 3,
|
47
|
+
retry_statuses: List[int] = None,
|
48
|
+
logger: Optional[logging.Logger] = None,
|
49
|
+
proxy: Optional[str] = None,
|
50
|
+
) -> None:
|
51
|
+
"""
|
52
|
+
Инициализация HTTP-клиента.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
base_url: Базовый URL для API
|
56
|
+
jwt_token: JWT токен для аутентификации (опционально)
|
57
|
+
timeout: Таймаут для запросов в секундах (по умолчанию 30)
|
58
|
+
max_retries: Максимальное количество повторных попыток (по умолчанию 3)
|
59
|
+
retry_statuses: Список кодов статуса для повторных попыток
|
60
|
+
(по умолчанию [429, 500, 502, 503, 504])
|
61
|
+
logger: Логгер для записи информации о запросах и ответах (опционально)
|
62
|
+
proxy: Прокси для запросов в формате 'socks5://user:pass@host:port' (опционально)
|
63
|
+
"""
|
64
|
+
self.base_url = base_url.rstrip("/")
|
65
|
+
self.jwt_token = jwt_token
|
66
|
+
self.timeout = timeout
|
67
|
+
self.max_retries = max_retries
|
68
|
+
self.retry_statuses = retry_statuses or [429, 500, 502, 503, 504]
|
69
|
+
self.retry_exceptions = (
|
70
|
+
aiohttp.ClientError,
|
71
|
+
asyncio.TimeoutError,
|
72
|
+
)
|
73
|
+
self.logger = logger or logging.getLogger(__name__)
|
74
|
+
self.proxy = proxy
|
75
|
+
self._session: Optional[ClientSession] = None
|
76
|
+
self._session_lock = asyncio.Lock()
|
77
|
+
|
78
|
+
# Проверяем, является ли прокси SOCKS5
|
79
|
+
if self.proxy and self.proxy.startswith('socks5://'):
|
80
|
+
try:
|
81
|
+
# Попытка импортировать aiohttp_socks
|
82
|
+
import aiohttp_socks
|
83
|
+
self.logger.info(f"Используется SOCKS5 прокси: {self.proxy}")
|
84
|
+
except ImportError:
|
85
|
+
self.logger.error("Для использования SOCKS5 прокси необходимо установить пакет 'aiohttp-socks'")
|
86
|
+
self.logger.error("Установите его с помощью: pip install aiohttp-socks")
|
87
|
+
self.proxy = None
|
88
|
+
|
89
|
+
async def _ensure_session(self) -> ClientSession:
|
90
|
+
"""
|
91
|
+
Убеждается, что сессия aiohttp существует и валидна.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
ClientSession: Активная aiohttp сессия
|
95
|
+
"""
|
96
|
+
if self._session is None or self._session.closed:
|
97
|
+
async with self._session_lock:
|
98
|
+
if self._session is None or self._session.closed:
|
99
|
+
timeout = ClientTimeout(total=self.timeout)
|
100
|
+
|
101
|
+
# Создаем соответствующий коннектор в зависимости от настроек прокси
|
102
|
+
if self.proxy:
|
103
|
+
if self.proxy.startswith('socks5://'):
|
104
|
+
try:
|
105
|
+
# Импортируем aiohttp_socks для поддержки SOCKS5 прокси
|
106
|
+
from aiohttp_socks import ProxyConnector
|
107
|
+
connector = ProxyConnector.from_url(self.proxy, ssl=False)
|
108
|
+
self.logger.debug(f"Создан SOCKS5 коннектор для прокси: {self.proxy}")
|
109
|
+
except ImportError:
|
110
|
+
self.logger.error("Не удалось импортировать aiohttp_socks")
|
111
|
+
connector = TCPConnector(ssl=False)
|
112
|
+
else:
|
113
|
+
# Для HTTP/HTTPS прокси используем встроенные возможности aiohttp
|
114
|
+
connector = TCPConnector(ssl=False)
|
115
|
+
self.logger.debug(f"Будет использован HTTP прокси: {self.proxy}")
|
116
|
+
else:
|
117
|
+
connector = TCPConnector(ssl=False)
|
118
|
+
|
119
|
+
# Создаем сессию
|
120
|
+
session_kwargs = {
|
121
|
+
'timeout': timeout,
|
122
|
+
'connector': connector,
|
123
|
+
'raise_for_status': False,
|
124
|
+
}
|
125
|
+
|
126
|
+
# Добавляем прокси для HTTP/HTTPS прокси
|
127
|
+
if self.proxy and not self.proxy.startswith('socks5://'):
|
128
|
+
session_kwargs['proxy'] = self.proxy
|
129
|
+
|
130
|
+
self._session = ClientSession(**session_kwargs)
|
131
|
+
|
132
|
+
return self._session
|
133
|
+
|
134
|
+
def _build_url(self, endpoint: str) -> str:
|
135
|
+
"""
|
136
|
+
Формирует полный URL для запроса.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
endpoint: Конечная точка API
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
str: Полный URL для запроса
|
143
|
+
"""
|
144
|
+
endpoint = endpoint.lstrip("/")
|
145
|
+
return f"{self.base_url}/{endpoint}"
|
146
|
+
|
147
|
+
def _get_headers(self, additional_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
148
|
+
"""
|
149
|
+
Формирует заголовки для запроса, включая авторизацию.
|
150
|
+
|
151
|
+
Args:
|
152
|
+
additional_headers: Дополнительные заголовки для запроса
|
153
|
+
|
154
|
+
Returns:
|
155
|
+
Dict[str, str]: Заголовки для запроса
|
156
|
+
"""
|
157
|
+
headers = {
|
158
|
+
"Accept": "application/json",
|
159
|
+
"Content-Type": "application/json",
|
160
|
+
}
|
161
|
+
|
162
|
+
if self.jwt_token:
|
163
|
+
headers["Authorization"] = f"Bearer {self.jwt_token}"
|
164
|
+
|
165
|
+
if additional_headers:
|
166
|
+
headers.update(additional_headers)
|
167
|
+
|
168
|
+
return headers
|
169
|
+
|
170
|
+
async def _parse_response(self, response: ClientResponse) -> Dict[str, Any]:
|
171
|
+
"""
|
172
|
+
Парсит ответ от API.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
response: Ответ от API
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
Dict[str, Any]: Данные из ответа API
|
179
|
+
|
180
|
+
Raises:
|
181
|
+
ApiParsingError: Если не удалось распарсить ответ
|
182
|
+
"""
|
183
|
+
try:
|
184
|
+
if response.content_type.startswith("application/json"):
|
185
|
+
return await response.json()
|
186
|
+
else:
|
187
|
+
text = await response.text()
|
188
|
+
return {"text": text}
|
189
|
+
except (json.JSONDecodeError, aiohttp.ContentTypeError) as e:
|
190
|
+
status_text = await response.text()
|
191
|
+
self.logger.error(f"Ошибка парсинга ответа: {str(e)}, Содержимое: {status_text[:100]}...")
|
192
|
+
raise ApiParsingError(f"Не удалось распарсить ответ: {str(e)}")
|
193
|
+
|
194
|
+
async def _handle_error_response(self, response: ClientResponse) -> None:
|
195
|
+
"""
|
196
|
+
Обрабатывает ответы с ошибками от API.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
response: Ответ от API с ошибкой
|
200
|
+
|
201
|
+
Raises:
|
202
|
+
ApiError: Соответствующее исключение в зависимости от кода ответа
|
203
|
+
"""
|
204
|
+
try:
|
205
|
+
response_data = await self._parse_response(response)
|
206
|
+
except ApiParsingError:
|
207
|
+
response_data = {}
|
208
|
+
|
209
|
+
# Получаем сообщение об ошибке из ответа, если оно есть
|
210
|
+
message = None
|
211
|
+
if isinstance(response_data, dict):
|
212
|
+
message = response_data.get("message")
|
213
|
+
if not message and "detail" in response_data:
|
214
|
+
# Обработка ошибок валидации FastAPI
|
215
|
+
detail = response_data["detail"]
|
216
|
+
if isinstance(detail, list) and detail:
|
217
|
+
errors = [f"{e.get('loc', [''])[0]}: {e.get('msg', '')}" for e in detail if isinstance(e, dict)]
|
218
|
+
message = ", ".join(errors)
|
219
|
+
else:
|
220
|
+
message = str(detail)
|
221
|
+
|
222
|
+
# Создаем соответствующее исключение
|
223
|
+
raise create_api_error(
|
224
|
+
status_code=response.status,
|
225
|
+
message=message,
|
226
|
+
response_data=response_data,
|
227
|
+
)
|
228
|
+
|
229
|
+
async def _request(
|
230
|
+
self,
|
231
|
+
method: str,
|
232
|
+
endpoint: str,
|
233
|
+
params: Optional[Dict[str, Any]] = None,
|
234
|
+
json_data: Optional[Dict[str, Any]] = None,
|
235
|
+
headers: Optional[Dict[str, str]] = None,
|
236
|
+
timeout: Optional[int] = None,
|
237
|
+
) -> Dict[str, Any]:
|
238
|
+
"""
|
239
|
+
Выполняет HTTP-запрос с повторными попытками.
|
240
|
+
|
241
|
+
Args:
|
242
|
+
method: HTTP-метод (GET, POST, PUT, DELETE, etc.)
|
243
|
+
endpoint: Конечная точка API
|
244
|
+
params: Параметры запроса (опционально)
|
245
|
+
json_data: Данные для отправки в формате JSON (опционально)
|
246
|
+
headers: Дополнительные заголовки для запроса (опционально)
|
247
|
+
timeout: Таймаут для запроса в секундах (опционально)
|
248
|
+
|
249
|
+
Returns:
|
250
|
+
Dict[str, Any]: Ответ от API
|
251
|
+
|
252
|
+
Raises:
|
253
|
+
ApiConnectionError: При ошибке соединения
|
254
|
+
ApiTimeoutError: При превышении времени ожидания
|
255
|
+
ApiError: При других ошибках API
|
256
|
+
"""
|
257
|
+
url = self._build_url(endpoint)
|
258
|
+
request_headers = self._get_headers(headers)
|
259
|
+
request_timeout = ClientTimeout(total=timeout or self.timeout)
|
260
|
+
|
261
|
+
attempt = 0
|
262
|
+
last_error = None
|
263
|
+
|
264
|
+
while attempt <= self.max_retries:
|
265
|
+
try:
|
266
|
+
attempt += 1
|
267
|
+
session = await self._ensure_session()
|
268
|
+
|
269
|
+
# Логирование запроса на уровне DEBUG
|
270
|
+
self.logger.debug(
|
271
|
+
f"Запрос {method} {url} (попытка {attempt}/{self.max_retries + 1}). "
|
272
|
+
f"Параметры: {params}, Данные: {json_data}, Заголовки: {request_headers}"
|
273
|
+
)
|
274
|
+
if self.proxy:
|
275
|
+
self.logger.debug(f"Запрос выполняется через прокси: {self.proxy}")
|
276
|
+
|
277
|
+
start_time = time.time()
|
278
|
+
async with session.request(
|
279
|
+
method=method,
|
280
|
+
url=url,
|
281
|
+
params=params,
|
282
|
+
json=json_data,
|
283
|
+
headers=request_headers,
|
284
|
+
timeout=request_timeout,
|
285
|
+
) as response:
|
286
|
+
elapsed_time = time.time() - start_time
|
287
|
+
|
288
|
+
# Логирование ответа на уровне DEBUG
|
289
|
+
self.logger.debug(
|
290
|
+
f"Ответ на {method} {url}: статус {response.status}, "
|
291
|
+
f"заголовки {dict(response.headers)}, время {elapsed_time:.2f}с"
|
292
|
+
)
|
293
|
+
|
294
|
+
# Если успешный ответ
|
295
|
+
if 200 <= response.status < 300:
|
296
|
+
return await self._parse_response(response)
|
297
|
+
|
298
|
+
# Если ошибка, но нужно повторить
|
299
|
+
if response.status in self.retry_statuses and attempt <= self.max_retries:
|
300
|
+
delay = self._calculate_retry_delay(attempt)
|
301
|
+
self.logger.warning(
|
302
|
+
f"Получен статус {response.status} при запросе {method} {url}. "
|
303
|
+
f"Повторная попытка через {delay:.2f}с (попытка {attempt})"
|
304
|
+
)
|
305
|
+
await asyncio.sleep(delay)
|
306
|
+
continue
|
307
|
+
|
308
|
+
# Если ошибка и больше не повторяем
|
309
|
+
await self._handle_error_response(response)
|
310
|
+
|
311
|
+
except self.retry_exceptions as e:
|
312
|
+
last_error = e
|
313
|
+
if attempt <= self.max_retries:
|
314
|
+
delay = self._calculate_retry_delay(attempt)
|
315
|
+
error_type = type(e).__name__
|
316
|
+
self.logger.warning(
|
317
|
+
f"Ошибка {error_type} при запросе {method} {url}: {str(e)}. "
|
318
|
+
f"Повторная попытка через {delay:.2f}с (попытка {attempt})"
|
319
|
+
)
|
320
|
+
await asyncio.sleep(delay)
|
321
|
+
else:
|
322
|
+
break
|
323
|
+
|
324
|
+
# Если все попытки неудачные
|
325
|
+
if isinstance(last_error, asyncio.TimeoutError):
|
326
|
+
raise ApiTimeoutError(f"Превышено время ожидания при запросе {method} {url} после {attempt} попыток")
|
327
|
+
else:
|
328
|
+
raise ApiConnectionError(f"Ошибка соединения при запросе {method} {url} после {attempt} попыток: {last_error}")
|
329
|
+
|
330
|
+
def _calculate_retry_delay(self, attempt: int) -> float:
|
331
|
+
"""
|
332
|
+
Вычисляет задержку перед повторной попыткой с экспоненциальным ростом.
|
333
|
+
|
334
|
+
Args:
|
335
|
+
attempt: Номер попытки (начиная с 1)
|
336
|
+
|
337
|
+
Returns:
|
338
|
+
float: Задержка в секундах
|
339
|
+
"""
|
340
|
+
# Экспоненциальная задержка с небольшим случайным разбросом
|
341
|
+
import random
|
342
|
+
base_delay = min(2 ** (attempt - 1), 30) # Ограничиваем до 30 секунд
|
343
|
+
jitter = random.uniform(0, 0.5 * base_delay) # Добавляем до 50% случайности
|
344
|
+
return base_delay + jitter
|
345
|
+
|
346
|
+
async def get(
|
347
|
+
self,
|
348
|
+
endpoint: str,
|
349
|
+
params: Optional[Dict[str, Any]] = None,
|
350
|
+
headers: Optional[Dict[str, str]] = None,
|
351
|
+
timeout: Optional[int] = None,
|
352
|
+
) -> Dict[str, Any]:
|
353
|
+
"""
|
354
|
+
Выполняет GET-запрос.
|
355
|
+
|
356
|
+
Args:
|
357
|
+
endpoint: Конечная точка API
|
358
|
+
params: Параметры запроса (опционально)
|
359
|
+
headers: Дополнительные заголовки для запроса (опционально)
|
360
|
+
timeout: Таймаут для запроса в секундах (опционально)
|
361
|
+
|
362
|
+
Returns:
|
363
|
+
Dict[str, Any]: Ответ от API
|
364
|
+
"""
|
365
|
+
return await self._request("GET", endpoint, params=params, headers=headers, timeout=timeout)
|
366
|
+
|
367
|
+
async def post(
|
368
|
+
self,
|
369
|
+
endpoint: str,
|
370
|
+
json_data: Optional[Dict[str, Any]] = None,
|
371
|
+
params: Optional[Dict[str, Any]] = None,
|
372
|
+
headers: Optional[Dict[str, str]] = None,
|
373
|
+
timeout: Optional[int] = None,
|
374
|
+
) -> Dict[str, Any]:
|
375
|
+
"""
|
376
|
+
Выполняет POST-запрос.
|
377
|
+
|
378
|
+
Args:
|
379
|
+
endpoint: Конечная точка API
|
380
|
+
json_data: Данные для отправки в формате JSON (опционально)
|
381
|
+
params: Параметры запроса (опционально)
|
382
|
+
headers: Дополнительные заголовки для запроса (опционально)
|
383
|
+
timeout: Таймаут для запроса в секундах (опционально)
|
384
|
+
|
385
|
+
Returns:
|
386
|
+
Dict[str, Any]: Ответ от API
|
387
|
+
"""
|
388
|
+
return await self._request("POST", endpoint, params=params, json_data=json_data, headers=headers, timeout=timeout)
|
389
|
+
|
390
|
+
async def put(
|
391
|
+
self,
|
392
|
+
endpoint: str,
|
393
|
+
json_data: Optional[Dict[str, Any]] = None,
|
394
|
+
params: Optional[Dict[str, Any]] = None,
|
395
|
+
headers: Optional[Dict[str, str]] = None,
|
396
|
+
timeout: Optional[int] = None,
|
397
|
+
) -> Dict[str, Any]:
|
398
|
+
"""
|
399
|
+
Выполняет PUT-запрос.
|
400
|
+
|
401
|
+
Args:
|
402
|
+
endpoint: Конечная точка API
|
403
|
+
json_data: Данные для отправки в формате JSON (опционально)
|
404
|
+
params: Параметры запроса (опционально)
|
405
|
+
headers: Дополнительные заголовки для запроса (опционально)
|
406
|
+
timeout: Таймаут для запроса в секундах (опционально)
|
407
|
+
|
408
|
+
Returns:
|
409
|
+
Dict[str, Any]: Ответ от API
|
410
|
+
"""
|
411
|
+
return await self._request("PUT", endpoint, params=params, json_data=json_data, headers=headers, timeout=timeout)
|
412
|
+
|
413
|
+
async def delete(
|
414
|
+
self,
|
415
|
+
endpoint: str,
|
416
|
+
params: Optional[Dict[str, Any]] = None,
|
417
|
+
json_data: Optional[Dict[str, Any]] = None,
|
418
|
+
headers: Optional[Dict[str, str]] = None,
|
419
|
+
timeout: Optional[int] = None,
|
420
|
+
) -> Dict[str, Any]:
|
421
|
+
"""
|
422
|
+
Выполняет DELETE-запрос.
|
423
|
+
|
424
|
+
Args:
|
425
|
+
endpoint: Конечная точка API
|
426
|
+
params: Параметры запроса (опционально)
|
427
|
+
json_data: Данные для отправки в формате JSON (опционально)
|
428
|
+
headers: Дополнительные заголовки для запроса (опционально)
|
429
|
+
timeout: Таймаут для запроса в секундах (опционально)
|
430
|
+
|
431
|
+
Returns:
|
432
|
+
Dict[str, Any]: Ответ от API
|
433
|
+
"""
|
434
|
+
return await self._request("DELETE", endpoint, params=params, json_data=json_data, headers=headers, timeout=timeout)
|
435
|
+
|
436
|
+
async def patch(
|
437
|
+
self,
|
438
|
+
endpoint: str,
|
439
|
+
json_data: Optional[Dict[str, Any]] = None,
|
440
|
+
params: Optional[Dict[str, Any]] = None,
|
441
|
+
headers: Optional[Dict[str, str]] = None,
|
442
|
+
timeout: Optional[int] = None,
|
443
|
+
) -> Dict[str, Any]:
|
444
|
+
"""
|
445
|
+
Выполняет PATCH-запрос.
|
446
|
+
|
447
|
+
Args:
|
448
|
+
endpoint: Конечная точка API
|
449
|
+
json_data: Данные для отправки в формате JSON (опционально)
|
450
|
+
params: Параметры запроса (опционально)
|
451
|
+
headers: Дополнительные заголовки для запроса (опционально)
|
452
|
+
timeout: Таймаут для запроса в секундах (опционально)
|
453
|
+
|
454
|
+
Returns:
|
455
|
+
Dict[str, Any]: Ответ от API
|
456
|
+
"""
|
457
|
+
return await self._request("PATCH", endpoint, params=params, json_data=json_data, headers=headers, timeout=timeout)
|
458
|
+
|
459
|
+
async def head(
|
460
|
+
self,
|
461
|
+
endpoint: str,
|
462
|
+
params: Optional[Dict[str, Any]] = None,
|
463
|
+
headers: Optional[Dict[str, str]] = None,
|
464
|
+
timeout: Optional[int] = None,
|
465
|
+
) -> Dict[str, Any]:
|
466
|
+
"""
|
467
|
+
Выполняет HEAD-запрос.
|
468
|
+
|
469
|
+
Args:
|
470
|
+
endpoint: Конечная точка API
|
471
|
+
params: Параметры запроса (опционально)
|
472
|
+
headers: Дополнительные заголовки для запроса (опционально)
|
473
|
+
timeout: Таймаут для запроса в секундах (опционально)
|
474
|
+
|
475
|
+
Returns:
|
476
|
+
Dict[str, Any]: Ответ от API
|
477
|
+
"""
|
478
|
+
return await self._request("HEAD", endpoint, params=params, headers=headers, timeout=timeout)
|
479
|
+
|
480
|
+
async def options(
|
481
|
+
self,
|
482
|
+
endpoint: str,
|
483
|
+
params: Optional[Dict[str, Any]] = None,
|
484
|
+
headers: Optional[Dict[str, str]] = None,
|
485
|
+
timeout: Optional[int] = None,
|
486
|
+
) -> Dict[str, Any]:
|
487
|
+
"""
|
488
|
+
Выполняет OPTIONS-запрос.
|
489
|
+
|
490
|
+
Args:
|
491
|
+
endpoint: Конечная точка API
|
492
|
+
params: Параметры запроса (опционально)
|
493
|
+
headers: Дополнительные заголовки для запроса (опционально)
|
494
|
+
timeout: Таймаут для запроса в секундах (опционально)
|
495
|
+
|
496
|
+
Returns:
|
497
|
+
Dict[str, Any]: Ответ от API
|
498
|
+
"""
|
499
|
+
return await self._request("OPTIONS", endpoint, params=params, headers=headers, timeout=timeout)
|
500
|
+
|
501
|
+
async def close(self) -> None:
|
502
|
+
"""Закрывает сессию aiohttp."""
|
503
|
+
if self._session and not self._session.closed:
|
504
|
+
await self._session.close()
|
505
|
+
self._session = None
|
506
|
+
|
507
|
+
def update_token(self, jwt_token: str) -> None:
|
508
|
+
"""
|
509
|
+
Обновляет JWT токен для аутентификации.
|
510
|
+
|
511
|
+
Args:
|
512
|
+
jwt_token: Новый JWT токен
|
513
|
+
"""
|
514
|
+
self.jwt_token = jwt_token
|
515
|
+
|
516
|
+
def update_proxy(self, proxy: Optional[str] = None) -> None:
|
517
|
+
"""
|
518
|
+
Обновляет настройки прокси.
|
519
|
+
|
520
|
+
Args:
|
521
|
+
proxy: Новый прокси в формате 'socks5://user:pass@host:port' или None для отключения прокси
|
522
|
+
"""
|
523
|
+
# Если прокси изменился, нужно закрыть существующую сессию
|
524
|
+
if self.proxy != proxy:
|
525
|
+
self.proxy = proxy
|
526
|
+
|
527
|
+
# Закрываем сессию, чтобы при следующем запросе она была пересоздана с новыми настройками
|
528
|
+
if self._session and not self._session.closed:
|
529
|
+
asyncio.create_task(self._session.close())
|
530
|
+
self._session = None
|
531
|
+
|
532
|
+
# Проверяем, если указан SOCKS5 прокси, нужен ли aiohttp_socks
|
533
|
+
if self.proxy and self.proxy.startswith('socks5://'):
|
534
|
+
try:
|
535
|
+
import aiohttp_socks
|
536
|
+
self.logger.info(f"Используется SOCKS5 прокси: {self.proxy}")
|
537
|
+
except ImportError:
|
538
|
+
self.logger.error("Для использования SOCKS5 прокси необходимо установить пакет 'aiohttp-socks'")
|
539
|
+
self.logger.error("Установите его с помощью: pip install aiohttp-socks")
|
540
|
+
self.proxy = None
|
@@ -0,0 +1,23 @@
|
|
1
|
+
"""
|
2
|
+
Пакет с модулями API клиента G-Engine.
|
3
|
+
|
4
|
+
Экспортирует классы модулей для различных функциональных областей API.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .auth import AuthModule
|
8
|
+
from .base import BaseApiModule
|
9
|
+
from .currencies import CurrenciesModule
|
10
|
+
from .finances import FinancesModule
|
11
|
+
from .payments import PaymentsModule
|
12
|
+
from .transactions import TransactionsModule
|
13
|
+
from .users import UsersModule
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
'AuthModule',
|
17
|
+
'BaseApiModule',
|
18
|
+
'CurrenciesModule',
|
19
|
+
'FinancesModule',
|
20
|
+
'PaymentsModule',
|
21
|
+
'TransactionsModule',
|
22
|
+
'UsersModule',
|
23
|
+
]
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"""
|
2
|
+
Модуль для работы с аутентификацией в API G-Engine.
|
3
|
+
|
4
|
+
Предоставляет методы для получения токенов доступа.
|
5
|
+
"""
|
6
|
+
from typing import Any, Dict
|
7
|
+
|
8
|
+
from .base import BaseApiModule
|
9
|
+
|
10
|
+
|
11
|
+
class AuthModule(BaseApiModule):
|
12
|
+
"""
|
13
|
+
Модуль для работы с аутентификацией.
|
14
|
+
|
15
|
+
Предоставляет методы для аутентификации пользователя и получения JWT-токена.
|
16
|
+
"""
|
17
|
+
|
18
|
+
async def login(self, login: str, password: str) -> Dict[str, Any]:
|
19
|
+
"""
|
20
|
+
Получает токен доступа на основе логина и пароля.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
login: Логин пользователя
|
24
|
+
password: Пароль пользователя
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
Dict[str, Any]: Информация о токене доступа
|
28
|
+
|
29
|
+
Examples:
|
30
|
+
>>> async with Client(base_url="https://api.example.com") as client:
|
31
|
+
>>> token_info = await client.auth.login(
|
32
|
+
>>> login="user@example.com",
|
33
|
+
>>> password="password123"
|
34
|
+
>>> )
|
35
|
+
>>> print(f"Получен токен: {token_info['access_token']}")
|
36
|
+
>>>
|
37
|
+
>>> # Обновляем токен в клиенте
|
38
|
+
>>> client.update_token(token_info['access_token'])
|
39
|
+
"""
|
40
|
+
data = {
|
41
|
+
"login": login,
|
42
|
+
"password": password,
|
43
|
+
}
|
44
|
+
|
45
|
+
self.logger.info(f"Аутентификация пользователя: {login}")
|
46
|
+
return await self._post("auth/token", data=data)
|