ingestr 0.13.82__py3-none-any.whl → 0.13.83__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.82"
1
+ version = "v0.13.83"
ingestr/src/factory.py CHANGED
@@ -73,6 +73,7 @@ from ingestr.src.sources import (
73
73
  StripeAnalyticsSource,
74
74
  TikTokSource,
75
75
  TrustpilotSource,
76
+ WiseSource,
76
77
  ZendeskSource,
77
78
  ZoomSource,
78
79
  )
@@ -192,6 +193,7 @@ class SourceDestinationFactory:
192
193
  "zoom": ZoomSource,
193
194
  "clickup": ClickupSource,
194
195
  "influxdb": InfluxDBSource,
196
+ "wise": WiseSource,
195
197
  }
196
198
  destinations: Dict[str, Type[DestinationProtocol]] = {
197
199
  "bigquery": BigQueryDestination,
ingestr/src/sources.py CHANGED
@@ -3386,3 +3386,41 @@ class InfluxDBSource:
3386
3386
  start_date=start_date,
3387
3387
  end_date=end_date,
3388
3388
  ).with_resources(table)
3389
+
3390
+
3391
+ class WiseSource:
3392
+ def handles_incrementality(self) -> bool:
3393
+ return True
3394
+
3395
+ def dlt_source(self, uri: str, table: str, **kwargs):
3396
+ parsed = urlparse(uri)
3397
+ params = parse_qs(parsed.query)
3398
+ api_key = params.get("api_key")
3399
+
3400
+ if not api_key:
3401
+ raise MissingValueError("api_key", "Wise")
3402
+
3403
+ if table not in ["profiles", "transfers", "balances"]:
3404
+ raise ValueError(
3405
+ f"Resource '{table}' is not supported for Wise source yet, if you are interested in it please create a GitHub issue at https://github.com/bruin-data/ingestr"
3406
+ )
3407
+
3408
+ start_date = kwargs.get("interval_start")
3409
+ if start_date:
3410
+ start_date = ensure_pendulum_datetime(start_date).in_timezone("UTC")
3411
+ else:
3412
+ start_date = pendulum.datetime(2020, 1, 1).in_timezone("UTC")
3413
+
3414
+ end_date = kwargs.get("interval_end")
3415
+ if end_date:
3416
+ end_date = ensure_pendulum_datetime(end_date).in_timezone("UTC")
3417
+ else:
3418
+ end_date = None
3419
+
3420
+ from ingestr.src.wise import wise_source
3421
+
3422
+ return wise_source(
3423
+ api_key=api_key[0],
3424
+ start_date=start_date,
3425
+ end_date=end_date,
3426
+ ).with_resources(table)
@@ -0,0 +1,68 @@
1
+ from typing import Iterable
2
+
3
+ import dlt
4
+ import pendulum
5
+ from dlt.common.typing import TDataItem
6
+ from dlt.sources import DltResource
7
+
8
+ from .client import WiseClient
9
+
10
+
11
+ @dlt.source(max_table_nesting=0)
12
+ def wise_source(
13
+ api_key: str,
14
+ start_date: pendulum.DateTime,
15
+ end_date: pendulum.DateTime | None = None,
16
+ ) -> Iterable[DltResource]:
17
+ client = WiseClient(api_key)
18
+
19
+ # List of all profiles belonging to user.
20
+ @dlt.resource(write_disposition="merge", name="profiles", primary_key="id")
21
+ def profiles() -> Iterable[TDataItem]:
22
+ yield from client.fetch_profiles()
23
+
24
+ # List transfers for a profile.
25
+ @dlt.resource(write_disposition="merge", name="transfers", primary_key="id")
26
+ def transfers(
27
+ profiles=profiles,
28
+ datetime=dlt.sources.incremental(
29
+ "created",
30
+ initial_value=start_date,
31
+ end_value=end_date,
32
+ range_end="closed",
33
+ range_start="closed",
34
+ ),
35
+ ):
36
+ if datetime.end_value is None:
37
+ end_dt = pendulum.now(tz="UTC")
38
+ else:
39
+ end_dt = datetime.end_value
40
+
41
+ start_dt = datetime.last_value
42
+
43
+ for profile in profiles:
44
+ yield from client.fetch_transfers(profile["id"], start_dt, end_dt)
45
+
46
+ # Retrieve the user's multi-currency account balance accounts. It returns all balance accounts the profile has.
47
+ @dlt.resource(write_disposition="merge", name="balances", primary_key="id")
48
+ def balances(
49
+ profiles=profiles,
50
+ datetime=dlt.sources.incremental(
51
+ "modificationTime",
52
+ initial_value=start_date,
53
+ end_value=end_date,
54
+ range_end="closed",
55
+ range_start="closed",
56
+ ),
57
+ ) -> Iterable[TDataItem]:
58
+ if datetime.end_value is None:
59
+ end_dt = pendulum.now(tz="UTC")
60
+ else:
61
+ end_dt = datetime.end_value
62
+
63
+ start_dt = datetime.last_value
64
+
65
+ for profile in profiles:
66
+ yield from client.fetch_balances(profile["id"], start_dt, end_dt)
67
+
68
+ return profiles, transfers, balances
@@ -0,0 +1,63 @@
1
+ from typing import Iterable
2
+
3
+ import pendulum
4
+ from dlt.sources.helpers.requests import Client
5
+
6
+
7
+ class WiseClient:
8
+ BASE_URL = "https://api.transferwise.com"
9
+
10
+ def __init__(self, api_key: str) -> None:
11
+ self.session = Client(raise_for_status=False).session
12
+ self.session.headers.update({"Authorization": f"Bearer {api_key}"})
13
+
14
+ # https://docs.wise.com/api-docs/api-reference/profile#list-profiles
15
+ def fetch_profiles(self) -> Iterable[dict]:
16
+ url = f"{self.BASE_URL}/v2/profiles"
17
+ resp = self.session.get(url)
18
+ resp.raise_for_status()
19
+ for profile in resp.json():
20
+ yield profile
21
+
22
+ # https://docs.wise.com/api-docs/api-reference/transfer#list-transfers
23
+ def fetch_transfers(
24
+ self, profile_id: str, start_time=pendulum.DateTime, end_time=pendulum.DateTime
25
+ ):
26
+ offset = 0
27
+
28
+ while True:
29
+ data = self.session.get(
30
+ f"{self.BASE_URL}/v1/transfers",
31
+ params={
32
+ "profile": profile_id,
33
+ "createdDateStart": start_time.to_date_string(),
34
+ "createdDateEnd": end_time.to_date_string(),
35
+ "limit": 100,
36
+ "offset": offset,
37
+ },
38
+ )
39
+ response_data = data.json()
40
+
41
+ if not response_data or len(response_data) == 0:
42
+ break
43
+
44
+ for transfer in response_data:
45
+ transfer["created"] = pendulum.parse(transfer["created"])
46
+
47
+ yield transfer
48
+ offset += 100
49
+
50
+ # https://docs.wise.com/api-docs/api-reference/balance#list
51
+ def fetch_balances(
52
+ self, profile_id: str, start_time=pendulum.DateTime, end_time=pendulum.DateTime
53
+ ) -> Iterable[dict]:
54
+ url = f"{self.BASE_URL}/v4/profiles/{profile_id}/balances"
55
+ resp = self.session.get(url, params={"types": "STANDARD,SAVINGS"})
56
+ resp.raise_for_status()
57
+ for balance in resp.json():
58
+ balance["modificationTime"] = pendulum.parse(balance["modificationTime"])
59
+ if (
60
+ balance["modificationTime"] > start_time
61
+ and balance["modificationTime"] < end_time
62
+ ):
63
+ yield balance
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ingestr
3
- Version: 0.13.82
3
+ Version: 0.13.83
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,16 +2,16 @@ ingestr/conftest.py,sha256=OE2yxeTCosS9CUFVuqNypm-2ftYvVBeeq7egm3878cI,1981
2
2
  ingestr/main.py,sha256=qoWHNcHh0-xVnyQxbQ-SKuTxPb1RNV3ENkCpqO7CLrk,26694
