ingestr 0.13.36__py3-none-any.whl → 0.13.37__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.

Potentially problematic release.


This version of ingestr might be problematic. Click here for more details.

ingestr/src/buildinfo.py CHANGED
@@ -1 +1 @@
1
- version = "v0.13.36"
1
+ version = "v0.13.37"
ingestr/src/factory.py CHANGED
@@ -54,6 +54,7 @@ from ingestr.src.sources import (
54
54
  TikTokSource,
55
55
  ZendeskSource,
56
56
  FreshdeskSource,
57
+ PhantombusterSource,
57
58
  )
58
59
 
59
60
  SQL_SOURCE_SCHEMES = [
@@ -150,6 +151,7 @@ class SourceDestinationFactory:
150
151
  "pipedrive": PipedriveSource,
151
152
  "frankfurter": FrankfurterSource,
152
153
  "freshdesk": FreshdeskSource,
154
+ "phantombuster": PhantombusterSource,
153
155
  }
154
156
  destinations: Dict[str, Type[DestinationProtocol]] = {
155
157
  "bigquery": BigQueryDestination,
@@ -0,0 +1,38 @@
1
+ from typing import Iterable, Optional
2
+
3
+ import dlt
4
+ import pendulum
5
+ import requests
6
+ from dlt.common.typing import TDataItem
7
+ from dlt.sources import DltResource
8
+ from dlt.sources.helpers.requests import Client
9
+
10
+ from ingestr.src.phantombuster.client import PhantombusterClient
11
+
12
+
13
+ def retry_on_limit(
14
+ response: Optional[requests.Response], exception: Optional[BaseException]
15
+ ) -> bool:
16
+ if response is not None and response.status_code == 429:
17
+ return True
18
+ return False
19
+
20
+
21
+ def create_client() -> requests.Session:
22
+ return Client(
23
+ raise_for_status=False,
24
+ retry_condition=retry_on_limit,
25
+ request_max_attempts=12,
26
+ request_backoff_factor=2,
27
+ ).session
28
+
29
+
30
+ @dlt.source(max_table_nesting=0)
31
+ def phantombuster_source(api_key: str, agent_id: str, start_date: pendulum.DateTime, end_date: pendulum.DateTime) -> Iterable[DltResource]:
32
+ client = PhantombusterClient(api_key)
33
+
34
+ @dlt.resource()
35
+ def completed_phantoms() -> Iterable[TDataItem]:
36
+ yield client.fetch_containers_result(create_client(), agent_id, start_date, end_date)
37
+
38
+ return completed_phantoms
@@ -0,0 +1,65 @@
1
+ from typing import Union
2
+
3
+ import pendulum
4
+ import requests
5
+
6
+
7
+ class PhantombusterClient:
8
+ def __init__(self, api_key: str):
9
+ self.api_key = api_key
10
+
11
+ def _get_headers(self):
12
+ return {
13
+ "X-Phantombuster-Key-1": self.api_key,
14
+ "accept": "application/json",
15
+ }
16
+
17
+ def fetch_containers_result(self, session: requests.Session, agent_id: str, start_date: pendulum.DateTime, end_date: pendulum.DateTime):
18
+ url = "https://api.phantombuster.com/api/v2/containers/fetch-all/"
19
+ before_ended_at = None
20
+ limit = 100
21
+ started_at = start_date.int_timestamp * 1000 + int(start_date.microsecond / 1000)
22
+ ended_at = end_date.int_timestamp * 1000 + int(end_date.microsecond / 1000)
23
+ while True:
24
+ params: dict[str, Union[str, int, float, bytes, None]] = {
25
+ "agentId": agent_id,
26
+ "limit": limit,
27
+ "mode": "finalized",
28
+ }
29
+
30
+ if before_ended_at:
31
+ params["beforeEndedAt"] = before_ended_at
32
+
33
+ response = session.get(url=url, headers=self._get_headers(), params=params)
34
+ data = response.json()
35
+ containers = data.get("containers", [])
36
+
37
+ for container in containers:
38
+ container_ended_at = container.get("endedAt")
39
+ if before_ended_at is None or before_ended_at > container["endedAt"]:
40
+ before_ended_at = container["endedAt"]
41
+
42
+ if not (started_at <= container_ended_at <= ended_at):
43
+ continue
44
+ try:
45
+ result = self.fetch_result_object(session, container["id"])
46
+ partition_dt = pendulum.from_timestamp(container_ended_at / 1000, tz="UTC").to_date_string()
47
+ row = {"container": container, "result": result, "partition_dt": partition_dt}
48
+ yield row
49
+
50
+ except requests.RequestException as e:
51
+ print(f"Error fetching result for container {container['id']}: {e}")
52
+
53
+ if data["maxLimitReached"] is False:
54
+ break
55
+
56
+
57
+ def fetch_result_object(self, session: requests.Session, container_id: str):
58
+ result_url = (
59
+ "https://api.phantombuster.com/api/v2/containers/fetch-result-object"
60
+ )
61
+ params = {"id": container_id}
62
+ response = session.get(result_url, headers=self._get_headers(), params=params)
63
+ response.raise_for_status()
64
+
65
+ return response.json()
ingestr/src/sources.py CHANGED
@@ -2231,4 +2231,44 @@ class FreshdeskSource:
2231
2231
 
2232
2232
  from ingestr.src.freshdesk import freshdesk_source
2233
2233
  return freshdesk_source(api_secret_key=api_key[0], domain=domain).with_resources(table)
2234
-
2234
+
2235
+ class PhantombusterSource:
2236
+ def handles_incrementality(self) -> bool:
2237
+ return True
2238
+
2239
+ def dlt_source(self, uri: str, table: str, **kwargs):
2240
+ #phantombuster://?api_key=<api_key>
2241
+ #source table = phantom_results:agent_id
2242
+ parsed_uri = urlparse(uri)
2243
+ params = parse_qs(parsed_uri.query)
2244
+ api_key = params.get("api_key")
2245
+ if api_key is None:
2246
+ raise MissingValueError("api_key", "Phantombuster")
2247
+
2248
+ table_fields = table.replace(" ", "").split(":")
2249
+ table_name = table_fields[0]
2250
+
2251
+ agent_id = table_fields[1] if len(table_fields) > 1 else None
2252
+
2253
+ if table_name not in ["completed_phantoms"]:
2254
+ raise UnsupportedResourceError(table_name, "Phantombuster")
2255
+
2256
+ if not agent_id:
2257
+ raise MissingValueError("agent_id", "Phantombuster")
2258
+
2259
+ start_date = kwargs.get("interval_start")
2260
+ if start_date is not None:
2261
+ start_date = ensure_pendulum_datetime(start_date)
2262
+ else:
2263
+ start_date = pendulum.parse("2018-01-01")
2264
+
2265
+ end_date = kwargs.get("interval_end")
2266
+
2267
+ #doesnot support incremental loading
2268
+ if end_date is not None:
2269
+ end_date = ensure_pendulum_datetime(end_date)
2270
+ else:
2271
+ end_date = pendulum.now()
2272
+
2273
+ from ingestr.src.phantombuster import phantombuster_source
2274
+ return phantombuster_source(api_key=api_key[0], agent_id=agent_id, start_date=start_date, end_date=end_date).with_resources(table_name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ingestr
3
- Version: 0.13.36
3
+ Version: 0.13.37
4
4
  Summary: ingestr is a command-line application that ingests data from various sources and stores them in any database.
5
5
  Project-URL: Homepage, https://github.com/bruin-data/ingestr
6
6
  Project-URL: Issues, https://github.com/bruin-data/ingestr/issues
@@ -2,15 +2,15 @@ ingestr/conftest.py,sha256=Q03FIJIZpLBbpj55cfCHIKEjc1FCvWJhMF2cidUJKQU,1748
2
2
  ingestr/main.py,sha256=mRlGSqi2sHcZ2AKlwn5MqoMvFxXlSjcZxmPJr76rmRk,25187
3
3
  ingestr/src/.gitignore,sha256=8cX1AZTSI0TcdZFGTmS_oyBjpfCzhOEt0DdAo2dFIY8,203
4
4
  ingestr/src/blob.py,sha256=onMe5ZHxPXTdcB_s2oGNdMo-XQJ3ajwOsWE9eSTGFmc,1495
5
- ingestr/src/buildinfo.py,sha256=abX1HXd_dkzG2hkJg7JdFGSvgjGi72VrEucHcTxIziA,21
5
+ ingestr/src/buildinfo.py,sha256=zGfudKvUvWbTMFprtyFws2zsqeHGQj08eCKTrwTnVj8,21
6
6
  ingestr/src/destinations.py,sha256=Z79f01BSmEaXnQno2IQVt4Th4dmD-BiOQXlibZJ5sTw,13180
7
7
  ingestr/src/errors.py,sha256=Ufs4_DfE77_E3vnA1fOQdi6cmuLVNm7_SbFLkL1XPGk,686
8
- ingestr/src/factory.py,sha256=M0FAes6KsvqCzuTnUBcxc6DF7UVO51IlrFxy2VDpbkQ,5392
8
+ ingestr/src/factory.py,sha256=Mm_Be60PFO4mUIeJLBMDVU_uyH0IeCiZ1dtNDFiDFSo,5463
9
9
  ingestr/src/filters.py,sha256=C-_TIVkF_cxZBgG-Run2Oyn0TAhJgA8IWXZ-OPY3uek,1136
10
10
  ingestr/src/loader.py,sha256=9NaWAyfkXdqAZSS-N72Iwo36Lbx4PyqIfaaH1dNdkFs,1712
11
11
  ingestr/src/partition.py,sha256=BrIP6wFJvyR7Nus_3ElnfxknUXeCipK_E_bB8kZowfc,969
12
12
  ingestr/src/resource.py,sha256=XG-sbBapFVEM7OhHQFQRTdTLlh-mHB-N4V1t8F8Tsww,543
13
- ingestr/src/sources.py,sha256=YpaUS5Ui-YXeZYLETPAj60WhU5fWI_lP6jVA0w6J6qo,77250
13
+ ingestr/src/sources.py,sha256=9ESEgdlaSQQszpRfp-etKvfFDvvmYfCc9sBlEPJxh3Q,78809
14
14
  ingestr/src/table_definition.py,sha256=REbAbqdlmUMUuRh8nEQRreWjPVOQ5ZcfqGkScKdCrmk,390
15
15
  ingestr/src/time.py,sha256=H_Fk2J4ShXyUM-EMY7MqCLZQhlnZMZvO952bmZPc4yE,254
16
16
  ingestr/src/version.py,sha256=J_2xgZ0mKlvuHcjdKCx2nlioneLH0I47JiU_Slr_Nwc,189
@@ -87,6 +87,8 @@ ingestr/src/notion/helpers/client.py,sha256=QXuudkf5Zzff98HRsCqA1g1EZWIrnfn1falP
87
87
  ingestr/src/notion/helpers/database.py,sha256=gigPibTeVefP3lA-8w4aOwX67pj7RlciPk5koDs1ry8,2737
88
88
  ingestr/src/personio/__init__.py,sha256=sHYpoV-rg-kA1YsflctChis0hKcTrL6mka9O0CHV4zA,11638
89
89
  ingestr/src/personio/helpers.py,sha256=EKmBN0Lf4R0lc3yqqs7D-RjoZ75E8gPcctt59xwHxrY,2901
90
+ ingestr/src/phantombuster/__init__.py,sha256=FJJiVP0ciR48FTmXYLAasZ4JQAB1Ow4M_Hh39J6hWks,1112
91
+ ingestr/src/phantombuster/client.py,sha256=HFJ46f_IU1NMMCA94ttoY1LBc0L7qfqeQEawczlbBvQ,2584
90
92
  ingestr/src/pipedrive/__init__.py,sha256=iRrxeMwo8_83ptgGnTFTNHV1nYvIsFfg0a3XzugPYeI,6982
91
93
  ingestr/src/pipedrive/settings.py,sha256=q119Fy4C5Ip1rMoCILX2BkHV3bwiXC_dW58KIiDUzsY,708
92
94
  ingestr/src/pipedrive/typing.py,sha256=lEMXu4hhAA3XkhVSlBUa-juqyupisd3c-qSQKxFvzoE,69
@@ -125,8 +127,8 @@ ingestr/testdata/delete_insert_part2.csv,sha256=B_KUzpzbNdDY_n7wWop1mT2cz36TmayS
125
127
  ingestr/testdata/merge_expected.csv,sha256=DReHqWGnQMsf2PBv_Q2pfjsgvikYFnf1zYcQZ7ZqYN0,276
126
128
  ingestr/testdata/merge_part1.csv,sha256=Pw8Z9IDKcNU0qQHx1z6BUf4rF_-SxKGFOvymCt4OY9I,185
127
129
  ingestr/testdata/merge_part2.csv,sha256=T_GiWxA81SN63_tMOIuemcvboEFeAmbKc7xRXvL9esw,287
128
- ingestr-0.13.36.dist-info/METADATA,sha256=AFJ4qtGMrtaG5luUcRCXAsp7yP8FKlL4EjP8KorvXKI,13575
129
- ingestr-0.13.36.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
130
- ingestr-0.13.36.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
131
- ingestr-0.13.36.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
132
- ingestr-0.13.36.dist-info/RECORD,,
130
+ ingestr-0.13.37.dist-info/METADATA,sha256=Mmc9hAE_zCJ_b5U9hCLpJXpU0858FirZdoO-FyPuOI4,13575
131
+ ingestr-0.13.37.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
132
+ ingestr-0.13.37.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
133
+ ingestr-0.13.37.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
134
+ ingestr-0.13.37.dist-info/RECORD,,