amochka 0.1.2.1__tar.gz → 0.1.4__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.1.2.1 → amochka-0.1.4}/PKG-INFO +1 -1
- {amochka-0.1.2.1 → amochka-0.1.4}/amochka/client.py +68 -37
- {amochka-0.1.2.1 → amochka-0.1.4}/amochka.egg-info/PKG-INFO +1 -1
- {amochka-0.1.2.1 → amochka-0.1.4}/setup.py +1 -1
- {amochka-0.1.2.1 → amochka-0.1.4}/README.md +0 -0
- {amochka-0.1.2.1 → amochka-0.1.4}/amochka/__init__.py +0 -0
- {amochka-0.1.2.1 → amochka-0.1.4}/amochka.egg-info/SOURCES.txt +0 -0
- {amochka-0.1.2.1 → amochka-0.1.4}/amochka.egg-info/dependency_links.txt +0 -0
- {amochka-0.1.2.1 → amochka-0.1.4}/amochka.egg-info/requires.txt +0 -0
- {amochka-0.1.2.1 → amochka-0.1.4}/amochka.egg-info/top_level.txt +0 -0
- {amochka-0.1.2.1 → amochka-0.1.4}/setup.cfg +0 -0
|
@@ -22,7 +22,7 @@ class Deal(dict):
|
|
|
22
22
|
|
|
23
23
|
Обеспечивает два способа доступа к кастомным полям:
|
|
24
24
|
1. get(key): при обращении по названию (строкой) или по ID поля (integer)
|
|
25
|
-
возвращает текстовое значение поля (например,
|
|
25
|
+
возвращает текстовое значение поля (например, «Дурина Юлия»).
|
|
26
26
|
2. get_id(key): возвращает идентификатор выбранного варианта (enum_id) для полей типа select.
|
|
27
27
|
Если в данных enum_id отсутствует, производится поиск в переданной конфигурации полей,
|
|
28
28
|
сравнение выполняется без учёта регистра и лишних пробелов.
|
|
@@ -41,9 +41,11 @@ class Deal(dict):
|
|
|
41
41
|
values = field.get("values")
|
|
42
42
|
if field_name and values and isinstance(values, list) and len(values) > 0:
|
|
43
43
|
key_name = field_name.lower().strip()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
stored_value = values[0].get("value")
|
|
45
|
+
stored_enum_id = values[0].get("enum_id") # может быть None для некоторых полей
|
|
46
|
+
# Сохраняем полную информацию (и для get() и для get_id())
|
|
47
|
+
self._custom[key_name] = {"value": stored_value, "enum_id": stored_enum_id}
|
|
48
|
+
logger.debug(f"Set custom field '{key_name}' = {{'value': {stored_value}, 'enum_id': {stored_enum_id}}}")
|
|
47
49
|
field_id = field.get("field_id")
|
|
48
50
|
if field_id is not None and values and isinstance(values, list) and len(values) > 0:
|
|
49
51
|
stored_value = values[0].get("value")
|
|
@@ -80,7 +82,7 @@ class Deal(dict):
|
|
|
80
82
|
def get_id(self, key, default=None):
|
|
81
83
|
"""
|
|
82
84
|
Возвращает идентификатор выбранного варианта (enum_id) для кастомного поля.
|
|
83
|
-
Если значение enum_id отсутствует в данных, производится поиск в конфигурации полей,
|
|
85
|
+
Если значение enum_id отсутствует в данных, производится поиск в конфигурации кастомных полей,
|
|
84
86
|
сравнение значения выполняется без учёта регистра и пробелов.
|
|
85
87
|
|
|
86
88
|
:param key: Название поля (строка) или ID поля (integer).
|
|
@@ -99,7 +101,6 @@ class Deal(dict):
|
|
|
99
101
|
enum_id = stored.get("enum_id")
|
|
100
102
|
if enum_id is not None:
|
|
101
103
|
return enum_id
|
|
102
|
-
# Если enum_id отсутствует, пробуем найти его в конфигурации кастомных полей
|
|
103
104
|
if self._custom_config:
|
|
104
105
|
field_def = None
|
|
105
106
|
if isinstance(key, int):
|
|
@@ -112,7 +113,6 @@ class Deal(dict):
|
|
|
112
113
|
if field_def:
|
|
113
114
|
enums = field_def.get("enums") or []
|
|
114
115
|
for enum in enums:
|
|
115
|
-
# Сравниваем текстовое значение без учёта регистра и лишних пробелов
|
|
116
116
|
if enum.get("value", "").lower().strip() == stored.get("value", "").lower().strip():
|
|
117
117
|
return enum.get("id", default)
|
|
118
118
|
return default
|
|
@@ -132,23 +132,45 @@ class AmoCRMClient:
|
|
|
132
132
|
Дополнительно можно задать уровень логирования через параметр log_level,
|
|
133
133
|
либо полностью отключить логирование, установив disable_logging=True.
|
|
134
134
|
"""
|
|
135
|
-
def __init__(
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
base_url,
|
|
138
|
+
token_file=None,
|
|
139
|
+
cache_file=None,
|
|
140
|
+
log_level=logging.INFO,
|
|
141
|
+
disable_logging=False,
|
|
142
|
+
cache_enabled=True,
|
|
143
|
+
cache_storage='file', # 'file' или 'memory'
|
|
144
|
+
cache_hours=24 # время жизни кэша в часах, или None для бесконечного кэша
|
|
145
|
+
):
|
|
136
146
|
"""
|
|
137
|
-
Инициализирует клиента, задавая базовый URL, токен авторизации и
|
|
138
|
-
|
|
147
|
+
Инициализирует клиента, задавая базовый URL, токен авторизации и настройки кэша для кастомных полей.
|
|
148
|
+
|
|
139
149
|
:param base_url: Базовый URL API amoCRM.
|
|
140
150
|
:param token_file: Файл, содержащий токен авторизации.
|
|
141
151
|
:param cache_file: Файл для кэширования данных кастомных полей.
|
|
142
152
|
:param log_level: Уровень логирования (например, logging.DEBUG, logging.INFO).
|
|
143
153
|
:param disable_logging: Если True, логирование будет отключено.
|
|
154
|
+
:param cache_enabled: Если False, кэширование отключается (остальные параметры игнорируются).
|
|
155
|
+
:param cache_storage: 'file' для файлового кэша, 'memory' для кэша только в оперативной памяти.
|
|
156
|
+
:param cache_hours: Время жизни кэша в часах. Если None – кэш считается бесконечным.
|
|
144
157
|
"""
|
|
145
158
|
self.base_url = base_url.rstrip('/')
|
|
146
159
|
domain = self.base_url.split("//")[-1].split(".")[0]
|
|
147
160
|
self.domain = domain
|
|
148
161
|
self.token_file = token_file or os.path.join(os.path.expanduser('~'), '.amocrm_token.json')
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
162
|
+
|
|
163
|
+
# Если выбран файловый кэш, определяем имя файла, иначе он не используется
|
|
164
|
+
if cache_storage.lower() == 'file':
|
|
165
|
+
if not cache_file:
|
|
166
|
+
cache_file = f"custom_fields_cache_{self.domain}.json"
|
|
167
|
+
self.cache_file = cache_file
|
|
168
|
+
else:
|
|
169
|
+
self.cache_file = None
|
|
170
|
+
self.cache_enabled = cache_enabled
|
|
171
|
+
self.cache_storage = cache_storage.lower() # 'file' или 'memory'
|
|
172
|
+
self.cache_hours = cache_hours
|
|
173
|
+
|
|
152
174
|
self.token = self.load_token()
|
|
153
175
|
self._custom_fields_mapping = None
|
|
154
176
|
|
|
@@ -233,10 +255,15 @@ class AmoCRMClient:
|
|
|
233
255
|
|
|
234
256
|
def _save_custom_fields_cache(self, mapping):
|
|
235
257
|
"""
|
|
236
|
-
Сохраняет
|
|
237
|
-
|
|
238
|
-
:param mapping: Словарь с отображением кастомных полей.
|
|
258
|
+
Сохраняет кэш кастомных полей в файл, если используется файловый кэш.
|
|
259
|
+
Если кэширование отключено или выбран кэш в памяти, операция пропускается.
|
|
239
260
|
"""
|
|
261
|
+
if not self.cache_enabled:
|
|
262
|
+
logger.debug("Caching disabled; cache not saved.")
|
|
263
|
+
return
|
|
264
|
+
if self.cache_storage != 'file':
|
|
265
|
+
logger.debug("Using memory caching; no file cache saved.")
|
|
266
|
+
return
|
|
240
267
|
cache_data = {"last_updated": time.time(), "mapping": mapping}
|
|
241
268
|
with open(self.cache_file, "w") as f:
|
|
242
269
|
json.dump(cache_data, f)
|
|
@@ -244,10 +271,15 @@ class AmoCRMClient:
|
|
|
244
271
|
|
|
245
272
|
def _load_custom_fields_cache(self):
|
|
246
273
|
"""
|
|
247
|
-
Загружает кэш кастомных полей из
|
|
248
|
-
|
|
249
|
-
:return: Кэшированные данные или None, если не удалось загрузить.
|
|
274
|
+
Загружает кэш кастомных полей из файла, если используется файловый кэш.
|
|
275
|
+
Если кэширование отключено или выбран кэш в памяти, возвращает None.
|
|
250
276
|
"""
|
|
277
|
+
if not self.cache_enabled:
|
|
278
|
+
logger.debug("Caching disabled; no cache loaded.")
|
|
279
|
+
return None
|
|
280
|
+
if self.cache_storage != 'file':
|
|
281
|
+
logger.debug("Using memory caching; cache will be kept in memory only.")
|
|
282
|
+
return None
|
|
251
283
|
if os.path.exists(self.cache_file):
|
|
252
284
|
with open(self.cache_file, "r") as f:
|
|
253
285
|
try:
|
|
@@ -259,26 +291,27 @@ class AmoCRMClient:
|
|
|
259
291
|
return None
|
|
260
292
|
return None
|
|
261
293
|
|
|
262
|
-
def get_custom_fields_mapping(self, force_update=False
|
|
294
|
+
def get_custom_fields_mapping(self, force_update=False):
|
|
263
295
|
"""
|
|
264
296
|
Возвращает словарь отображения кастомных полей для сделок.
|
|
265
|
-
Если данные кэшированы и не устарели, возвращает кэш; иначе выполняет
|
|
266
|
-
чтобы получить все страницы (с лимитом 250 полей на запрос).
|
|
267
|
-
|
|
268
|
-
:param force_update: Принудительное обновление кэша.
|
|
269
|
-
:param cache_duration_hours: Время жизни кэша в часах.
|
|
270
|
-
:return: Словарь mapping, где ключи – ID полей, а значения – их модели.
|
|
297
|
+
Если данные кэшированы и не устарели, возвращает кэш; иначе выполняет запросы для получения данных.
|
|
271
298
|
"""
|
|
272
|
-
if not force_update:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
299
|
+
if not force_update and self._custom_fields_mapping is not None:
|
|
300
|
+
return self._custom_fields_mapping
|
|
301
|
+
|
|
302
|
+
cache_data = self._load_custom_fields_cache() if self.cache_enabled else None
|
|
303
|
+
if cache_data:
|
|
304
|
+
last_updated = cache_data.get("last_updated", 0)
|
|
305
|
+
if self.cache_hours is not None:
|
|
306
|
+
if time.time() - last_updated < self.cache_hours * 3600:
|
|
279
307
|
self._custom_fields_mapping = cache_data.get("mapping")
|
|
280
308
|
logger.debug("Using cached custom fields mapping.")
|
|
281
309
|
return self._custom_fields_mapping
|
|
310
|
+
else:
|
|
311
|
+
# Бесконечный кэш – не проверяем срок
|
|
312
|
+
self._custom_fields_mapping = cache_data.get("mapping")
|
|
313
|
+
logger.debug("Using cached custom fields mapping (infinite cache).")
|
|
314
|
+
return self._custom_fields_mapping
|
|
282
315
|
|
|
283
316
|
mapping = {}
|
|
284
317
|
page = 1
|
|
@@ -297,7 +330,8 @@ class AmoCRMClient:
|
|
|
297
330
|
|
|
298
331
|
logger.debug("Custom fields mapping fetched (содержимое маппинга не выводится полностью).")
|
|
299
332
|
self._custom_fields_mapping = mapping
|
|
300
|
-
self.
|
|
333
|
+
if self.cache_enabled:
|
|
334
|
+
self._save_custom_fields_cache(mapping)
|
|
301
335
|
return mapping
|
|
302
336
|
|
|
303
337
|
def find_custom_field_id(self, search_term):
|
|
@@ -347,14 +381,11 @@ class AmoCRMClient:
|
|
|
347
381
|
payload[key] = value
|
|
348
382
|
logger.debug(f"Standard field {key} set to {value}")
|
|
349
383
|
else:
|
|
350
|
-
# Если значение integer, интерпретируем как enum_id для полей типа select,
|
|
351
|
-
# иначе как текстовое значение.
|
|
352
384
|
if isinstance(value, int):
|
|
353
385
|
field_value_dict = {"enum_id": value}
|
|
354
386
|
else:
|
|
355
387
|
field_value_dict = {"value": value}
|
|
356
388
|
try:
|
|
357
|
-
# Если ключ уже число, считаем его field_id
|
|
358
389
|
field_id = int(key)
|
|
359
390
|
custom_fields.append({"field_id": field_id, "values": [field_value_dict]})
|
|
360
391
|
logger.debug(f"Custom field by id {field_id} set to {value}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|