amochka 0.3.1__tar.gz → 0.3.2__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.
- {amochka-0.3.1 → amochka-0.3.2}/PKG-INFO +1 -2
- {amochka-0.3.1 → amochka-0.3.2}/amochka/client.py +33 -10
- {amochka-0.3.1 → amochka-0.3.2}/amochka.egg-info/PKG-INFO +1 -2
- {amochka-0.3.1 → amochka-0.3.2}/amochka.egg-info/requires.txt +0 -1
- {amochka-0.3.1 → amochka-0.3.2}/etl/config.py +2 -0
- {amochka-0.3.1 → amochka-0.3.2}/etl/extractors.py +1 -0
- {amochka-0.3.1 → amochka-0.3.2}/pyproject.toml +1 -2
- {amochka-0.3.1 → amochka-0.3.2}/README.md +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/amochka/__init__.py +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/amochka/etl.py +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/amochka.egg-info/SOURCES.txt +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/amochka.egg-info/dependency_links.txt +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/amochka.egg-info/top_level.txt +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/etl/__init__.py +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/etl/loaders.py +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/etl/migrations/001_create_tables.sql +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/etl/run_etl.py +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/etl/transformers.py +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/setup.cfg +0 -0
- {amochka-0.3.1 → amochka-0.3.2}/tests/test_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amochka
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Python library for working with amoCRM API with ETL capabilities
|
|
5
5
|
Author-email: Timur <timurdt@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -26,7 +26,6 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
26
26
|
Requires-Python: >=3.6
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
Requires-Dist: requests>=2.25.0
|
|
29
|
-
Requires-Dist: ratelimit>=2.2.0
|
|
30
29
|
Requires-Dist: psycopg2-binary>=2.9.0
|
|
31
30
|
Requires-Dist: python-dotenv>=1.0.0
|
|
32
31
|
|
|
@@ -5,7 +5,7 @@ import requests
|
|
|
5
5
|
import logging
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from typing import Iterator, List, Optional, Sequence, Union
|
|
8
|
-
|
|
8
|
+
# ratelimit больше не используется - rate limiting реализован вручную через self.rate_limit
|
|
9
9
|
|
|
10
10
|
# Создаём базовый логгер
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
@@ -15,7 +15,7 @@ if not logger.handlers:
|
|
|
15
15
|
ch.setFormatter(formatter)
|
|
16
16
|
logger.addHandler(ch)
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
DEFAULT_RATE_LIMIT = 7 # Максимум запросов в секунду по умолчанию
|
|
19
19
|
|
|
20
20
|
class Deal(dict):
|
|
21
21
|
"""
|
|
@@ -262,12 +262,13 @@ class AmoCRMClient:
|
|
|
262
262
|
либо полностью отключить логирование, установив disable_logging=True.
|
|
263
263
|
"""
|
|
264
264
|
def __init__(
|
|
265
|
-
self,
|
|
266
|
-
base_url,
|
|
267
|
-
token_file=None,
|
|
268
|
-
cache_config=None,
|
|
269
|
-
log_level=logging.INFO,
|
|
265
|
+
self,
|
|
266
|
+
base_url,
|
|
267
|
+
token_file=None,
|
|
268
|
+
cache_config=None,
|
|
269
|
+
log_level=logging.INFO,
|
|
270
270
|
disable_logging=False,
|
|
271
|
+
rate_limit: Optional[int] = None,
|
|
271
272
|
*,
|
|
272
273
|
client_id: Optional[str] = None,
|
|
273
274
|
client_secret: Optional[str] = None,
|
|
@@ -275,14 +276,17 @@ class AmoCRMClient:
|
|
|
275
276
|
):
|
|
276
277
|
"""
|
|
277
278
|
Инициализирует клиента, задавая базовый URL, токен авторизации и настройки кэша для кастомных полей.
|
|
278
|
-
|
|
279
|
+
|
|
279
280
|
:param base_url: Базовый URL API amoCRM.
|
|
280
281
|
:param token_file: Файл, содержащий токен авторизации.
|
|
281
282
|
:param cache_config: Конфигурация кэширования (объект CacheConfig или None для значений по умолчанию)
|
|
282
283
|
:param log_level: Уровень логирования (например, logging.DEBUG, logging.INFO).
|
|
283
284
|
:param disable_logging: Если True, логирование будет отключено.
|
|
285
|
+
:param rate_limit: Максимальное количество запросов в секунду (по умолчанию 7).
|
|
284
286
|
"""
|
|
285
287
|
self.base_url = base_url.rstrip('/')
|
|
288
|
+
self.rate_limit = rate_limit if rate_limit is not None else DEFAULT_RATE_LIMIT
|
|
289
|
+
self._request_times: List[float] = [] # Для отслеживания времени запросов
|
|
286
290
|
domain = self.base_url.split("//")[-1].split(".")[0]
|
|
287
291
|
self.domain = domain
|
|
288
292
|
self.token_file = token_file or os.path.join(os.path.expanduser('~'), '.amocrm_token.json')
|
|
@@ -378,8 +382,24 @@ class AmoCRMClient:
|
|
|
378
382
|
|
|
379
383
|
raise Exception("Токен истёк или некорректен, и нет данных для refresh_token. Обновите токен.")
|
|
380
384
|
|
|
381
|
-
|
|
382
|
-
|
|
385
|
+
def _wait_for_rate_limit(self):
|
|
386
|
+
"""Ожидает, если превышен лимит запросов в секунду."""
|
|
387
|
+
now = time.time()
|
|
388
|
+
# Удаляем запросы старше 1 секунды
|
|
389
|
+
self._request_times = [t for t in self._request_times if now - t < 1.0]
|
|
390
|
+
|
|
391
|
+
if len(self._request_times) >= self.rate_limit:
|
|
392
|
+
# Ждём до освобождения слота
|
|
393
|
+
sleep_time = 1.0 - (now - self._request_times[0])
|
|
394
|
+
if sleep_time > 0:
|
|
395
|
+
self.logger.debug(f"Rate limit: ожидание {sleep_time:.3f}с (лимит {self.rate_limit} req/s)")
|
|
396
|
+
time.sleep(sleep_time)
|
|
397
|
+
# Очищаем старые записи после ожидания
|
|
398
|
+
now = time.time()
|
|
399
|
+
self._request_times = [t for t in self._request_times if now - t < 1.0]
|
|
400
|
+
|
|
401
|
+
self._request_times.append(time.time())
|
|
402
|
+
|
|
383
403
|
def _make_request(self, method, endpoint, params=None, data=None, timeout=10):
|
|
384
404
|
"""
|
|
385
405
|
Выполняет HTTP-запрос к API amoCRM с учетом ограничения по скорости (rate limit).
|
|
@@ -392,6 +412,9 @@ class AmoCRMClient:
|
|
|
392
412
|
:return: Ответ в формате JSON или None (если статус 204).
|
|
393
413
|
:raises Exception: При получении кода ошибки, отличного от 200/204.
|
|
394
414
|
"""
|
|
415
|
+
# Ручной rate limiting
|
|
416
|
+
self._wait_for_rate_limit()
|
|
417
|
+
|
|
395
418
|
url = f"{self.base_url}{endpoint}"
|
|
396
419
|
headers = {
|
|
397
420
|
"Authorization": f"Bearer {self.token}",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amochka
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Python library for working with amoCRM API with ETL capabilities
|
|
5
5
|
Author-email: Timur <timurdt@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -26,7 +26,6 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
26
26
|
Requires-Python: >=3.6
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
Requires-Dist: requests>=2.25.0
|
|
29
|
-
Requires-Dist: ratelimit>=2.2.0
|
|
30
29
|
Requires-Dist: psycopg2-binary>=2.9.0
|
|
31
30
|
Requires-Dist: python-dotenv>=1.0.0
|
|
32
31
|
|
|
@@ -107,6 +107,7 @@ class AmoCRMAccount:
|
|
|
107
107
|
mybi_account_id: int # Внутренний account_id как в mybi.ru (для совместимости)
|
|
108
108
|
pipeline_ids: Optional[List[int]] = None # None = все воронки
|
|
109
109
|
cache_dir: Optional[Path] = None
|
|
110
|
+
rate_limit: int = 7 # Максимум запросов в секунду (по умолчанию 7)
|
|
110
111
|
|
|
111
112
|
def __post_init__(self):
|
|
112
113
|
if isinstance(self.token_path, str):
|
|
@@ -125,6 +126,7 @@ class AmoCRMAccount:
|
|
|
125
126
|
mybi_account_id=int(d.get("mybi_account_id", 0)),
|
|
126
127
|
pipeline_ids=d.get("pipeline_ids"),
|
|
127
128
|
cache_dir=Path(d.get("cache_dir", ".cache")) if d.get("cache_dir") else None,
|
|
129
|
+
rate_limit=int(d.get("rate_limit", 7)),
|
|
128
130
|
)
|
|
129
131
|
|
|
130
132
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "amochka"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Python library for working with amoCRM API with ETL capabilities"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -31,7 +31,6 @@ keywords = ["amocrm", "crm", "api", "client", "automation", "etl"]
|
|
|
31
31
|
requires-python = ">=3.6"
|
|
32
32
|
dependencies = [
|
|
33
33
|
"requests>=2.25.0",
|
|
34
|
-
"ratelimit>=2.2.0",
|
|
35
34
|
"psycopg2-binary>=2.9.0",
|
|
36
35
|
"python-dotenv>=1.0.0"
|
|
37
36
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|