amochka 0.3.0__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.0 → amochka-0.3.2}/PKG-INFO +103 -25
- amochka-0.3.2/README.md +173 -0
- {amochka-0.3.0 → amochka-0.3.2}/amochka/client.py +33 -10
- {amochka-0.3.0 → amochka-0.3.2}/amochka.egg-info/PKG-INFO +103 -25
- {amochka-0.3.0 → amochka-0.3.2}/amochka.egg-info/requires.txt +0 -1
- {amochka-0.3.0 → amochka-0.3.2}/etl/config.py +2 -0
- {amochka-0.3.0 → amochka-0.3.2}/etl/extractors.py +1 -0
- {amochka-0.3.0 → amochka-0.3.2}/pyproject.toml +1 -2
- amochka-0.3.0/README.md +0 -94
- {amochka-0.3.0 → amochka-0.3.2}/amochka/__init__.py +0 -0
- {amochka-0.3.0 → amochka-0.3.2}/amochka/etl.py +0 -0
- {amochka-0.3.0 → amochka-0.3.2}/amochka.egg-info/SOURCES.txt +0 -0
- {amochka-0.3.0 → amochka-0.3.2}/amochka.egg-info/dependency_links.txt +0 -0
- {amochka-0.3.0 → amochka-0.3.2}/amochka.egg-info/top_level.txt +0 -0
- {amochka-0.3.0 → amochka-0.3.2}/etl/__init__.py +0 -0
- {amochka-0.3.0 → amochka-0.3.2}/etl/loaders.py +0 -0
- {amochka-0.3.0 → amochka-0.3.2}/etl/migrations/001_create_tables.sql +0 -0
- {amochka-0.3.0 → amochka-0.3.2}/etl/run_etl.py +0 -0
- {amochka-0.3.0 → amochka-0.3.2}/etl/transformers.py +0 -0
- {amochka-0.3.0 → amochka-0.3.2}/setup.cfg +0 -0
- {amochka-0.3.0 → 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
|
|
|
@@ -39,88 +38,167 @@ Requires-Dist: python-dotenv>=1.0.0
|
|
|
39
38
|
- Редактирование сделок, включая обновление стандартных и кастомных полей
|
|
40
39
|
- Поддержку нескольких amoCRM-аккаунтов с персистентным кэшированием кастомных полей для каждого аккаунта отдельно
|
|
41
40
|
- Ограничение запросов (7 запросов в секунду) с использованием декораторов из библиотеки `ratelimit`
|
|
41
|
+
- **Полнофункциональный ETL модуль** для синхронизации данных amoCRM в PostgreSQL
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
## Возможности
|
|
44
|
+
|
|
45
|
+
### API клиент
|
|
44
46
|
|
|
45
47
|
- `get_deal_by_id(deal_id)` — получение детальной информации по сделке
|
|
46
48
|
- `get_pipelines()` — список воронок и статусов
|
|
47
49
|
- `fetch_updated_leads_raw(pipeline_id, updated_from, ...)` — выгрузка необработанных сделок за период
|
|
48
50
|
|
|
51
|
+
### ETL модуль
|
|
52
|
+
|
|
53
|
+
- **Extractors**: извлечение данных из amoCRM (сделки, контакты, события, примечания)
|
|
54
|
+
- **Transformers**: преобразование в табличный формат для БД
|
|
55
|
+
- **Loaders**: загрузка в PostgreSQL с UPSERT логикой и сохранением внутренних ID
|
|
56
|
+
- **Migrations**: автоматическое создание таблиц и схем
|
|
57
|
+
- **Incremental sync**: инкрементальная синхронизация по updated_at
|
|
58
|
+
- Интеграция с **Apache Airflow** для автоматизации ETL процессов
|
|
59
|
+
|
|
49
60
|
## Требования к окружению
|
|
50
61
|
|
|
51
|
-
Python 3.
|
|
62
|
+
Python 3.6 или новее.
|
|
52
63
|
|
|
53
64
|
## Установка
|
|
54
65
|
|
|
55
|
-
Установите зависимости командой:
|
|
56
|
-
|
|
57
66
|
```bash
|
|
58
|
-
pip install
|
|
67
|
+
pip install amochka
|
|
59
68
|
```
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
Для использования ETL модуля установите дополнительные зависимости:
|
|
62
71
|
|
|
63
72
|
```bash
|
|
64
|
-
pip install amochka
|
|
73
|
+
pip install amochka psycopg2-binary python-dotenv
|
|
65
74
|
```
|
|
66
75
|
|
|
67
76
|
## Кэширование кастомных полей
|
|
68
77
|
|
|
69
78
|
Для уменьшения количества запросов к API кастомные поля кэшируются персистентно. Если параметр cache_file не указан, имя файла кэша генерируется автоматически на основе домена amoCRM-аккаунта. Вы можете обновлять кэш принудительно, передавая параметр force_update=True в метод get_custom_fields_mapping() или настроить время жизни кэша (по умолчанию — 24 часа).
|
|
70
79
|
|
|
71
|
-
##
|
|
80
|
+
## Примеры использования
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
### Быстрый старт: выгрузка обновленных сделок
|
|
74
83
|
|
|
75
84
|
```python
|
|
76
85
|
from datetime import datetime, timedelta
|
|
77
86
|
from amochka import AmoCRMClient, CacheConfig
|
|
78
87
|
|
|
79
88
|
client = AmoCRMClient(
|
|
80
|
-
base_url="https://
|
|
81
|
-
token_file="
|
|
89
|
+
base_url="https://example.amocrm.ru",
|
|
90
|
+
token_file="token.json",
|
|
82
91
|
cache_config=CacheConfig.disabled(),
|
|
83
92
|
disable_logging=True
|
|
84
93
|
)
|
|
85
94
|
|
|
86
95
|
three_hours_ago = datetime.utcnow() - timedelta(hours=3)
|
|
87
|
-
client.fetch_updated_leads_raw(
|
|
96
|
+
leads = client.fetch_updated_leads_raw(
|
|
97
|
+
pipeline_id=123456,
|
|
98
|
+
updated_from=three_hours_ago,
|
|
99
|
+
save_to_file="leads.json",
|
|
100
|
+
include_contacts=True
|
|
101
|
+
)
|
|
88
102
|
```
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
### ETL: синхронизация в PostgreSQL
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from etl.config import DatabaseConfig, AmoCRMAccount
|
|
108
|
+
from etl.extractors import AmoCRMExtractor
|
|
109
|
+
from etl.loaders import PostgresLoader
|
|
110
|
+
from etl.run_etl import sync_leads_with_contacts
|
|
111
|
+
from datetime import datetime, timezone
|
|
112
|
+
|
|
113
|
+
# Настройка БД
|
|
114
|
+
db_config = DatabaseConfig(
|
|
115
|
+
host="localhost",
|
|
116
|
+
port=5432,
|
|
117
|
+
dbname="amocrm",
|
|
118
|
+
user="postgres",
|
|
119
|
+
password="password",
|
|
120
|
+
schema="public"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Настройка amoCRM аккаунта
|
|
124
|
+
account = AmoCRMAccount(
|
|
125
|
+
id=1,
|
|
126
|
+
name="main",
|
|
127
|
+
base_url="https://example.amocrm.ru",
|
|
128
|
+
token_path="token.json",
|
|
129
|
+
mybi_account_id=1,
|
|
130
|
+
pipeline_ids=[123456]
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# ETL процесс
|
|
134
|
+
loader = PostgresLoader(db_config)
|
|
135
|
+
extractor = AmoCRMExtractor(account)
|
|
136
|
+
|
|
137
|
+
result = sync_leads_with_contacts(
|
|
138
|
+
extractor=extractor,
|
|
139
|
+
loader=loader,
|
|
140
|
+
mybi_account_id=1,
|
|
141
|
+
updated_from=datetime(2025, 1, 1, tzinfo=timezone.utc),
|
|
142
|
+
updated_to=datetime.now(timezone.utc),
|
|
143
|
+
pipeline_ids=[123456]
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
print(f"Загружено сделок: {result['leads_count']}")
|
|
147
|
+
print(f"Загружено контактов: {result['contacts_count']}")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Пример структуры данных
|
|
91
151
|
|
|
92
152
|
```json
|
|
93
153
|
[
|
|
94
154
|
{
|
|
95
|
-
"id":
|
|
96
|
-
"name": "
|
|
155
|
+
"id": 12345678,
|
|
156
|
+
"name": "Сделка: Заявка от клиента",
|
|
97
157
|
"custom_fields_values": [
|
|
98
158
|
{
|
|
99
|
-
"field_name": "
|
|
100
|
-
"values": [{"value": "
|
|
159
|
+
"field_name": "utm_source",
|
|
160
|
+
"values": [{"value": "google"}]
|
|
101
161
|
}
|
|
102
162
|
],
|
|
103
163
|
"_embedded": {
|
|
104
164
|
"tags": [
|
|
105
|
-
{"id":
|
|
165
|
+
{"id": 123, "name": "Приоритетный клиент"}
|
|
106
166
|
]
|
|
107
167
|
}
|
|
108
168
|
}
|
|
109
169
|
]
|
|
110
170
|
```
|
|
111
171
|
|
|
112
|
-
|
|
172
|
+
## Интеграция с Apache Airflow
|
|
173
|
+
|
|
174
|
+
Модуль ETL разработан для использования в Airflow DAG. Пример минимального DAG:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from airflow.decorators import dag, task
|
|
178
|
+
from etl.config import DatabaseConfig, AmoCRMAccount
|
|
179
|
+
from etl.run_etl import sync_leads_with_contacts
|
|
180
|
+
|
|
181
|
+
@dag(schedule_interval=None)
|
|
182
|
+
def amocrm_sync():
|
|
183
|
+
@task
|
|
184
|
+
def sync_data():
|
|
185
|
+
db_config = DatabaseConfig.from_env()
|
|
186
|
+
account = AmoCRMAccount.from_env()
|
|
187
|
+
# ... ETL процесс
|
|
188
|
+
|
|
189
|
+
amocrm_sync()
|
|
190
|
+
```
|
|
113
191
|
|
|
114
192
|
## Тесты
|
|
115
193
|
|
|
116
|
-
|
|
194
|
+
Запустить тесты можно командой:
|
|
117
195
|
|
|
118
196
|
```bash
|
|
119
197
|
pytest -q
|
|
120
198
|
```
|
|
121
199
|
|
|
122
|
-
|
|
200
|
+
Тесты проверяют основную функциональность API клиента и помогают убедиться, что изменения в коде не ломают работу библиотеки.
|
|
123
201
|
|
|
124
|
-
##
|
|
202
|
+
## Лицензия
|
|
125
203
|
|
|
126
|
-
|
|
204
|
+
MIT
|
amochka-0.3.2/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# amochka
|
|
2
|
+
|
|
3
|
+
Официальная документация API amocrm - https://www.amocrm.ru/developers/content/crm_platform/api-reference
|
|
4
|
+
|
|
5
|
+
**amochka** — библиотека для работы с API amoCRM на Python. Она поддерживает:
|
|
6
|
+
- Получение данных сделок с вложенными сущностями (контакты, компании, теги, и т.д.)
|
|
7
|
+
- Редактирование сделок, включая обновление стандартных и кастомных полей
|
|
8
|
+
- Поддержку нескольких amoCRM-аккаунтов с персистентным кэшированием кастомных полей для каждого аккаунта отдельно
|
|
9
|
+
- Ограничение запросов (7 запросов в секунду) с использованием декораторов из библиотеки `ratelimit`
|
|
10
|
+
- **Полнофункциональный ETL модуль** для синхронизации данных amoCRM в PostgreSQL
|
|
11
|
+
|
|
12
|
+
## Возможности
|
|
13
|
+
|
|
14
|
+
### API клиент
|
|
15
|
+
|
|
16
|
+
- `get_deal_by_id(deal_id)` — получение детальной информации по сделке
|
|
17
|
+
- `get_pipelines()` — список воронок и статусов
|
|
18
|
+
- `fetch_updated_leads_raw(pipeline_id, updated_from, ...)` — выгрузка необработанных сделок за период
|
|
19
|
+
|
|
20
|
+
### ETL модуль
|
|
21
|
+
|
|
22
|
+
- **Extractors**: извлечение данных из amoCRM (сделки, контакты, события, примечания)
|
|
23
|
+
- **Transformers**: преобразование в табличный формат для БД
|
|
24
|
+
- **Loaders**: загрузка в PostgreSQL с UPSERT логикой и сохранением внутренних ID
|
|
25
|
+
- **Migrations**: автоматическое создание таблиц и схем
|
|
26
|
+
- **Incremental sync**: инкрементальная синхронизация по updated_at
|
|
27
|
+
- Интеграция с **Apache Airflow** для автоматизации ETL процессов
|
|
28
|
+
|
|
29
|
+
## Требования к окружению
|
|
30
|
+
|
|
31
|
+
Python 3.6 или новее.
|
|
32
|
+
|
|
33
|
+
## Установка
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install amochka
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Для использования ETL модуля установите дополнительные зависимости:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install amochka psycopg2-binary python-dotenv
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Кэширование кастомных полей
|
|
46
|
+
|
|
47
|
+
Для уменьшения количества запросов к API кастомные поля кэшируются персистентно. Если параметр cache_file не указан, имя файла кэша генерируется автоматически на основе домена amoCRM-аккаунта. Вы можете обновлять кэш принудительно, передавая параметр force_update=True в метод get_custom_fields_mapping() или настроить время жизни кэша (по умолчанию — 24 часа).
|
|
48
|
+
|
|
49
|
+
## Примеры использования
|
|
50
|
+
|
|
51
|
+
### Быстрый старт: выгрузка обновленных сделок
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from datetime import datetime, timedelta
|
|
55
|
+
from amochka import AmoCRMClient, CacheConfig
|
|
56
|
+
|
|
57
|
+
client = AmoCRMClient(
|
|
58
|
+
base_url="https://example.amocrm.ru",
|
|
59
|
+
token_file="token.json",
|
|
60
|
+
cache_config=CacheConfig.disabled(),
|
|
61
|
+
disable_logging=True
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
three_hours_ago = datetime.utcnow() - timedelta(hours=3)
|
|
65
|
+
leads = client.fetch_updated_leads_raw(
|
|
66
|
+
pipeline_id=123456,
|
|
67
|
+
updated_from=three_hours_ago,
|
|
68
|
+
save_to_file="leads.json",
|
|
69
|
+
include_contacts=True
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### ETL: синхронизация в PostgreSQL
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from etl.config import DatabaseConfig, AmoCRMAccount
|
|
77
|
+
from etl.extractors import AmoCRMExtractor
|
|
78
|
+
from etl.loaders import PostgresLoader
|
|
79
|
+
from etl.run_etl import sync_leads_with_contacts
|
|
80
|
+
from datetime import datetime, timezone
|
|
81
|
+
|
|
82
|
+
# Настройка БД
|
|
83
|
+
db_config = DatabaseConfig(
|
|
84
|
+
host="localhost",
|
|
85
|
+
port=5432,
|
|
86
|
+
dbname="amocrm",
|
|
87
|
+
user="postgres",
|
|
88
|
+
password="password",
|
|
89
|
+
schema="public"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Настройка amoCRM аккаунта
|
|
93
|
+
account = AmoCRMAccount(
|
|
94
|
+
id=1,
|
|
95
|
+
name="main",
|
|
96
|
+
base_url="https://example.amocrm.ru",
|
|
97
|
+
token_path="token.json",
|
|
98
|
+
mybi_account_id=1,
|
|
99
|
+
pipeline_ids=[123456]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# ETL процесс
|
|
103
|
+
loader = PostgresLoader(db_config)
|
|
104
|
+
extractor = AmoCRMExtractor(account)
|
|
105
|
+
|
|
106
|
+
result = sync_leads_with_contacts(
|
|
107
|
+
extractor=extractor,
|
|
108
|
+
loader=loader,
|
|
109
|
+
mybi_account_id=1,
|
|
110
|
+
updated_from=datetime(2025, 1, 1, tzinfo=timezone.utc),
|
|
111
|
+
updated_to=datetime.now(timezone.utc),
|
|
112
|
+
pipeline_ids=[123456]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
print(f"Загружено сделок: {result['leads_count']}")
|
|
116
|
+
print(f"Загружено контактов: {result['contacts_count']}")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Пример структуры данных
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
"id": 12345678,
|
|
125
|
+
"name": "Сделка: Заявка от клиента",
|
|
126
|
+
"custom_fields_values": [
|
|
127
|
+
{
|
|
128
|
+
"field_name": "utm_source",
|
|
129
|
+
"values": [{"value": "google"}]
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
"_embedded": {
|
|
133
|
+
"tags": [
|
|
134
|
+
{"id": 123, "name": "Приоритетный клиент"}
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Интеграция с Apache Airflow
|
|
142
|
+
|
|
143
|
+
Модуль ETL разработан для использования в Airflow DAG. Пример минимального DAG:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from airflow.decorators import dag, task
|
|
147
|
+
from etl.config import DatabaseConfig, AmoCRMAccount
|
|
148
|
+
from etl.run_etl import sync_leads_with_contacts
|
|
149
|
+
|
|
150
|
+
@dag(schedule_interval=None)
|
|
151
|
+
def amocrm_sync():
|
|
152
|
+
@task
|
|
153
|
+
def sync_data():
|
|
154
|
+
db_config = DatabaseConfig.from_env()
|
|
155
|
+
account = AmoCRMAccount.from_env()
|
|
156
|
+
# ... ETL процесс
|
|
157
|
+
|
|
158
|
+
amocrm_sync()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Тесты
|
|
162
|
+
|
|
163
|
+
Запустить тесты можно командой:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
pytest -q
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Тесты проверяют основную функциональность API клиента и помогают убедиться, что изменения в коде не ломают работу библиотеки.
|
|
170
|
+
|
|
171
|
+
## Лицензия
|
|
172
|
+
|
|
173
|
+
MIT
|
|
@@ -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
|
|
|
@@ -39,88 +38,167 @@ Requires-Dist: python-dotenv>=1.0.0
|
|
|
39
38
|
- Редактирование сделок, включая обновление стандартных и кастомных полей
|
|
40
39
|
- Поддержку нескольких amoCRM-аккаунтов с персистентным кэшированием кастомных полей для каждого аккаунта отдельно
|
|
41
40
|
- Ограничение запросов (7 запросов в секунду) с использованием декораторов из библиотеки `ratelimit`
|
|
41
|
+
- **Полнофункциональный ETL модуль** для синхронизации данных amoCRM в PostgreSQL
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
## Возможности
|
|
44
|
+
|
|
45
|
+
### API клиент
|
|
44
46
|
|
|
45
47
|
- `get_deal_by_id(deal_id)` — получение детальной информации по сделке
|
|
46
48
|
- `get_pipelines()` — список воронок и статусов
|
|
47
49
|
- `fetch_updated_leads_raw(pipeline_id, updated_from, ...)` — выгрузка необработанных сделок за период
|
|
48
50
|
|
|
51
|
+
### ETL модуль
|
|
52
|
+
|
|
53
|
+
- **Extractors**: извлечение данных из amoCRM (сделки, контакты, события, примечания)
|
|
54
|
+
- **Transformers**: преобразование в табличный формат для БД
|
|
55
|
+
- **Loaders**: загрузка в PostgreSQL с UPSERT логикой и сохранением внутренних ID
|
|
56
|
+
- **Migrations**: автоматическое создание таблиц и схем
|
|
57
|
+
- **Incremental sync**: инкрементальная синхронизация по updated_at
|
|
58
|
+
- Интеграция с **Apache Airflow** для автоматизации ETL процессов
|
|
59
|
+
|
|
49
60
|
## Требования к окружению
|
|
50
61
|
|
|
51
|
-
Python 3.
|
|
62
|
+
Python 3.6 или новее.
|
|
52
63
|
|
|
53
64
|
## Установка
|
|
54
65
|
|
|
55
|
-
Установите зависимости командой:
|
|
56
|
-
|
|
57
66
|
```bash
|
|
58
|
-
pip install
|
|
67
|
+
pip install amochka
|
|
59
68
|
```
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
Для использования ETL модуля установите дополнительные зависимости:
|
|
62
71
|
|
|
63
72
|
```bash
|
|
64
|
-
pip install amochka
|
|
73
|
+
pip install amochka psycopg2-binary python-dotenv
|
|
65
74
|
```
|
|
66
75
|
|
|
67
76
|
## Кэширование кастомных полей
|
|
68
77
|
|
|
69
78
|
Для уменьшения количества запросов к API кастомные поля кэшируются персистентно. Если параметр cache_file не указан, имя файла кэша генерируется автоматически на основе домена amoCRM-аккаунта. Вы можете обновлять кэш принудительно, передавая параметр force_update=True в метод get_custom_fields_mapping() или настроить время жизни кэша (по умолчанию — 24 часа).
|
|
70
79
|
|
|
71
|
-
##
|
|
80
|
+
## Примеры использования
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
### Быстрый старт: выгрузка обновленных сделок
|
|
74
83
|
|
|
75
84
|
```python
|
|
76
85
|
from datetime import datetime, timedelta
|
|
77
86
|
from amochka import AmoCRMClient, CacheConfig
|
|
78
87
|
|
|
79
88
|
client = AmoCRMClient(
|
|
80
|
-
base_url="https://
|
|
81
|
-
token_file="
|
|
89
|
+
base_url="https://example.amocrm.ru",
|
|
90
|
+
token_file="token.json",
|
|
82
91
|
cache_config=CacheConfig.disabled(),
|
|
83
92
|
disable_logging=True
|
|
84
93
|
)
|
|
85
94
|
|
|
86
95
|
three_hours_ago = datetime.utcnow() - timedelta(hours=3)
|
|
87
|
-
client.fetch_updated_leads_raw(
|
|
96
|
+
leads = client.fetch_updated_leads_raw(
|
|
97
|
+
pipeline_id=123456,
|
|
98
|
+
updated_from=three_hours_ago,
|
|
99
|
+
save_to_file="leads.json",
|
|
100
|
+
include_contacts=True
|
|
101
|
+
)
|
|
88
102
|
```
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
### ETL: синхронизация в PostgreSQL
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from etl.config import DatabaseConfig, AmoCRMAccount
|
|
108
|
+
from etl.extractors import AmoCRMExtractor
|
|
109
|
+
from etl.loaders import PostgresLoader
|
|
110
|
+
from etl.run_etl import sync_leads_with_contacts
|
|
111
|
+
from datetime import datetime, timezone
|
|
112
|
+
|
|
113
|
+
# Настройка БД
|
|
114
|
+
db_config = DatabaseConfig(
|
|
115
|
+
host="localhost",
|
|
116
|
+
port=5432,
|
|
117
|
+
dbname="amocrm",
|
|
118
|
+
user="postgres",
|
|
119
|
+
password="password",
|
|
120
|
+
schema="public"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Настройка amoCRM аккаунта
|
|
124
|
+
account = AmoCRMAccount(
|
|
125
|
+
id=1,
|
|
126
|
+
name="main",
|
|
127
|
+
base_url="https://example.amocrm.ru",
|
|
128
|
+
token_path="token.json",
|
|
129
|
+
mybi_account_id=1,
|
|
130
|
+
pipeline_ids=[123456]
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# ETL процесс
|
|
134
|
+
loader = PostgresLoader(db_config)
|
|
135
|
+
extractor = AmoCRMExtractor(account)
|
|
136
|
+
|
|
137
|
+
result = sync_leads_with_contacts(
|
|
138
|
+
extractor=extractor,
|
|
139
|
+
loader=loader,
|
|
140
|
+
mybi_account_id=1,
|
|
141
|
+
updated_from=datetime(2025, 1, 1, tzinfo=timezone.utc),
|
|
142
|
+
updated_to=datetime.now(timezone.utc),
|
|
143
|
+
pipeline_ids=[123456]
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
print(f"Загружено сделок: {result['leads_count']}")
|
|
147
|
+
print(f"Загружено контактов: {result['contacts_count']}")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Пример структуры данных
|
|
91
151
|
|
|
92
152
|
```json
|
|
93
153
|
[
|
|
94
154
|
{
|
|
95
|
-
"id":
|
|
96
|
-
"name": "
|
|
155
|
+
"id": 12345678,
|
|
156
|
+
"name": "Сделка: Заявка от клиента",
|
|
97
157
|
"custom_fields_values": [
|
|
98
158
|
{
|
|
99
|
-
"field_name": "
|
|
100
|
-
"values": [{"value": "
|
|
159
|
+
"field_name": "utm_source",
|
|
160
|
+
"values": [{"value": "google"}]
|
|
101
161
|
}
|
|
102
162
|
],
|
|
103
163
|
"_embedded": {
|
|
104
164
|
"tags": [
|
|
105
|
-
{"id":
|
|
165
|
+
{"id": 123, "name": "Приоритетный клиент"}
|
|
106
166
|
]
|
|
107
167
|
}
|
|
108
168
|
}
|
|
109
169
|
]
|
|
110
170
|
```
|
|
111
171
|
|
|
112
|
-
|
|
172
|
+
## Интеграция с Apache Airflow
|
|
173
|
+
|
|
174
|
+
Модуль ETL разработан для использования в Airflow DAG. Пример минимального DAG:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from airflow.decorators import dag, task
|
|
178
|
+
from etl.config import DatabaseConfig, AmoCRMAccount
|
|
179
|
+
from etl.run_etl import sync_leads_with_contacts
|
|
180
|
+
|
|
181
|
+
@dag(schedule_interval=None)
|
|
182
|
+
def amocrm_sync():
|
|
183
|
+
@task
|
|
184
|
+
def sync_data():
|
|
185
|
+
db_config = DatabaseConfig.from_env()
|
|
186
|
+
account = AmoCRMAccount.from_env()
|
|
187
|
+
# ... ETL процесс
|
|
188
|
+
|
|
189
|
+
amocrm_sync()
|
|
190
|
+
```
|
|
113
191
|
|
|
114
192
|
## Тесты
|
|
115
193
|
|
|
116
|
-
|
|
194
|
+
Запустить тесты можно командой:
|
|
117
195
|
|
|
118
196
|
```bash
|
|
119
197
|
pytest -q
|
|
120
198
|
```
|
|
121
199
|
|
|
122
|
-
|
|
200
|
+
Тесты проверяют основную функциональность API клиента и помогают убедиться, что изменения в коде не ломают работу библиотеки.
|
|
123
201
|
|
|
124
|
-
##
|
|
202
|
+
## Лицензия
|
|
125
203
|
|
|
126
|
-
|
|
204
|
+
MIT
|
|
@@ -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
|
]
|
amochka-0.3.0/README.md
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# amochka
|
|
2
|
-
|
|
3
|
-
Официальная документация API amocrm - https://www.amocrm.ru/developers/content/crm_platform/api-reference
|
|
4
|
-
|
|
5
|
-
**amochka** — библиотека для работы с API amoCRM на Python. Она поддерживает:
|
|
6
|
-
- Получение данных сделок с вложенными сущностями (контакты, компании, теги, и т.д.)
|
|
7
|
-
- Редактирование сделок, включая обновление стандартных и кастомных полей
|
|
8
|
-
- Поддержку нескольких amoCRM-аккаунтов с персистентным кэшированием кастомных полей для каждого аккаунта отдельно
|
|
9
|
-
- Ограничение запросов (7 запросов в секунду) с использованием декораторов из библиотеки `ratelimit`
|
|
10
|
-
|
|
11
|
-
### Основные функции
|
|
12
|
-
|
|
13
|
-
- `get_deal_by_id(deal_id)` — получение детальной информации по сделке
|
|
14
|
-
- `get_pipelines()` — список воронок и статусов
|
|
15
|
-
- `fetch_updated_leads_raw(pipeline_id, updated_from, ...)` — выгрузка необработанных сделок за период
|
|
16
|
-
|
|
17
|
-
## Требования к окружению
|
|
18
|
-
|
|
19
|
-
Python 3.8 или новее. Потребуются пакеты `requests` и `ratelimit`.
|
|
20
|
-
|
|
21
|
-
## Установка
|
|
22
|
-
|
|
23
|
-
Установите зависимости командой:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
pip install requests ratelimit
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Затем скопируйте репозиторий или установите пакет из PyPI (после публикации):
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
pip install amochka
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Кэширование кастомных полей
|
|
36
|
-
|
|
37
|
-
Для уменьшения количества запросов к API кастомные поля кэшируются персистентно. Если параметр cache_file не указан, имя файла кэша генерируется автоматически на основе домена amoCRM-аккаунта. Вы можете обновлять кэш принудительно, передавая параметр force_update=True в метод get_custom_fields_mapping() или настроить время жизни кэша (по умолчанию — 24 часа).
|
|
38
|
-
|
|
39
|
-
## Выгрузка обновленных сделок
|
|
40
|
-
|
|
41
|
-
Метод `fetch_updated_leads_raw()` позволяет получить все сделки из указанной воронки, которые были изменены в заданный промежуток времени. Результат можно сохранить в JSON-файл без какой‑либо обработки:
|
|
42
|
-
|
|
43
|
-
```python
|
|
44
|
-
from datetime import datetime, timedelta
|
|
45
|
-
from amochka import AmoCRMClient, CacheConfig
|
|
46
|
-
|
|
47
|
-
client = AmoCRMClient(
|
|
48
|
-
base_url="https://bneginskogo.amocrm.ru",
|
|
49
|
-
token_file="/path/to/token.json",
|
|
50
|
-
cache_config=CacheConfig.disabled(),
|
|
51
|
-
disable_logging=True
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
three_hours_ago = datetime.utcnow() - timedelta(hours=3)
|
|
55
|
-
client.fetch_updated_leads_raw(6241334, updated_from=three_hours_ago, save_to_file="leads.json")
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Пример получаемого JSON (укороченный):
|
|
59
|
-
|
|
60
|
-
```json
|
|
61
|
-
[
|
|
62
|
-
{
|
|
63
|
-
"id": 26282337,
|
|
64
|
-
"name": "Автосделка: Заявка от (Максим Брокер Дубай Бюро Негинского)",
|
|
65
|
-
"custom_fields_values": [
|
|
66
|
-
{
|
|
67
|
-
"field_name": "roistat",
|
|
68
|
-
"values": [{"value": "2026"}]
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
|
-
"_embedded": {
|
|
72
|
-
"tags": [
|
|
73
|
-
{"id": 179813, "name": "WZ (Федор 971568113315)"}
|
|
74
|
-
]
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
]
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Для подключения к реальному аккаунту сохраните JSON с OAuth‑токеном и укажите его путь в параметре `token_file` при создании клиента. Базовый URL можно взять из переменной окружения `AMO_BASE_URL`.
|
|
81
|
-
|
|
82
|
-
## Тесты
|
|
83
|
-
|
|
84
|
-
Файл `tests/test_client.py` содержит небольшой набор автоматических тестов, написанных на [pytest](https://docs.pytest.org/). Они запускают методы клиента на подставном классе `DummyClient` и проверяют, что функции работают так, как ожидается. Запустить тесты можно командой:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
pytest -q
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
Эти тесты помогают убедиться, что изменения в коде не ломают основную функциональность.
|
|
91
|
-
|
|
92
|
-
## Пример использования `fetch_updated_leads_raw`
|
|
93
|
-
|
|
94
|
-
Кроме примера в разделе выше, код из `example_fetch.py` демонстрирует полный процесс получения сделок и сохранения их в файл.
|
|
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
|