amochka 0.4.7__py3-none-any.whl → 0.4.8__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.7"
5
+ __version__ = "0.4.8"
6
6
 
7
7
  from .client import AmoCRMClient, CacheConfig
8
8
  from .errors import (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amochka
3
- Version: 0.4.7
3
+ Version: 0.4.8
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,15 +1,15 @@
1
- amochka/__init__.py,sha256=1IrHrHoIJWMJgXlmxVzX93HXxTgDL7agZRR7GeP6AiY,925
1
+ amochka/__init__.py,sha256=yvD3BuCLtzagE2oTYt36fPKGYpcImv2mY590XqBsXwI,925
2
2
  amochka/client.py,sha256=zQUy6DhoQfLDnO9q7CxXd28cjLQmwE15MtTGsGYYVSs,91571
3
3
  amochka/errors.py,sha256=Rg4U9srjboQwgU3wwhAed0p3vGcc0ZhuyKHIDhYFnp8,1371
4
4
  amochka/etl.py,sha256=tzdGPRGxR49aSAjLksic-FqechvDUTfK8ZUUJGuIU7M,8960
5
5
  etl/__init__.py,sha256=bp9fPqbKlOc7xzs27diHEvysy1FgBrwlpX6GnR6GL9U,255
6
- etl/config.py,sha256=BvaGn5BSGMIfvUNNsnap04iy3BHyMOuRX81G7EiLUfE,9032
6
+ etl/config.py,sha256=eBQ5U9Kulyho-XMu8gRSJ47WrnONiiWyL2l3GtZhrNs,9416
7
7
  etl/extractors.py,sha256=PqjzlmUa8FLZa1z85mP6Y0-s_TH3REqW58632JzRKUc,13047
8
8
  etl/loaders.py,sha256=AA4XDCbre-Y8SrIji5Sk8olW9WvREM9CMuuknFVe5j0,32589
9
- etl/run_etl.py,sha256=6ieidJiiXOILI1hzFzCCyVjUHX6wCJ-D6556NUqKOxw,26847
10
- etl/transformers.py,sha256=OwYJ_9l3oqvy2Y3-umXjAGweOIqlfRI0iSiCFPrcQ8E,17867
9
+ etl/run_etl.py,sha256=wCpCs2vL9cxucxHjcpPQydja9JzFxaRlfc9CwpS9OGU,27270
10
+ etl/transformers.py,sha256=ML9iBtLRh8OgFtSK-1ngmRw6vJu_GXHCbln1U8d7sHc,18150
11
11
  etl/migrations/001_create_tables.sql,sha256=YrSaZjpofC1smjYx0bM4eHQumboruIBY3fwRDlJLLSo,15749
12
- amochka-0.4.7.dist-info/METADATA,sha256=W_0rYdYd14YBxn0hp625NmZJtVzPVOY-ERR9hdKsrBQ,7530
13
- amochka-0.4.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
- amochka-0.4.7.dist-info/top_level.txt,sha256=grRX8aLFG-yYKPsAqCD6sUBmdLSQeOMHsc9Dl6S7Lzo,12
15
- amochka-0.4.7.dist-info/RECORD,,
12
+ amochka-0.4.8.dist-info/METADATA,sha256=gB8ZgvtYOkXy6wmxyQbV6gxhs8eHOx9H5-XTV9yrYao,7530
13
+ amochka-0.4.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
+ amochka-0.4.8.dist-info/top_level.txt,sha256=grRX8aLFG-yYKPsAqCD6sUBmdLSQeOMHsc9Dl6S7Lzo,12
15
+ amochka-0.4.8.dist-info/RECORD,,
etl/config.py CHANGED
@@ -32,6 +32,12 @@ def _load_env_file(path: Path) -> Dict[str, str]:
32
32
  return env
33
33
 
34
34
 
35
+ def _get_bool(value: Optional[str], default: bool = False) -> bool:
36
+ if value is None:
37
+ return default
38
+ return value.strip().lower() in {"1", "true", "yes", "y", "on"}
39
+
40
+
35
41
  @dataclass
36
42
  class DatabaseConfig:
37
43
  """Конфигурация подключения к PostgreSQL."""
@@ -139,6 +145,7 @@ class ETLConfig:
139
145
  batch_size: int = 100
140
146
  window_minutes: int = 120 # Окно выгрузки по умолчанию (2 часа)
141
147
  log_level: str = "INFO"
148
+ include_companies: bool = False
142
149
 
143
150
  @classmethod
144
151
  def from_env(cls, env_path: Optional[Path] = None) -> "ETLConfig":
@@ -180,12 +187,15 @@ class ETLConfig:
180
187
  )
181
188
  )
182
189
 
190
+ include_companies = _get_bool(os.environ.get("ETL_INCLUDE_COMPANIES"), False)
191
+
183
192
  return cls(
184
193
  database=db_config,
185
194
  accounts=accounts,
186
195
  batch_size=int(os.environ.get("ETL_BATCH_SIZE", "100") or "100"),
187
196
  window_minutes=int(os.environ.get("ETL_WINDOW_MINUTES", "120") or "120"),
188
197
  log_level=os.environ.get("ETL_LOG_LEVEL", "INFO") or "INFO",
198
+ include_companies=include_companies,
189
199
  )
190
200
 
191
201
 
@@ -213,6 +223,7 @@ DEFAULT_CONFIG = ETLConfig(
213
223
  ],
214
224
  batch_size=100,
215
225
  window_minutes=120,
226
+ include_companies=False,
216
227
  )
217
228
 
218
229
 
