amochka 0.1.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: amochka
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Библиотека для работы с API amoCRM
5
5
  Home-page: UNKNOWN
6
6
  Author: Timurka
@@ -19,7 +19,15 @@ RATE_LIMIT = 7 # Максимум 7 запросов в секунду
19
19
  class Deal(dict):
20
20
  """
21
21
  Объект сделки расширяет стандартный словарь данными из custom_fields_values.
22
- (Описание класса без изменений)
22
+
23
+ Обеспечивает два способа доступа к кастомным полям:
24
+ 1. get(key): при обращении по названию (строкой) или по ID поля (integer)
25
+ возвращает текстовое значение поля (например, «Дурина Юлия»).
26
+ 2. get_id(key): возвращает идентификатор выбранного варианта (enum_id) для полей типа select.
27
+ Если в данных enum_id отсутствует, производится поиск в переданной конфигурации полей,
28
+ сравнение выполняется без учёта регистра и лишних пробелов.
29
+
30
+ Параметр custom_fields_config – словарь, где ключи – ID полей, а значения – модели полей.
23
31
  """
24
32
  def __init__(self, data, custom_fields_config=None):
25
33
  super().__init__(data)
@@ -33,9 +41,11 @@ class Deal(dict):
33
41
  values = field.get("values")
34
42
  if field_name and values and isinstance(values, list) and len(values) > 0:
35
43
  key_name = field_name.lower().strip()
36
- # Сохраняем текстовое значение для доступа по названию
37
- self._custom[key_name] = values[0].get("value")
38
- logger.debug(f"Set custom field '{key_name}' = {self._custom[key_name]}")
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}}}")
39
49
  field_id = field.get("field_id")
40
50
  if field_id is not None and values and isinstance(values, list) and len(values) > 0:
41
51
  stored_value = values[0].get("value")
@@ -72,7 +82,12 @@ class Deal(dict):
72
82
  def get_id(self, key, default=None):
73
83
  """
74
84
  Возвращает идентификатор выбранного варианта (enum_id) для кастомного поля.
75
- (Описание метода без изменений)
85
+ Если значение enum_id отсутствует в данных, производится поиск в конфигурации кастомных полей,
86
+ сравнение значения выполняется без учёта регистра и пробелов.
87
+
88
+ :param key: Название поля (строка) или ID поля (integer).
89
+ :param default: Значение по умолчанию, если enum_id не найден.
90
+ :return: Идентификатор выбранного варианта (целое число) или default.
76
91
  """
77
92
  stored = None
78
93
  if isinstance(key, str):
@@ -105,22 +120,57 @@ class Deal(dict):
105
120
  class AmoCRMClient:
106
121
  """
107
122
  Клиент для работы с API amoCRM.
108
- (Описание класса без изменений, за исключением добавления параметра use_file_cache)
123
+
124
+ Основные функции:
125
+ - load_token: Загружает и проверяет токен авторизации.
126
+ - _make_request: Выполняет HTTP-запрос с учетом ограничения по скорости.
127
+ - get_deal_by_id: Получает данные сделки по ID и возвращает объект Deal.
128
+ - get_custom_fields_mapping: Загружает и кэширует список кастомных полей.
129
+ - find_custom_field_id: Ищет кастомное поле по его названию.
130
+ - update_lead: Обновляет сделку, включая стандартные и кастомные поля.
131
+
132
+ Дополнительно можно задать уровень логирования через параметр log_level,
133
+ либо полностью отключить логирование, установив disable_logging=True.
109
134
  """