3
3
  ingestr/src/.gitignore,sha256=8cX1AZTSI0TcdZFGTmS_oyBjpfCzhOEt0DdAo2dFIY8,203
4
4
  ingestr/src/blob.py,sha256=UUWMjHUuoR9xP1XZQ6UANQmnMVyDx3d0X4-2FQC271I,2138
5
- ingestr/src/buildinfo.py,sha256=S1Cgqn9Xc0avTk0r_rJ8fMXucqD7c5O68FsKjSWj2Pc,21
5
+ ingestr/src/buildinfo.py,sha256=MHjeovk0HwiWzmF0hXLo3Hw11MPw21fjYmBbB8wW8Fo,21
6
6
  ingestr/src/destinations.py,sha256=M2Yni6wiWcrvZ8EPJemidqxN156l0rehgCc7xuil7mo,22840
7
7
  ingestr/src/errors.py,sha256=Ufs4_DfE77_E3vnA1fOQdi6cmuLVNm7_SbFLkL1XPGk,686
8
- ingestr/src/factory.py,sha256=rF5Ry4o4t8KulSPBtrd7ZKCI_0TH1DAetG0zs9H7oik,6792
8
+ ingestr/src/factory.py,sha256=SQr7s-WCx_jL8DmPbeCc4VuE35z1J7gCAwKGAkOpv2M,6836
9
9
  ingestr/src/filters.py,sha256=LLecXe9QkLFkFLUZ92OXNdcANr1a8edDxrflc2ko_KA,1452