etl/run_etl.py CHANGED
@@ -88,6 +88,7 @@ def sync_leads_with_contacts(
88
88
  updated_from: Optional[datetime] = None,
89
89
  updated_to: Optional[datetime] = None,
90
90
  pipeline_ids: Optional[List[int]] = None,
91
+ include_companies: bool = False,
91
92
  batch_size: int = 100,
92
93
  ) -> dict:
93
94
  """
@@ -101,11 +102,16 @@ def sync_leads_with_contacts(
101
102
  Returns:
102
103
  dict с ключами: leads_count, contacts_count
103
104
  """
104
- contact_transformer = ContactTransformer(mybi_account_id)
105
+ contact_transformer = ContactTransformer(mybi_account_id, include_companies=include_companies)
105
106
 
106
107
  # Загружаем справочники для денормализации
107
108
  pipelines_map, statuses_map = extractor.load_pipelines_and_statuses()
108
- lead_transformer = LeadTransformer(mybi_account_id, pipelines_map, statuses_map)
109
+ lead_transformer = LeadTransformer(
110
+ mybi_account_id,
111
+ pipelines_map,
112
+ statuses_map,
113
+ include_companies=include_companies,
114
+ )
109
115
 
110
116
  # 1. Собираем сделки и ID контактов из API
111
117
  leads_iter = extractor.iter_leads(
@@ -261,6 +267,7 @@ def sync_contacts(
261
267
  contact_ids: Optional[Set[int]] = None,
262
268
  updated_from: Optional[datetime] = None,
263
269
  updated_to: Optional[datetime] = None,
270
+ include_companies: bool = False,
264
271
  batch_size: int = 100,
265
272
  ) -> int:
266
273
  """
@@ -279,7 +286,7 @@ def sync_contacts(
279
286
  Returns:
280
287
  Количество загруженных контактов
281
288
  """
282
- contact_transformer = ContactTransformer(mybi_account_id)
289
+ contact_transformer = ContactTransformer(mybi_account_id, include_companies=include_companies)
283
290
 
284
291
  # Загружаем все обновлённые контакты за период
285
292
  contacts_iter = extractor.iter_contacts(
@@ -484,6 +491,7 @@ def run_etl_for_account(
484
491
  entities: List[str],
485
492
  window_minutes: int,
486
493
  full_sync: bool = False,
494
+ include_companies: bool = False,
487
495
  batch_size: int = 100,
488
496
  ) -> dict:
489
497
  """
@@ -537,6 +545,7 @@ def run_etl_for_account(
537
545
  updated_from=leads_updated_from,
538
546
  updated_to=updated_to,
539
547
  pipeline_ids=account.pipeline_ids,
548
+ include_companies=include_companies,
540
549
  batch_size=batch_size,
541
550
  )
542
551
  stats["leads"] = result["leads_count"]
@@ -578,6 +587,7 @@ def run_etl_for_account(
578
587
  contact_ids=contact_ids_in_pipelines, # Только контакты из наших воронок
579
588
  updated_from=contacts_updated_from,
580
589
  updated_to=updated_to,
590
+ include_companies=include_companies,
581
591
  batch_size=batch_size,
582
592
  )
583
593
  else:
@@ -677,6 +687,7 @@ def main():
677
687
  entities,
678
688
  config.window_minutes,
679
689
  full_sync=args.full,
690
+ include_companies=config.include_companies,
680
691
  batch_size=config.batch_size,
681
692
  )
682
693
  all_stats[account.name] = stats
etl/transformers.py CHANGED
@@ -78,7 +78,13 @@ class TransformedEvent:
78
78
  class LeadTransformer:
79
79
  """Трансформер для сделок (leads)."""
80
80
 
81
- def __init__(self, account_id: int, pipelines_map: Optional[Dict[int, str]] = None, statuses_map: Optional[Dict[int, Dict]] = None):
81
+ def __init__(
82
+ self,
83
+ account_id: int,
84
+ pipelines_map: Optional[Dict[int, str]] = None,
85
+ statuses_map: Optional[Dict[int, Dict]] = None,
86
+ include_companies: bool = False,
87
+ ):
82
88
  """
83
89
  Инициализирует трансформер.
84
90
 
@@ -90,6 +96,7 @@ class LeadTransformer:
90
96
  self.account_id = account_id
91
97
  self.pipelines_map = pipelines_map or {}
92
98
  self.statuses_map = statuses_map or {}
99
+ self.include_companies = include_companies
93
100
 
94
101
  def transform(self, lead: Dict[str, Any]) -> TransformedLead:
95
102
  """
@@ -145,7 +152,7 @@ class LeadTransformer:
145
152
  main_contact_id = contacts[0].get("id")
146
153
 
147
154
  # Компания из _embedded.companies
148
- companies = embedded.get("companies", [])
155
+ companies = embedded.get("companies", []) if self.include_companies else []
149
156
  company_id = companies[0].get("id") if companies else None
150
157
 
151
158
  lead_facts_record = {
@@ -221,8 +228,9 @@ class LeadTransformer:
221
228
  class ContactTransformer:
222
229
  """Трансформер для контактов."""
223
230
 
224
- def __init__(self, account_id: int):
231
+ def __init__(self, account_id: int, include_companies: bool = False):
225
232
  self.account_id = account_id
233
+ self.include_companies = include_companies
226
234
 
227
235
  def transform(self, contact: Dict[str, Any]) -> TransformedContact:
228
236
  """Преобразует контакт из amoCRM в структуру mybi."""
@@ -249,7 +257,7 @@ class ContactTransformer:
249
257
 
250
258
  # Компания из _embedded
251
259
  embedded = contact.get("_embedded", {})
252
- companies = embedded.get("companies", [])
260
+ companies = embedded.get("companies", []) if self.include_companies else []
253
261
  if companies:
254
262
  contact_record["company"] = companies[0].get("name")
255
263