110
- def __init__(self, base_url, token_file=None, cache_file=None, log_level=logging.INFO, disable_logging=False, use_file_cache=True):
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
+ ):
111
146
  """
112
- Инициализирует клиента, задавая базовый URL, токен авторизации и файл кэша для кастомных полей.
147
+ Инициализирует клиента, задавая базовый URL, токен авторизации и настройки кэша для кастомных полей.
113
148
 
114
- :param use_file_cache: Если True, кэш будет сохраняться в файл; иначе — только в оперативной памяти.
149
+ :param base_url: Базовый URL API amoCRM.
150
+ :param token_file: Файл, содержащий токен авторизации.
151
+ :param cache_file: Файл для кэширования данных кастомных полей.
152
+ :param log_level: Уровень логирования (например, logging.DEBUG, logging.INFO).
153
+ :param disable_logging: Если True, логирование будет отключено.
154
+ :param cache_enabled: Если False, кэширование отключается (остальные параметры игнорируются).
155
+ :param cache_storage: 'file' для файлового кэша, 'memory' для кэша только в оперативной памяти.
156
+ :param cache_hours: Время жизни кэша в часах. Если None – кэш считается бесконечным.
115
157
  """
116
158
  self.base_url = base_url.rstrip('/')
117
159
  domain = self.base_url.split("//")[-1].split(".")[0]
118
160
  self.domain = domain
119
161
  self.token_file = token_file or os.path.join(os.path.expanduser('~'), '.amocrm_token.json')
120
- if not cache_file:
121
- cache_file = f"custom_fields_cache_{self.domain}.json"
122
- self.cache_file = cache_file
123
- self.use_file_cache = use_file_cache
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
+
124
174
  self.token = self.load_token()
125
175
  self._custom_fields_mapping = None
126
176
 
@@ -132,7 +182,12 @@ class AmoCRMClient:
132
182
  logger.debug(f"AmoCRMClient initialized for domain {self.domain}")
133
183
 
134
184
  def load_token(self):
135
- # Метод без изменений
185
+ """
186
+ Загружает токен авторизации из файла или строки, проверяет его срок действия.
187
+
188
+ :return: Действительный access_token.
189
+ :raises Exception: Если токен не найден или истёк.
190
+ """
136
191
  data = None
137
192
  if os.path.exists(self.token_file):
138
193
  with open(self.token_file, 'r') as f:
@@ -160,7 +215,16 @@ class AmoCRMClient:
160
215
  @sleep_and_retry
161
216
  @limits(calls=RATE_LIMIT, period=1)
162
217
  def _make_request(self, method, endpoint, params=None, data=None):
163
- # Метод без изменений
218
+ """
219
+ Выполняет HTTP-запрос к API amoCRM с учетом ограничения по скорости (rate limit).
220
+
221
+ :param method: HTTP-метод (GET, PATCH, POST, DELETE и т.д.).
222
+ :param endpoint: Конечная точка API (начинается с /api/v4/).
223
+ :param params: GET-параметры запроса.
224
+ :param data: Данные, отправляемые в JSON-формате.
225
+ :return: Ответ в формате JSON или None (если статус 204).
226
+ :raises Exception: При получении кода ошибки, отличного от 200/204.
227
+ """
164
228
  url = f"{self.base_url}{endpoint}"
