gengineapi 0.1.0__py3-none-any.whl → 0.2.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 +4 -0
- gengineapi/farm.py +464 -0
- {gengineapi-0.1.0.dist-info → gengineapi-0.2.0.dist-info}/METADATA +82 -75
- {gengineapi-0.1.0.dist-info → gengineapi-0.2.0.dist-info}/RECORD +6 -5
- {gengineapi-0.1.0.dist-info → gengineapi-0.2.0.dist-info}/WHEEL +0 -0
- {gengineapi-0.1.0.dist-info → gengineapi-0.2.0.dist-info}/top_level.txt +0 -0
gengineapi/__init__.py
CHANGED
@@ -19,6 +19,7 @@ from .exceptions import (
|
|
19
19
|
ApiTimeoutError,
|
20
20
|
ApiValidationError,
|
21
21
|
)
|
22
|
+
from .farm import ClientFarm, ClientConfig
|
22
23
|
from .http import AsyncHttpClient
|
23
24
|
|
24
25
|
__version__ = "1.0.0"
|
@@ -29,6 +30,9 @@ __all__ = [
|
|
29
30
|
'GEngineConfig',
|
30
31
|
# HTTP клиент
|
31
32
|
'AsyncHttpClient',
|
33
|
+
# Ферма клиентов
|
34
|
+
'ClientFarm',
|
35
|
+
'ClientConfig',
|
32
36
|
# Исключения
|
33
37
|
'ApiError',
|
34
38
|
'ApiConnectionError',
|
gengineapi/farm.py
ADDED
@@ -0,0 +1,464 @@
|
|
1
|
+
"""
|
2
|
+
Модуль, предоставляющий функциональность для управления фермой клиентов G-Engine API.
|
3
|
+
|
4
|
+
Позволяет создавать и управлять несколькими независимыми клиентами
|
5
|
+
с различными конфигурациями, прокси и токенами аутентификации.
|
6
|
+
"""
|
7
|
+
import asyncio
|
8
|
+
import logging
|
9
|
+
import uuid
|
10
|
+
from typing import Dict, Optional, List, Any
|
11
|
+
|
12
|
+
from .client import GEngineClient
|
13
|
+
|
14
|
+
|
15
|
+
class ClientConfig:
|
16
|
+
"""
|
17
|
+
Класс конфигурации для отдельного клиента.
|
18
|
+
|
19
|
+
Позволяет хранить и управлять параметрами для создания клиента.
|
20
|
+
|
21
|
+
Attributes:
|
22
|
+
name: Уникальное имя конфигурации
|
23
|
+
base_url: Базовый URL для API
|
24
|
+
jwt_token: JWT токен для аутентификации
|
25
|
+
timeout: Таймаут для запросов в секундах
|
26
|
+
max_retries: Максимальное количество повторных попыток
|
27
|
+
logger: Логгер для записи информации
|
28
|
+
proxy: Прокси для запросов
|
29
|
+
client: Экземпляр клиента, если он был создан
|
30
|
+
"""
|
31
|
+
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
base_url: str,
|
35
|
+
jwt_token: Optional[str] = None,
|
36
|
+
timeout: int = 30,
|
37
|
+
max_retries: int = 3,
|
38
|
+
logger: Optional[logging.Logger] = None,
|
39
|
+
proxy: Optional[str] = None,
|
40
|
+
name: Optional[str] = None, # Уникальное имя конфига
|
41
|
+
tags: Optional[List[str]] = None, # Теги для группировки и фильтрации
|
42
|
+
) -> None:
|
43
|
+
"""
|
44
|
+
Инициализация конфигурации клиента.
|
45
|
+
|
46
|
+
Args:
|
47
|
+
base_url: Базовый URL для API
|
48
|
+
jwt_token: JWT токен для аутентификации (опционально)
|
49
|
+
timeout: Таймаут для запросов в секундах (по умолчанию 30)
|
50
|
+
max_retries: Максимальное количество повторных попыток (по умолчанию 3)
|
51
|
+
logger: Логгер для записи информации (опционально)
|
52
|
+
proxy: Прокси для запросов в формате 'socks5://user:pass@host:port' (опционально)
|
53
|
+
name: Уникальное имя конфигурации (опционально, генерируется автоматически)
|
54
|
+
tags: Список тегов для группировки и фильтрации (опционально)
|
55
|
+
"""
|
56
|
+
self.base_url = base_url
|
57
|
+
self.jwt_token = jwt_token
|
58
|
+
self.timeout = timeout
|
59
|
+
self.max_retries = max_retries
|
60
|
+
self.logger = logger
|
61
|
+
self.proxy = proxy
|
62
|
+
self.name = name or str(uuid.uuid4())
|
63
|
+
self.tags = tags or []
|
64
|
+
self.client: Optional[GEngineClient] = None
|
65
|
+
self.last_used: float = 0 # Время последнего использования (для ротации)
|
66
|
+
self.error_count: int = 0 # Счетчик ошибок
|
67
|
+
self.success_count: int = 0 # Счетчик успешных запросов
|
68
|
+
self.is_active: bool = True # Флаг активности конфигурации
|
69
|
+
|
70
|
+
async def get_client(self) -> GEngineClient:
|
71
|
+
"""
|
72
|
+
Возвращает клиент или создает новый, если он не существует.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
GEngineClient: Экземпляр клиента API
|
76
|
+
"""
|
77
|
+
if self.client is None:
|
78
|
+
self.client = GEngineClient(
|
79
|
+
base_url=self.base_url,
|
80
|
+
jwt_token=self.jwt_token,
|
81
|
+
timeout=self.timeout,
|
82
|
+
max_retries=self.max_retries,
|
83
|
+
logger=self.logger,
|
84
|
+
proxy=self.proxy
|
85
|
+
)
|
86
|
+
|
87
|
+
# Обновляем время последнего использования
|
88
|
+
import time
|
89
|
+
self.last_used = time.time()
|
90
|
+
|
91
|
+
return self.client
|
92
|
+
|
93
|
+
async def close(self) -> None:
|
94
|
+
"""Закрывает клиент."""
|
95
|
+
if self.client:
|
96
|
+
await self.client.close()
|
97
|
+
self.client = None
|
98
|
+
|
99
|
+
def update_token(self, jwt_token: str) -> None:
|
100
|
+
"""
|
101
|
+
Обновляет JWT токен в конфигурации и в клиенте, если он существует.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
jwt_token: Новый JWT токен
|
105
|
+
"""
|
106
|
+
self.jwt_token = jwt_token
|
107
|
+
if self.client:
|
108
|
+
self.client.update_token(jwt_token)
|
109
|
+
|
110
|
+
def update_proxy(self, proxy: Optional[str] = None) -> None:
|
111
|
+
"""
|
112
|
+
Обновляет настройки прокси в конфигурации и в клиенте, если он существует.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
proxy: Новый прокси или None для отключения прокси
|
116
|
+
"""
|
117
|
+
self.proxy = proxy
|
118
|
+
if self.client:
|
119
|
+
if hasattr(self.client, 'update_proxy'):
|
120
|
+
self.client.update_proxy(proxy)
|
121
|
+
else:
|
122
|
+
# Если клиент не поддерживает динамическое обновление прокси,
|
123
|
+
# закрываем его и создадим новый при следующем запросе
|
124
|
+
asyncio.create_task(self.close())
|
125
|
+
|
126
|
+
def deactivate(self) -> None:
|
127
|
+
"""Деактивирует конфигурацию."""
|
128
|
+
self.is_active = False
|
129
|
+
|
130
|
+
def activate(self) -> None:
|
131
|
+
"""Активирует конфигурацию."""
|
132
|
+
self.is_active = True
|
133
|
+
|
134
|
+
def to_dict(self) -> Dict[str, Any]:
|
135
|
+
"""
|
136
|
+
Преобразует конфигурацию в словарь.
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
Dict[str, Any]: Словарь с параметрами конфигурации
|
140
|
+
"""
|
141
|
+
return {
|
142
|
+
"name": self.name,
|
143
|
+
"base_url": self.base_url,
|
144
|
+
"jwt_token": self.jwt_token,
|
145
|
+
"timeout": self.timeout,
|
146
|
+
"max_retries": self.max_retries,
|
147
|
+
"proxy": self.proxy,
|
148
|
+
"tags": self.tags,
|
149
|
+
"is_active": self.is_active,
|
150
|
+
"error_count": self.error_count,
|
151
|
+
"success_count": self.success_count,
|
152
|
+
"last_used": self.last_used,
|
153
|
+
}
|
154
|
+
|
155
|
+
|
156
|
+
class ClientFarm:
|
157
|
+
"""
|
158
|
+
Управляет фермой клиентов с разными конфигурациями.
|
159
|
+
|
160
|
+
Позволяет создавать, хранить и использовать множество клиентов
|
161
|
+
с разными настройками, токенами и прокси.
|
162
|
+
"""
|
163
|
+
|
164
|
+
def __init__(self, logger: Optional[logging.Logger] = None) -> None:
|
165
|
+
"""
|
166
|
+
Инициализация фермы клиентов.
|
167
|
+
|
168
|
+
Args:
|
169
|
+
logger: Логгер для записи информации (опционально)
|
170
|
+
"""
|
171
|
+
self.configs: Dict[str, ClientConfig] = {}
|
172
|
+
self.logger = logger or logging.getLogger(__name__)
|
173
|
+
self._selection_strategy: str = "round_robin" # Стратегия выбора клиента
|
174
|
+
self._last_used_index: int = -1 # Индекс последнего использованного клиента
|
175
|
+
|
176
|
+
def add_config(self, config: ClientConfig) -> None:
|
177
|
+
"""
|
178
|
+
Добавляет конфигурацию в ферму.
|
179
|
+
|
180
|
+
Args:
|
181
|
+
config: Конфигурация клиента
|
182
|
+
"""
|
183
|
+
self.configs[config.name] = config
|
184
|
+
self.logger.info(f"Добавлена конфигурация: {config.name}")
|
185
|
+
|
186
|
+
def create_config(self, **kwargs) -> ClientConfig:
|
187
|
+
"""
|
188
|
+
Создает и добавляет новую конфигурацию.
|
189
|
+
|
190
|
+
Args:
|
191
|
+
**kwargs: Параметры для создания конфигурации
|
192
|
+
|
193
|
+
Returns:
|
194
|
+
ClientConfig: Созданная конфигурация
|
195
|
+
"""
|
196
|
+
config = ClientConfig(**kwargs)
|
197
|
+
self.add_config(config)
|
198
|
+
return config
|
199
|
+
|
200
|
+
async def get_client(self, name: str) -> Optional[GEngineClient]:
|
201
|
+
"""
|
202
|
+
Возвращает клиент по имени конфигурации.
|
203
|
+
|
204
|
+
Args:
|
205
|
+
name: Имя конфигурации
|
206
|
+
|
207
|
+
Returns:
|
208
|
+
Optional[GEngineClient]: Экземпляр клиента или None, если конфигурация не найдена
|
209
|
+
"""
|
210
|
+
config = self.configs.get(name)
|
211
|
+
if config and config.is_active:
|
212
|
+
return await config.get_client()
|
213
|
+
return None
|
214
|
+
|
215
|
+
async def get_next_client(self, tag: Optional[str] = None) -> Optional[GEngineClient]:
|
216
|
+
"""
|
217
|
+
Возвращает следующий клиент согласно выбранной стратегии.
|
218
|
+
|
219
|
+
Args:
|
220
|
+
tag: Тег для фильтрации клиентов (опционально)
|
221
|
+
|
222
|
+
Returns:
|
223
|
+
Optional[GEngineClient]: Экземпляр клиента или None, если нет доступных клиентов
|
224
|
+
"""
|
225
|
+
active_configs = [
|
226
|
+
config for config in self.configs.values()
|
227
|
+
if config.is_active and (tag is None or tag in config.tags)
|
228
|
+
]
|
229
|
+
|
230
|
+
if not active_configs:
|
231
|
+
self.logger.warning("Нет доступных конфигураций клиентов")
|
232
|
+
return None
|
233
|
+
|
234
|
+
if self._selection_strategy == "round_robin":
|
235
|
+
# Стратегия Round Robin
|
236
|
+
self._last_used_index = (self._last_used_index + 1) % len(active_configs)
|
237
|
+
config = active_configs[self._last_used_index]
|
238
|
+
return await config.get_client()
|
239
|
+
|
240
|
+
elif self._selection_strategy == "random":
|
241
|
+
# Случайный выбор
|
242
|
+
import random
|
243
|
+
config = random.choice(active_configs)
|
244
|
+
return await config.get_client()
|
245
|
+
|
246
|
+
elif self._selection_strategy == "least_errors":
|
247
|
+
# Выбираем клиент с наименьшим количеством ошибок
|
248
|
+
config = min(active_configs, key=lambda c: c.error_count)
|
249
|
+
return await config.get_client()
|
250
|
+
|
251
|
+
elif self._selection_strategy == "least_recently_used":
|
252
|
+
# Выбираем наименее недавно использованный клиент
|
253
|
+
config = min(active_configs, key=lambda c: c.last_used)
|
254
|
+
return await config.get_client()
|
255
|
+
|
256
|
+
else:
|
257
|
+
# По умолчанию используем Round Robin
|
258
|
+
self._last_used_index = (self._last_used_index + 1) % len(active_configs)
|
259
|
+
config = active_configs[self._last_used_index]
|
260
|
+
return await config.get_client()
|
261
|
+
|
262
|
+
def set_selection_strategy(self, strategy: str) -> None:
|
263
|
+
"""
|
264
|
+
Устанавливает стратегию выбора клиента.
|
265
|
+
|
266
|
+
Args:
|
267
|
+
strategy: Стратегия выбора ('round_robin', 'random', 'least_errors', 'least_recently_used')
|
268
|
+
"""
|
269
|
+
valid_strategies = ["round_robin", "random", "least_errors", "least_recently_used"]
|
270
|
+
if strategy not in valid_strategies:
|
271
|
+
raise ValueError(f"Недопустимая стратегия выбора: {strategy}. "
|
272
|
+
f"Должна быть одна из: {', '.join(valid_strategies)}")
|
273
|
+
|
274
|
+
self._selection_strategy = strategy
|
275
|
+
self.logger.info(f"Установлена стратегия выбора клиента: {strategy}")
|
276
|
+
|
277
|
+
async def close_client(self, name: str) -> None:
|
278
|
+
"""
|
279
|
+
Закрывает клиент по имени конфигурации.
|
280
|
+
|
281
|
+
Args:
|
282
|
+
name: Имя конфигурации
|
283
|
+
"""
|
284
|
+
config = self.configs.get(name)
|
285
|
+
if config:
|
286
|
+
await config.close()
|
287
|
+
self.logger.info(f"Клиент закрыт: {name}")
|
288
|
+
|
289
|
+
async def close_all(self) -> None:
|
290
|
+
"""Закрывает все клиенты в ферме."""
|
291
|
+
for name, config in self.configs.items():
|
292
|
+
await config.close()
|
293
|
+
self.logger.info(f"Клиент закрыт: {name}")
|
294
|
+
|
295
|
+
def remove_config(self, name: str) -> None:
|
296
|
+
"""
|
297
|
+
Удаляет конфигурацию из фермы.
|
298
|
+
|
299
|
+
Args:
|
300
|
+
name: Имя конфигурации
|
301
|
+
"""
|
302
|
+
if name in self.configs:
|
303
|
+
config = self.configs[name]
|
304
|
+
# Запустим задачу на закрытие клиента, если он существует
|
305
|
+
if config.client:
|
306
|
+
asyncio.create_task(config.close())
|
307
|
+
del self.configs[name]
|
308
|
+
self.logger.info(f"Конфигурация удалена: {name}")
|
309
|
+
|
310
|
+
def get_configs_by_tag(self, tag: str) -> List[ClientConfig]:
|
311
|
+
"""
|
312
|
+
Возвращает список конфигураций с указанным тегом.
|
313
|
+
|
314
|
+
Args:
|
315
|
+
tag: Тег для фильтрации
|
316
|
+
|
317
|
+
Returns:
|
318
|
+
List[ClientConfig]: Список конфигураций с указанным тегом
|
319
|
+
"""
|
320
|
+
return [config for config in self.configs.values() if tag in config.tags]
|
321
|
+
|
322
|
+
def get_active_configs(self) -> List[ClientConfig]:
|
323
|
+
"""
|
324
|
+
Возвращает список активных конфигураций.
|
325
|
+
|
326
|
+
Returns:
|
327
|
+
List[ClientConfig]: Список активных конфигураций
|
328
|
+
"""
|
329
|
+
return [config for config in self.configs.values() if config.is_active]
|
330
|
+
|
331
|
+
def deactivate_config(self, name: str) -> None:
|
332
|
+
"""
|
333
|
+
Деактивирует конфигурацию.
|
334
|
+
|
335
|
+
Args:
|
336
|
+
name: Имя конфигурации
|
337
|
+
"""
|
338
|
+
if name in self.configs:
|
339
|
+
self.configs[name].deactivate()
|
340
|
+
self.logger.info(f"Конфигурация деактивирована: {name}")
|
341
|
+
|
342
|
+
def activate_config(self, name: str) -> None:
|
343
|
+
"""
|
344
|
+
Активирует конфигурацию.
|
345
|
+
|
346
|
+
Args:
|
347
|
+
name: Имя конфигурации
|
348
|
+
"""
|
349
|
+
if name in self.configs:
|
350
|
+
self.configs[name].activate()
|
351
|
+
self.logger.info(f"Конфигурация активирована: {name}")
|
352
|
+
|
353
|
+
def register_error(self, name: str) -> None:
|
354
|
+
"""
|
355
|
+
Регистрирует ошибку для указанной конфигурации.
|
356
|
+
|
357
|
+
Args:
|
358
|
+
name: Имя конфигурации
|
359
|
+
"""
|
360
|
+
if name in self.configs:
|
361
|
+
self.configs[name].error_count += 1
|
362
|
+
|
363
|
+
# Если превышен порог ошибок, деактивируем конфигурацию
|
364
|
+
if self.configs[name].error_count >= 10: # Пример порога
|
365
|
+
self.configs[name].deactivate()
|
366
|
+
self.logger.warning(f"Конфигурация деактивирована из-за ошибок: {name}")
|
367
|
+
|
368
|
+
def register_success(self, name: str) -> None:
|
369
|
+
"""
|
370
|
+
Регистрирует успешное использование указанной конфигурации.
|
371
|
+
|
372
|
+
Args:
|
373
|
+
name: Имя конфигурации
|
374
|
+
"""
|
375
|
+
if name in self.configs:
|
376
|
+
self.configs[name].success_count += 1
|
377
|
+
|
378
|
+
# Если клиент был деактивирован из-за ошибок, но успешно выполнил запрос,
|
379
|
+
# сбрасываем счетчик ошибок и активируем его снова
|
380
|
+
if not self.configs[name].is_active and self.configs[name].error_count > 0:
|
381
|
+
self.configs[name].error_count = 0
|
382
|
+
self.configs[name].activate()
|
383
|
+
self.logger.info(f"Конфигурация активирована после успешного запроса: {name}")
|
384
|
+
|
385
|
+
def get_stats(self) -> Dict[str, Dict[str, Any]]:
|
386
|
+
"""
|
387
|
+
Возвращает статистику по всем конфигурациям.
|
388
|
+
|
389
|
+
Returns:
|
390
|
+
Dict[str, Dict[str, Any]]: Словарь со статистикой
|
391
|
+
"""
|
392
|
+
return {name: config.to_dict() for name, config in self.configs.items()}
|
393
|
+
|
394
|
+
async def export_configs(self, file_path: str) -> None:
|
395
|
+
"""
|
396
|
+
Экспортирует конфигурации в JSON-файл.
|
397
|
+
|
398
|
+
Args:
|
399
|
+
file_path: Путь к файлу для экспорта
|
400
|
+
"""
|
401
|
+
import json
|
402
|
+
|
403
|
+
# Закрываем все клиенты перед экспортом
|
404
|
+
await self.close_all()
|
405
|
+
|
406
|
+
# Получаем данные конфигураций
|
407
|
+
configs_data = {name: config.to_dict() for name, config in self.configs.items()}
|
408
|
+
|
409
|
+
# Сохраняем в файл
|
410
|
+
with open(file_path, "w") as f:
|
411
|
+
json.dump(configs_data, f, indent=2)
|
412
|
+
|
413
|
+
self.logger.info(f"Конфигурации экспортированы в файл: {file_path}")
|
414
|
+
|
415
|
+
@classmethod
|
416
|
+
async def import_configs(cls, file_path: str, logger: Optional[logging.Logger] = None) -> "ClientFarm":
|
417
|
+
"""
|
418
|
+
Импортирует конфигурации из JSON-файла.
|
419
|
+
|
420
|
+
Args:
|
421
|
+
file_path: Путь к файлу с конфигурациями
|
422
|
+
logger: Логгер для записи информации (опционально)
|
423
|
+
|
424
|
+
Returns:
|
425
|
+
ClientFarm: Новый экземпляр фермы клиентов с импортированными конфигурациями
|
426
|
+
"""
|
427
|
+
import json
|
428
|
+
|
429
|
+
farm = cls(logger=logger)
|
430
|
+
|
431
|
+
try:
|
432
|
+
# Загружаем данные из файла
|
433
|
+
with open(file_path, "r") as f:
|
434
|
+
configs_data = json.load(f)
|
435
|
+
|
436
|
+
# Создаем конфигурации на основе данных
|
437
|
+
for name, data in configs_data.items():
|
438
|
+
# Извлекаем только параметры, необходимые для создания конфигурации
|
439
|
+
config_params = {
|
440
|
+
"name": data.get("name"),
|
441
|
+
"base_url": data.get("base_url"),
|
442
|
+
"jwt_token": data.get("jwt_token"),
|
443
|
+
"timeout": data.get("timeout"),
|
444
|
+
"max_retries": data.get("max_retries"),
|
445
|
+
"proxy": data.get("proxy"),
|
446
|
+
"tags": data.get("tags", []),
|
447
|
+
}
|
448
|
+
|
449
|
+
# Создаем конфигурацию
|
450
|
+
config = ClientConfig(**config_params)
|
451
|
+
|
452
|
+
# Устанавливаем дополнительные параметры
|
453
|
+
config.is_active = data.get("is_active", True)
|
454
|
+
config.error_count = data.get("error_count", 0)
|
455
|
+
config.success_count = data.get("success_count", 0)
|
456
|
+
|
457
|
+
# Добавляем конфигурацию в ферму
|
458
|
+
farm.add_config(config)
|
459
|
+
except Exception as e:
|
460
|
+
if logger:
|
461
|
+
logger.error(f"Ошибка при импорте конфигураций: {e}")
|
462
|
+
raise
|
463
|
+
|
464
|
+
return farm
|
@@ -1,13 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: gengineapi
|
3
|
-
Version: 0.
|
4
|
-
Summary:
|
3
|
+
Version: 0.2.0
|
4
|
+
Summary: G-Engine API Client
|
5
5
|
Requires-Python: >=3.13
|
6
6
|
Description-Content-Type: text/markdown
|
7
7
|
Requires-Dist: aiohttp>=3.8.0
|
8
8
|
Requires-Dist: typing-extensions>=4.0.0
|
9
9
|
Requires-Dist: aiohttp-socks>=0.7.1
|
10
|
-
Requires-Dist: uv-publish
|
11
10
|
|
12
11
|
# G-Engine API Client
|
13
12
|
|
@@ -21,6 +20,7 @@ Requires-Dist: uv-publish
|
|
21
20
|
- Автоматические повторные попытки для временных ошибок
|
22
21
|
- Поддержка JWT-аутентификации
|
23
22
|
- Поддержка прокси, включая SOCKS5
|
23
|
+
- Ферма клиентов с балансировкой нагрузки и стратегиями ротации
|
24
24
|
- Подробное логирование
|
25
25
|
- Строгая типизация с помощью аннотаций типов
|
26
26
|
- Класс-конфигурация для централизованной настройки и повторного использования
|
@@ -40,21 +40,23 @@ pip install .
|
|
40
40
|
## Структура проекта
|
41
41
|
|
42
42
|
```
|
43
|
-
|
44
|
-
|
45
|
-
├──
|
46
|
-
├──
|
47
|
-
├──
|
48
|
-
├──
|
49
|
-
|
50
|
-
├──
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
43
|
+
src/
|
44
|
+
└── gengineapi/
|
45
|
+
├── __init__.py # Экспорт основных классов
|
46
|
+
├── client.py # Основной класс клиента
|
47
|
+
├── http.py # HTTP клиент с поддержкой аутентификации
|
48
|
+
├── exceptions.py # Иерархия исключений API
|
49
|
+
├── config.py # Класс-конфигурация для настройки параметров
|
50
|
+
├── farm.py # Ферма клиентов для управления множеством клиентов
|
51
|
+
└── modules/ # Папка с модулями API
|
52
|
+
├── __init__.py
|
53
|
+
├── base.py # Базовый класс для всех модулей
|
54
|
+
├── payments.py # Модуль для работы с платежами
|
55
|
+
├── finances.py # Модуль для работы с финансами
|
56
|
+
├── auth.py # Модуль для аутентификации
|
57
|
+
├── users.py # Модуль для работы с пользователями
|
58
|
+
├── transactions.py # Модуль для работы с транзакциями
|
59
|
+
└── currencies.py # Модуль для работы с валютами
|
58
60
|
```
|
59
61
|
|
60
62
|
## Использование
|
@@ -63,7 +65,7 @@ gengine_client/
|
|
63
65
|
|
64
66
|
```python
|
65
67
|
import asyncio
|
66
|
-
from
|
68
|
+
from src.gengineapi import GEngineClient
|
67
69
|
|
68
70
|
async def main():
|
69
71
|
# Создаем клиент с существующим токеном
|
@@ -84,7 +86,7 @@ asyncio.run(main())
|
|
84
86
|
|
85
87
|
```python
|
86
88
|
import asyncio
|
87
|
-
from
|
89
|
+
from src.gengineapi import GEngineClient
|
88
90
|
|
89
91
|
async def main():
|
90
92
|
# Создаем клиент без токена
|
@@ -119,7 +121,7 @@ asyncio.run(main())
|
|
119
121
|
|
120
122
|
```python
|
121
123
|
import asyncio
|
122
|
-
from
|
124
|
+
from src.gengineapi import GEngineClient
|
123
125
|
|
124
126
|
async def main():
|
125
127
|
# Создаем клиент с использованием SOCKS5 прокси
|
@@ -149,7 +151,7 @@ asyncio.run(main())
|
|
149
151
|
|
150
152
|
```python
|
151
153
|
import asyncio
|
152
|
-
from
|
154
|
+
from src.gengineapi import GEngineConfig
|
153
155
|
|
154
156
|
async def main():
|
155
157
|
# Настройка параметров клиента
|
@@ -188,80 +190,85 @@ async def main():
|
|
188
190
|
asyncio.run(main())
|
189
191
|
```
|
190
192
|
|
191
|
-
###
|
193
|
+
### Использование фермы клиентов
|
194
|
+
|
195
|
+
Ферма клиентов позволяет управлять множеством клиентов с разными конфигурациями и стратегиями ротации:
|
192
196
|
|
193
197
|
```python
|
194
198
|
import asyncio
|
195
|
-
import
|
196
|
-
from gengine_client import GEngineConfig
|
197
|
-
|
198
|
-
# Установка переменных окружения
|
199
|
-
os.environ["GENGINE_BASE_URL"] = "https://api.example.com/api/v2"
|
200
|
-
os.environ["GENGINE_TOKEN"] = "env_jwt_token"
|
201
|
-
os.environ["GENGINE_TIMEOUT"] = "45"
|
202
|
-
os.environ["GENGINE_MAX_RETRIES"] = "5"
|
203
|
-
os.environ["GENGINE_PROXY"] = "socks5://127.0.0.1:9050" # Опционально - прокси
|
199
|
+
from src.gengineapi import ClientFarm
|
204
200
|
|
205
201
|
async def main():
|
206
|
-
#
|
207
|
-
|
202
|
+
# Создаем ферму клиентов
|
203
|
+
farm = ClientFarm()
|
208
204
|
|
209
|
-
#
|
210
|
-
|
205
|
+
# Добавляем несколько конфигураций с разными прокси
|
206
|
+
farm.create_config(
|
207
|
+
name="client1",
|
208
|
+
base_url="https://api.example.com/api/v2",
|
209
|
+
jwt_token="token1",
|
210
|
+
proxy="socks5://user1:pass1@host1:port1",
|
211
|
+
tags=["group1", "production"]
|
212
|
+
)
|
211
213
|
|
212
|
-
|
213
|
-
|
214
|
+
farm.create_config(
|
215
|
+
name="client2",
|
216
|
+
base_url="https://api.example.com/api/v2",
|
217
|
+
jwt_token="token2",
|
218
|
+
proxy="socks5://user2:pass2@host2:port2",
|
219
|
+
tags=["group1", "backup"]
|
220
|
+
)
|
214
221
|
|
215
|
-
#
|
216
|
-
|
222
|
+
# Устанавливаем стратегию выбора клиента
|
223
|
+
farm.set_selection_strategy("round_robin") # или "random", "least_errors", "least_recently_used"
|
224
|
+
|
225
|
+
# Получаем клиент по имени
|
226
|
+
client1 = await farm.get_client("client1")
|
227
|
+
await client1.users.get_me()
|
228
|
+
|
229
|
+
# Получаем следующий клиент согласно стратегии
|
230
|
+
client = await farm.get_next_client()
|
231
|
+
await client.currencies.get_rate(source="cb_rf", pair="USD:RUB")
|
232
|
+
|
233
|
+
# Получаем клиент по тегу
|
234
|
+
client = await farm.get_next_client(tag="production")
|
235
|
+
await client.transactions.get_transactions(limit=5)
|
236
|
+
|
237
|
+
# При завершении работы закрываем все клиенты
|
238
|
+
await farm.close_all()
|
217
239
|
|
218
240
|
asyncio.run(main())
|
219
241
|
```
|
220
242
|
|
221
|
-
###
|
243
|
+
### Экспорт и импорт конфигураций фермы
|
222
244
|
|
223
245
|
```python
|
224
246
|
import asyncio
|
225
|
-
import
|
226
|
-
from decimal import Decimal
|
227
|
-
from gengine_client import GEngineClient
|
247
|
+
from src.gengineapi import ClientFarm
|
228
248
|
|
229
249
|
async def main():
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
result = await client.payments.execute(transaction_id=transaction_id)
|
249
|
-
print(f"Статус платежа: {result['status_code']}")
|
250
|
-
|
251
|
-
# Получаем статус платежа
|
252
|
-
status = await client.payments.get_status(transaction_id=transaction_id)
|
253
|
-
print(f"Текущий статус платежа: {status['status_code']}")
|
250
|
+
# Создаем и настраиваем ферму
|
251
|
+
farm = ClientFarm()
|
252
|
+
|
253
|
+
# Добавляем конфигурации
|
254
|
+
farm.create_config(name="client1", base_url="...", jwt_token="...", proxy="...")
|
255
|
+
farm.create_config(name="client2", base_url="...", jwt_token="...", proxy="...")
|
256
|
+
|
257
|
+
# Экспортируем конфигурации в файл
|
258
|
+
await farm.export_configs("farm_config.json")
|
259
|
+
|
260
|
+
# Импортируем конфигурации из файла
|
261
|
+
new_farm = await ClientFarm.import_configs("farm_config.json")
|
262
|
+
|
263
|
+
# Используем импортированные конфигурации
|
264
|
+
client = await new_farm.get_next_client()
|
265
|
+
|
266
|
+
# При завершении работы закрываем все клиенты
|
267
|
+
await new_farm.close_all()
|
254
268
|
|
255
269
|
asyncio.run(main())
|
256
270
|
```
|
257
271
|
|
258
|
-
## Примеры
|
259
|
-
|
260
|
-
В репозитории есть примеры использования клиента с разными сценариями:
|
261
|
-
|
262
|
-
- `examples.py` - полный набор примеров использования, включая работу с прокси
|
263
|
-
- `old_examples/` - директория с устаревшими примерами
|
264
|
-
|
265
272
|
## Зависимости
|
266
273
|
|
267
274
|
- Python 3.7+
|
@@ -1,7 +1,8 @@
|
|
1
|
-
gengineapi/__init__.py,sha256=
|
1
|
+
gengineapi/__init__.py,sha256=p48czhfjLfWAYQ1YbiorEPmmOyyE9GzTC9vLWMKu2es,1283
|
2
2
|
gengineapi/client.py,sha256=gEaiWKNaK08H-98evDOMntu4VOBuwqua0k8GxU0xHpk,4797
|
3
3
|
gengineapi/config.py,sha256=2DxvCPf80wCLX-6JR-p52mFSgQeZLWLJOohIlqVRQoQ,10348
|
4
4
|
gengineapi/exceptions.py,sha256=t6jKY3FuvjJ6IqX45YfKuPmH_ktqsrxbdfP27W5_BPw,4111
|
5
|
+
gengineapi/farm.py,sha256=mg6pM5lflYXxbwjPEOqrCWFh2ujrgN-S6Dmcic2VLD8,20156
|
5
6
|
gengineapi/http.py,sha256=Xh7oi_7XFuFAdmOKy6mnpCAyDTRGkPKpXgd3LhaBf5s,24979
|
6
7
|
gengineapi/modules/__init__.py,sha256=roxuYxYqifG81WhNTyopuORNcjMg_6YrQqWA28x2c5E,618
|
7
8
|
gengineapi/modules/auth.py,sha256=ByTySErLQ16huxxmvbJKXObRdblqCRfJR21lteKn3FQ,1753
|
@@ -11,7 +12,7 @@ gengineapi/modules/finances.py,sha256=fwOWo1LI56ZC3qlTG-tv_cqbI7RyCPYQSNOT5r6VzT
|
|
11
12
|
gengineapi/modules/payments.py,sha256=dwOqQcwsq256OQKzXJUn8a0H6krsf3Rb73KdAaRcEnw,4634
|
12
13
|
gengineapi/modules/transactions.py,sha256=QUtTxzTFsLesqog9Da6H-pdq5DP0sgh9w2cIlZQiKCk,2932
|
13
14
|
gengineapi/modules/users.py,sha256=WQyI37MVBAzqDE3i1LPpV2G5g_1cGvQh0vLomTF4vkI,4291
|
14
|
-
gengineapi-0.
|
15
|
-
gengineapi-0.
|
16
|
-
gengineapi-0.
|
17
|
-
gengineapi-0.
|
15
|
+
gengineapi-0.2.0.dist-info/METADATA,sha256=ITliNO5hqOBbTEQW276ie5GcMBfQWFzqjSGzpprW5l8,10732
|
16
|
+
gengineapi-0.2.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
17
|
+
gengineapi-0.2.0.dist-info/top_level.txt,sha256=mYdcVWOxFzj4EzoczGzeT-ccVrNEjNI98w2JW51wnfk,11
|
18
|
+
gengineapi-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|