10
10
  ingestr/src/http_client.py,sha256=bxqsk6nJNXCo-79gW04B53DQO-yr25vaSsqP0AKtjx4,732
11
11
  ingestr/src/loader.py,sha256=9NaWAyfkXdqAZSS-N72Iwo36Lbx4PyqIfaaH1dNdkFs,1712
12
12
  ingestr/src/partition.py,sha256=BrIP6wFJvyR7Nus_3ElnfxknUXeCipK_E_bB8kZowfc,969
13
13
  ingestr/src/resource.py,sha256=ZqmZxFQVGlF8rFPhBiUB08HES0yoTj8sZ--jKfaaVps,1164
14
- ingestr/src/sources.py,sha256=Yaej1Iy75JPOccw6jYL6kbhzYFq33t4YelGT-NiAA5I,119661
14
+ ingestr/src/sources.py,sha256=NfD7pj0WOD1wQOV3oZkHmGOAwHMuCCO_Mzf8F8EpobU,120918
15
15
  ingestr/src/table_definition.py,sha256=REbAbqdlmUMUuRh8nEQRreWjPVOQ5ZcfqGkScKdCrmk,390
16
16
  ingestr/src/time.py,sha256=H_Fk2J4ShXyUM-EMY7MqCLZQhlnZMZvO952bmZPc4yE,254
17
17
  ingestr/src/version.py,sha256=J_2xgZ0mKlvuHcjdKCx2nlioneLH0I47JiU_Slr_Nwc,189
@@ -134,6 +134,8 @@ ingestr/src/tiktok_ads/__init__.py,sha256=_upvSv79uktELTzg78VavYgQftfhp4YM6OXfpk
134
134
  ingestr/src/tiktok_ads/tiktok_helpers.py,sha256=jmWHvZzN1Vt_PWrJkgq5a2wIwon-OBEzXoZx0jEy-74,3905
135
135
  ingestr/src/trustpilot/__init__.py,sha256=ofhjep4qRPIi8q41qc97QVex8UbWF-Fd7gUsqeQlQX8,1279
136
136
  ingestr/src/trustpilot/client.py,sha256=zKYt5C7nrR83Id0KN49EPmtml8MEtlSPlAosEFU3VXY,1616
137
+ ingestr/src/wise/__init__.py,sha256=EiXWE89Jb2Do1t8HK1-GZRS2KwgaiThxsHVEpaBcoh4,2087
138
+ ingestr/src/wise/client.py,sha256=2hTCuOB24-HiwTp02o5i99KF5rjI0CXqgIb9V6gAjjE,2247
137
139
  ingestr/src/zendesk/__init__.py,sha256=tmJ_jdb6kpwmEKpcv6Im71-bOZI6h-Tcofe18OH4I24,17762
138
140
  ingestr/src/zendesk/settings.py,sha256=Vdj706nTJFQ-3KH4nO97iYCQuba3dV3E9gfnmLK6xwU,2294
139
141
  ingestr/src/zendesk/helpers/__init__.py,sha256=YTJejCiUjfIcsj9FrkY0l-JGYDI7RRte1Ydq5FDH_0c,888
@@ -151,8 +153,8 @@ ingestr/testdata/merge_expected.csv,sha256=DReHqWGnQMsf2PBv_Q2pfjsgvikYFnf1zYcQZ
151
153
  ingestr/testdata/merge_part1.csv,sha256=Pw8Z9IDKcNU0qQHx1z6BUf4rF_-SxKGFOvymCt4OY9I,185
152
154
  ingestr/testdata/merge_part2.csv,sha256=T_GiWxA81SN63_tMOIuemcvboEFeAmbKc7xRXvL9esw,287
153
155
  ingestr/tests/unit/test_smartsheets.py,sha256=eiC2CCO4iNJcuN36ONvqmEDryCA1bA1REpayHpu42lk,5058
154
- ingestr-0.13.82.dist-info/METADATA,sha256=4edZaTP7apukcZRUY4J00G99O3d7Qxz37csXXCNhaww,15182
155
- ingestr-0.13.82.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
156
- ingestr-0.13.82.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
157
- ingestr-0.13.82.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
158
- ingestr-0.13.82.dist-info/RECORD,,
156
+ ingestr-0.13.83.dist-info/METADATA,sha256=SP8vz_9PH-RBNLkM7QzR3YEMIR8nxXhsGyJN9s09Cig,15182
157
+ ingestr-0.13.83.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
158
+ ingestr-0.13.83.dist-info/entry_points.txt,sha256=oPJy0KBnPWYjDtP1k8qwAihcTLHSZokSQvRAw_wtfJM,46
159
+ ingestr-0.13.83.dist-info/licenses/LICENSE.md,sha256=cW8wIhn8HFE-KLStDF9jHQ1O_ARWP3kTpk_-eOccL24,1075
160
+ ingestr-0.13.83.dist-info/RECORD,,