165
229
  headers = {
166
230
  "Authorization": f"Bearer {self.token}",
@@ -176,7 +240,12 @@ class AmoCRMClient:
176
240
  return response.json()
177
241
 
178
242
  def get_deal_by_id(self, deal_id):
179
- # Метод без изменений
243
+ """
244
+ Получает данные сделки по её ID и возвращает объект Deal.
245
+
246
+ :param deal_id: ID сделки.
247
+ :return: Объект Deal, включающий данные стандартных и кастомных полей.
248
+ """
180
249
  endpoint = f"/api/v4/leads/{deal_id}"
181
250
  params = {'with': 'contacts,companies,catalog_elements,loss_reason,tags'}
182
251
  data = self._make_request("GET", endpoint, params=params)
@@ -187,10 +256,13 @@ class AmoCRMClient:
187
256
  def _save_custom_fields_cache(self, mapping):
188
257
  """
189
258
  Сохраняет кэш кастомных полей в файл, если используется файловый кэш.
190
- Если файловый кэш не используется, операция пропускается.
259
+ Если кэширование отключено или выбран кэш в памяти, операция пропускается.
191
260
  """
192
- if not self.use_file_cache:
193
- logger.debug("File caching disabled; cache stored in memory only.")
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.")
194
266
  return
195
267
  cache_data = {"last_updated": time.time(), "mapping": mapping}
196
268
  with open(self.cache_file, "w") as f:
@@ -200,10 +272,13 @@ class AmoCRMClient:
200
272
  def _load_custom_fields_cache(self):
201
273
  """
202
274
  Загружает кэш кастомных полей из файла, если используется файловый кэш.
203
- Если файловый кэш не используется, возвращает None.
275
+ Если кэширование отключено или выбран кэш в памяти, возвращает None.
204
276
  """
205
- if not self.use_file_cache:
206
- logger.debug("File caching disabled; no cache loaded from file.")
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.")
207
282
  return None
208
283
  if os.path.exists(self.cache_file):
209
284
  with open(self.cache_file, "r") as f:
@@ -216,21 +291,27 @@ class AmoCRMClient:
216
291
  return None
217
292
  return None
218
293
 
219
- def get_custom_fields_mapping(self, force_update=False, cache_duration_hours=24):
294
+ def get_custom_fields_mapping(self, force_update=False):
220
295
  """
221
296
  Возвращает словарь отображения кастомных полей для сделок.
222
297
  Если данные кэшированы и не устарели, возвращает кэш; иначе выполняет запросы для получения данных.
223
298
  """
224
- if not force_update:
225
- if self._custom_fields_mapping:
226
- return self._custom_fields_mapping
227
- cache_data = self._load_custom_fields_cache()
228
- if cache_data:
229
- last_updated = cache_data.get("last_updated", 0)
230
- if time.time() - last_updated < cache_duration_hours * 3600:
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:
231
307
  self._custom_fields_mapping = cache_data.get("mapping")
232
308
  logger.debug("Using cached custom fields mapping.")
233
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
234
315
 
235
316
  mapping = {}
236
317
  page = 1
@@ -249,12 +330,16 @@ class AmoCRMClient:
249
330
 
250
331
  logger.debug("Custom fields mapping fetched (содержимое маппинга не выводится полностью).")
251
332
  self._custom_fields_mapping = mapping
252
- self._save_custom_fields_cache(mapping)
333
+ if self.cache_enabled:
334
+ self._save_custom_fields_cache(mapping)
253
335
  return mapping
254
336
 
255
337
  def find_custom_field_id(self, search_term):
256
338
  """
257
339
  Ищет кастомное поле по заданному названию (или части названия).
340
+
341
+ :param search_term: Строка для поиска по имени поля.
342
+ :return: Кортеж (field_id, field_obj) если найдено, иначе (None, None).
258
343
  """
259
344
  mapping = self.get_custom_fields_mapping()
260
345
  search_term_lower = search_term.lower().strip()
@@ -272,6 +357,18 @@ class AmoCRMClient:
272
357
  def update_lead(self, lead_id, update_fields: dict, tags_to_add: list = None, tags_to_delete: list = None):
273
358
  """
274
359
  Обновляет сделку, задавая новые значения для стандартных и кастомных полей.
360
+
361
+ Для кастомных полей:
362
+ - Если значение передается как целое число, оно интерпретируется как идентификатор варианта (enum_id)
363
+ для полей типа select.
364
+ - Если значение передается как строка, используется ключ "value".
365
+
366
+ :param lead_id: ID сделки, которую нужно обновить.
367
+ :param update_fields: Словарь с полями для обновления. Ключи могут быть стандартными или названием кастомного поля.
368
+ :param tags_to_add: Список тегов для добавления к сделке.
369
+ :param tags_to_delete: Список тегов для удаления из сделки.
370
+ :return: Ответ API в формате JSON.
371
+ :raises Exception: Если одно из кастомных полей не найдено.
275
372
  """
276
373
  payload = {}
277
374
  standard_fields = {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: amochka
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Библиотека для работы с API amoCRM
5
5
  Home-page: UNKNOWN
6
6
  Author: Timurka
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='amochka',
5
- version='0.1.3',
5
+ version='0.1.4',
6
6
  packages=find_packages(),
7
7
  install_requires=[
8
8
  'requests',
File without changes
File without changes
File without changes