amochka 0.4.0__py3-none-any.whl → 0.4.3__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.
amochka/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  amochka: Библиотека для работы с API amoCRM.
3
3
  """
4
4
 
5
- __version__ = "0.4.0"
5
+ __version__ = "0.4.3"
6
6
 
7
7
  from .client import AmoCRMClient, CacheConfig
8
8
  from .errors import (
amochka/client.py CHANGED
@@ -477,11 +477,15 @@ class AmoCRMClient:
477
477
  'redirect_uri': os.environ.get('AMOCRM_REDIRECT_URI'),
478
478
  }
479
479
  # 2. Загружаем из файла или строки
480
- elif os.path.exists(self.token_file):
480
+ elif self.token_file and os.path.exists(self.token_file):
481
481
  with open(self.token_file, 'r') as f:
482
482
  data = json.load(f)
483
483
  self.logger.debug(f"Token loaded from file: {self.token_file}")
484
484
  else:
485
+ if not self.token_file:
486
+ raise AuthenticationError(
487
+ "Токен не найден: ни в environment variables, ни в файле, ни в переданной строке."
488
+ )
485
489
  try:
486
490
  data = json.loads(self.token_file)
487
491
  self.logger.debug("Token parsed from provided string.")
@@ -583,7 +587,11 @@ class AmoCRMClient:
583
587
  if response.status_code in (200, 204):
584
588
  if response.status_code == 204:
585
589
  return None
586
- return response.json()
590
+ try:
591
+ return response.json()
592
+ except ValueError:
593
+ preview = response.text[:200] if response.text else ""
594
+ raise APIError(response.status_code, f"Invalid JSON response: {preview}")
587
595
 
588
596
  # Retryable ошибки (429, 5xx)
589
597
  if response.status_code in retryable_status_codes:
@@ -675,7 +683,10 @@ class AmoCRMClient:
675
683
  self.logger.error(f"Не удалось обновить токен: {resp.status_code}")
676
684
  raise AuthenticationError(f"Не удалось обновить токен: {resp.status_code}")
677
685
 
678
- data = resp.json() or {}
686
+ try:
687
+ data = resp.json() or {}
688
+ except ValueError as exc:
689
+ raise AuthenticationError("Ответ refresh_token не является валидным JSON") from exc
679
690
  access_token = data.get("access_token")
680
691
  refresh_token = data.get("refresh_token", self.refresh_token)
681
692
  expires_in = data.get("expires_in")
@@ -921,7 +932,10 @@ class AmoCRMClient:
921
932
  return int(value)
922
933
  if isinstance(value, str):
923
934
  try:
924
- return int(datetime.fromisoformat(value).timestamp())
935
+ value_str = value.strip()
936
+ if value_str.endswith("Z"):
937
+ value_str = value_str[:-1] + "+00:00"
938
+ return int(datetime.fromisoformat(value_str).timestamp())
925
939
  except ValueError as exc:
926
940
  raise ValueError(f"Не удалось преобразовать '{value}' в timestamp") from exc
927
941
  raise TypeError(f"Неподдерживаемый тип для timestamp: {type(value)}")
@@ -1591,6 +1605,9 @@ class AmoCRMClient:
1591
1605
  notes = []
1592
1606
  while True:
1593
1607
  response = self._make_request("GET", endpoint, params=params)
1608
+ if not response:
1609
+ self.logger.warning(f"Empty response for notes {entity} {entity_id}, stopping pagination")
1610
+ break
1594
1611
  if response and "_embedded" in response and "notes" in response["_embedded"]:
1595
1612
  notes.extend(response["_embedded"]["notes"])
1596
1613
  if not get_all:
@@ -1625,6 +1642,8 @@ class AmoCRMClient:
1625
1642
  endpoint = f"/api/v4/{plural}/{entity_id}/notes/{note_id}"
1626
1643
  self.logger.debug(f"Fetching note {note_id} for {entity} {entity_id}")
1627
1644
  note_data = self._make_request("GET", endpoint)
1645
+ if not note_data or not isinstance(note_data, dict):
1646
+ raise APIError(0, f"Invalid response for note {note_id} {entity} {entity_id}.")
1628
1647
  self.logger.debug(f"Note {note_id} for {entity} {entity_id} fetched successfully.")
1629
1648
  return note_data
1630
1649
 
@@ -1670,6 +1689,9 @@ class AmoCRMClient:
1670
1689
  events = []
1671
1690
  while True:
1672
1691
  response = self._make_request("GET", "/api/v4/events", params=params)
1692
+ if not response:
1693
+ self.logger.warning(f"Empty response for events {entity} {entity_id}, stopping pagination")
1694
+ break
1673
1695
  if response and "_embedded" in response and "events" in response["_embedded"]:
1674
1696
  events.extend(response["_embedded"]["events"])
1675
1697
  # Если не нужно получать все страницы, выходим
@@ -1736,6 +1758,8 @@ class AmoCRMClient:
1736
1758
  endpoint = f"/api/v4/events/{event_id}"
1737
1759
  self.logger.debug(f"Fetching event with ID {event_id}")
1738
1760
  event_data = self._make_request("GET", endpoint)
1761
+ if not event_data or not isinstance(event_data, dict):
1762
+ raise APIError(0, f"Invalid response for event {event_id}.")
1739
1763
  self.logger.debug(f"Event {event_id} details fetched successfully.")
1740
1764
  return event_data
1741
1765
 
amochka/etl.py CHANGED
@@ -123,6 +123,8 @@ def export_contacts_to_ndjson(
123
123
  params["limit"] = limit
124
124
  while True:
125
125
  response = client._make_request("GET", "/api/v4/contacts", params=params)
126
+ if not response:
127
+ break
126
128
  embedded = (response or {}).get("_embedded", {})
127
129
  contacts = embedded.get("contacts") or []
128
130
  if not contacts:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amochka
3
- Version: 0.4.0
3
+ Version: 0.4.3
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
@@ -1,7 +1,7 @@
1
- amochka/__init__.py,sha256=aF51XIVOetHHu7xVAe--atPb6LrGzDgRo8uSbJW2icg,925
2
- amochka/client.py,sha256=WFPlkBolKory7juyIYjqxFCw6QGN2Z7KSKlMd_KoLIY,87757
1
+ amochka/__init__.py,sha256=SXqHWqb3-E2ixu87d6j8Bw8MF7TJ_3_hzSFe3vFUUVk,925
2
+ amochka/client.py,sha256=z4E25n5JkK64Si_RZkc-rD735a9sxmWDtGqgGnffpIw,89189
3
3
  amochka/errors.py,sha256=Rg4U9srjboQwgU3wwhAed0p3vGcc0ZhuyKHIDhYFnp8,1371
4
- amochka/etl.py,sha256=N8rXNFbtmlKfsYpgr7HDcP4enoj63XQPWuTDxGuMhw4,8901
4
+ amochka/etl.py,sha256=tzdGPRGxR49aSAjLksic-FqechvDUTfK8ZUUJGuIU7M,8960
5
5
  etl/__init__.py,sha256=bp9fPqbKlOc7xzs27diHEvysy1FgBrwlpX6GnR6GL9U,255
6
6
  etl/config.py,sha256=BvaGn5BSGMIfvUNNsnap04iy3BHyMOuRX81G7EiLUfE,9032
7
7
  etl/extractors.py,sha256=PqjzlmUa8FLZa1z85mP6Y0-s_TH3REqW58632JzRKUc,13047
@@ -9,7 +9,7 @@ etl/loaders.py,sha256=x8PcDQoq2kjbd52H2VzKKz5vHzyD6DXSsb9X0foPX_U,31941
9
9
  etl/run_etl.py,sha256=LxKQLE_yUPkg7kaFmmMxrdggz27puS1VdV9NTna9e4Q,26665
10
10
  etl/transformers.py,sha256=OwYJ_9l3oqvy2Y3-umXjAGweOIqlfRI0iSiCFPrcQ8E,17867
11
11
  etl/migrations/001_create_tables.sql,sha256=YrSaZjpofC1smjYx0bM4eHQumboruIBY3fwRDlJLLSo,15749
12
- amochka-0.4.0.dist-info/METADATA,sha256=c9I25LoMxn4pI0pLFAIKw3qG992_9nl5YQmLSynK-LA,7530
13
- amochka-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- amochka-0.4.0.dist-info/top_level.txt,sha256=grRX8aLFG-yYKPsAqCD6sUBmdLSQeOMHsc9Dl6S7Lzo,12
15
- amochka-0.4.0.dist-info/RECORD,,
12
+ amochka-0.4.3.dist-info/METADATA,sha256=nRqy4mnZ-FRsmNdZNCLsooEZjQKbceZwDdzQ_cAEoyU,7530
13
+ amochka-0.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ amochka-0.4.3.dist-info/top_level.txt,sha256=grRX8aLFG-yYKPsAqCD6sUBmdLSQeOMHsc9Dl6S7Lzo,12
15
+ amochka-0.4.3.dist-info/RECORD,,