omniload 0.0.0.dev0__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.
- omniload/conftest.py +72 -0
- omniload/main.py +810 -0
- omniload/src/.gitignore +10 -0
- omniload/src/adjust/__init__.py +108 -0
- omniload/src/adjust/adjust_helpers.py +122 -0
- omniload/src/airtable/__init__.py +84 -0
- omniload/src/allium/__init__.py +128 -0
- omniload/src/anthropic/__init__.py +277 -0
- omniload/src/anthropic/helpers.py +525 -0
- omniload/src/applovin/__init__.py +316 -0
- omniload/src/applovin_max/__init__.py +117 -0
- omniload/src/appsflyer/__init__.py +325 -0
- omniload/src/appsflyer/client.py +110 -0
- omniload/src/appstore/__init__.py +142 -0
- omniload/src/appstore/client.py +126 -0
- omniload/src/appstore/errors.py +15 -0
- omniload/src/appstore/models.py +117 -0
- omniload/src/appstore/resources.py +179 -0
- omniload/src/arrow/__init__.py +81 -0
- omniload/src/asana_source/__init__.py +281 -0
- omniload/src/asana_source/helpers.py +30 -0
- omniload/src/asana_source/settings.py +158 -0
- omniload/src/attio/__init__.py +102 -0
- omniload/src/attio/helpers.py +65 -0
- omniload/src/blob.py +95 -0
- omniload/src/bruin/__init__.py +76 -0
- omniload/src/chess/__init__.py +180 -0
- omniload/src/chess/helpers.py +35 -0
- omniload/src/chess/settings.py +18 -0
- omniload/src/clickup/__init__.py +85 -0
- omniload/src/clickup/helpers.py +47 -0
- omniload/src/collector/spinner.py +43 -0
- omniload/src/couchbase_source/__init__.py +118 -0
- omniload/src/couchbase_source/helpers.py +135 -0
- omniload/src/cursor/__init__.py +83 -0
- omniload/src/cursor/helpers.py +188 -0
- omniload/src/customer_io/__init__.py +486 -0
- omniload/src/customer_io/helpers.py +530 -0
- omniload/src/destinations.py +982 -0
- omniload/src/docebo/__init__.py +589 -0
- omniload/src/docebo/client.py +435 -0
- omniload/src/docebo/helpers.py +97 -0
- omniload/src/dune/__init__.py +104 -0
- omniload/src/dune/helpers.py +108 -0
- omniload/src/dynamodb/__init__.py +86 -0
- omniload/src/elasticsearch/__init__.py +80 -0
- omniload/src/elasticsearch/helpers.py +141 -0
- omniload/src/errors.py +26 -0
- omniload/src/facebook_ads/__init__.py +403 -0
- omniload/src/facebook_ads/exceptions.py +19 -0
- omniload/src/facebook_ads/helpers.py +296 -0
- omniload/src/facebook_ads/settings.py +224 -0
- omniload/src/facebook_ads/utils.py +53 -0
- omniload/src/factory.py +305 -0
- omniload/src/filesystem/__init__.py +133 -0
- omniload/src/filesystem/helpers.py +114 -0
- omniload/src/filesystem/readers.py +187 -0
- omniload/src/filters.py +62 -0
- omniload/src/fireflies/__init__.py +151 -0
- omniload/src/fireflies/helpers.py +753 -0
- omniload/src/fluxx/__init__.py +10013 -0
- omniload/src/fluxx/helpers.py +233 -0
- omniload/src/frankfurter/__init__.py +157 -0
- omniload/src/frankfurter/helpers.py +48 -0
- omniload/src/freshdesk/__init__.py +103 -0
- omniload/src/freshdesk/freshdesk_client.py +151 -0
- omniload/src/freshdesk/settings.py +23 -0
- omniload/src/fundraiseup/__init__.py +95 -0
- omniload/src/fundraiseup/client.py +81 -0
- omniload/src/github/__init__.py +202 -0
- omniload/src/github/helpers.py +207 -0
- omniload/src/github/queries.py +129 -0
- omniload/src/github/settings.py +24 -0
- omniload/src/google_ads/__init__.py +198 -0
- omniload/src/google_ads/field.py +17 -0
- omniload/src/google_ads/metrics.py +254 -0
- omniload/src/google_ads/predicates.py +37 -0
- omniload/src/google_ads/reports.py +411 -0
- omniload/src/google_ads/test_google_ads.py +184 -0
- omniload/src/google_analytics/__init__.py +144 -0
- omniload/src/google_analytics/helpers.py +312 -0
- omniload/src/google_sheets/README.md +95 -0
- omniload/src/google_sheets/__init__.py +166 -0
- omniload/src/google_sheets/helpers/__init__.py +15 -0
- omniload/src/google_sheets/helpers/api_calls.py +160 -0
- omniload/src/google_sheets/helpers/data_processing.py +316 -0
- omniload/src/gorgias/__init__.py +595 -0
- omniload/src/gorgias/helpers.py +166 -0
- omniload/src/hostaway/__init__.py +302 -0
- omniload/src/hostaway/client.py +288 -0
- omniload/src/http/__init__.py +38 -0
- omniload/src/http/readers.py +146 -0
- omniload/src/http_client.py +24 -0
- omniload/src/hubspot/__init__.py +800 -0
- omniload/src/hubspot/helpers.py +417 -0
- omniload/src/hubspot/settings.py +329 -0
- omniload/src/indeed/__init__.py +153 -0
- omniload/src/indeed/helpers.py +228 -0
- omniload/src/influxdb/__init__.py +46 -0
- omniload/src/influxdb/client.py +34 -0
- omniload/src/intercom/__init__.py +142 -0
- omniload/src/intercom/helpers.py +674 -0
- omniload/src/intercom/settings.py +279 -0
- omniload/src/isoc_pulse/__init__.py +159 -0
- omniload/src/jira_source/__init__.py +377 -0
- omniload/src/jira_source/helpers.py +510 -0
- omniload/src/jira_source/settings.py +184 -0
- omniload/src/kafka/__init__.py +120 -0
- omniload/src/kafka/helpers.py +241 -0
- omniload/src/kinesis/__init__.py +153 -0
- omniload/src/kinesis/helpers.py +96 -0
- omniload/src/klaviyo/__init__.py +237 -0
- omniload/src/klaviyo/client.py +212 -0
- omniload/src/klaviyo/helpers.py +19 -0
- omniload/src/linear/__init__.py +634 -0
- omniload/src/linear/helpers.py +111 -0
- omniload/src/linkedin_ads/__init__.py +266 -0
- omniload/src/linkedin_ads/dimension_time_enum.py +17 -0
- omniload/src/linkedin_ads/helpers.py +246 -0
- omniload/src/loader.py +69 -0
- omniload/src/mailchimp/__init__.py +126 -0
- omniload/src/mailchimp/helpers.py +226 -0
- omniload/src/mailchimp/settings.py +164 -0
- omniload/src/masking.py +344 -0
- omniload/src/mixpanel/__init__.py +62 -0
- omniload/src/mixpanel/client.py +104 -0
- omniload/src/monday/__init__.py +246 -0
- omniload/src/monday/helpers.py +392 -0
- omniload/src/monday/settings.py +325 -0
- omniload/src/mongodb/__init__.py +281 -0
- omniload/src/mongodb/helpers.py +975 -0
- omniload/src/notion/__init__.py +69 -0
- omniload/src/notion/helpers/__init__.py +14 -0
- omniload/src/notion/helpers/client.py +178 -0
- omniload/src/notion/helpers/database.py +92 -0
- omniload/src/notion/settings.py +17 -0
- omniload/src/partition.py +32 -0
- omniload/src/personio/__init__.py +345 -0
- omniload/src/personio/helpers.py +100 -0
- omniload/src/phantombuster/__init__.py +65 -0
- omniload/src/phantombuster/client.py +87 -0
- omniload/src/pinterest/__init__.py +82 -0
- omniload/src/pipedrive/__init__.py +212 -0
- omniload/src/pipedrive/helpers/__init__.py +37 -0
- omniload/src/pipedrive/helpers/custom_fields_munger.py +116 -0
- omniload/src/pipedrive/helpers/pages.py +129 -0
- omniload/src/pipedrive/settings.py +41 -0
- omniload/src/pipedrive/typing.py +17 -0
- omniload/src/plusvibeai/__init__.py +335 -0
- omniload/src/plusvibeai/helpers.py +544 -0
- omniload/src/plusvibeai/settings.py +252 -0
- omniload/src/primer/__init__.py +45 -0
- omniload/src/primer/helpers.py +79 -0
- omniload/src/quickbooks/__init__.py +117 -0
- omniload/src/reddit_ads/__init__.py +183 -0
- omniload/src/reddit_ads/helpers.py +232 -0
- omniload/src/resource.py +40 -0
- omniload/src/revenuecat/__init__.py +83 -0
- omniload/src/revenuecat/helpers.py +237 -0
- omniload/src/salesforce/__init__.py +170 -0
- omniload/src/salesforce/helpers.py +78 -0
- omniload/src/shopify/__init__.py +1953 -0
- omniload/src/shopify/exceptions.py +17 -0
- omniload/src/shopify/helpers.py +202 -0
- omniload/src/shopify/settings.py +19 -0
- omniload/src/slack/__init__.py +290 -0
- omniload/src/slack/helpers.py +218 -0
- omniload/src/slack/settings.py +36 -0
- omniload/src/smartsheets/__init__.py +82 -0
- omniload/src/snapchat_ads/__init__.py +455 -0
- omniload/src/snapchat_ads/client.py +72 -0
- omniload/src/snapchat_ads/helpers.py +630 -0
- omniload/src/snapchat_ads/settings.py +130 -0
- omniload/src/socrata_source/__init__.py +83 -0
- omniload/src/socrata_source/helpers.py +85 -0
- omniload/src/socrata_source/settings.py +8 -0
- omniload/src/solidgate/__init__.py +219 -0
- omniload/src/solidgate/helpers.py +154 -0
- omniload/src/sources.py +5408 -0
- omniload/src/sql_database/__init__.py +0 -0
- omniload/src/sql_database/callbacks.py +66 -0
- omniload/src/stripe_analytics/__init__.py +183 -0
- omniload/src/stripe_analytics/helpers.py +386 -0
- omniload/src/stripe_analytics/settings.py +80 -0
- omniload/src/table_definition.py +15 -0
- omniload/src/testdata/fakebqcredentials.json +14 -0
- omniload/src/tiktok_ads/__init__.py +150 -0
- omniload/src/tiktok_ads/tiktok_helpers.py +130 -0
- omniload/src/time.py +11 -0
- omniload/src/trustpilot/__init__.py +48 -0
- omniload/src/trustpilot/client.py +48 -0
- omniload/src/version.py +6 -0
- omniload/src/wise/__init__.py +68 -0
- omniload/src/wise/client.py +63 -0
- omniload/src/zendesk/__init__.py +480 -0
- omniload/src/zendesk/helpers/__init__.py +39 -0
- omniload/src/zendesk/helpers/api_helpers.py +119 -0
- omniload/src/zendesk/helpers/credentials.py +68 -0
- omniload/src/zendesk/helpers/talk_api.py +132 -0
- omniload/src/zendesk/settings.py +71 -0
- omniload/src/zoom/__init__.py +99 -0
- omniload/src/zoom/helpers.py +102 -0
- omniload/testdata/.gitignore +2 -0
- omniload/testdata/create_replace.csv +21 -0
- omniload/testdata/delete_insert_expected.csv +6 -0
- omniload/testdata/delete_insert_part1.csv +5 -0
- omniload/testdata/delete_insert_part2.csv +6 -0
- omniload/testdata/merge_expected.csv +5 -0
- omniload/testdata/merge_part1.csv +4 -0
- omniload/testdata/merge_part2.csv +5 -0
- omniload/tests/unit/test_smartsheets.py +133 -0
- omniload-0.0.0.dev0.dist-info/METADATA +439 -0
- omniload-0.0.0.dev0.dist-info/RECORD +218 -0
- omniload-0.0.0.dev0.dist-info/WHEEL +4 -0
- omniload-0.0.0.dev0.dist-info/entry_points.txt +2 -0
- omniload-0.0.0.dev0.dist-info/licenses/LICENSE.Apache-2.0 +201 -0
- omniload-0.0.0.dev0.dist-info/licenses/LICENSE.md +21 -0
- omniload-0.0.0.dev0.dist-info/licenses/NOTICE +35 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Cursor source helpers"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Iterator, List, Optional
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
REQUEST_TIMEOUT = 30
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CursorClient:
|
|
11
|
+
"""Cursor REST API client with API key authentication."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
api_key: str,
|
|
16
|
+
base_url: str = "https://api.cursor.com",
|
|
17
|
+
timeout: int = REQUEST_TIMEOUT,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Initialize Cursor client with API key authentication.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
api_key: API key for authentication
|
|
24
|
+
base_url: Cursor API base URL
|
|
25
|
+
timeout: Request timeout in seconds
|
|
26
|
+
"""
|
|
27
|
+
self.base_url = base_url.rstrip("/")
|
|
28
|
+
self.timeout = timeout
|
|
29
|
+
self.api_key = api_key
|
|
30
|
+
|
|
31
|
+
def _make_request(
|
|
32
|
+
self,
|
|
33
|
+
endpoint: str,
|
|
34
|
+
method: str = "POST",
|
|
35
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
36
|
+
) -> Dict[str, Any]:
|
|
37
|
+
"""
|
|
38
|
+
Make HTTP request to Cursor API.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
endpoint: API endpoint path
|
|
42
|
+
method: HTTP method (default: POST)
|
|
43
|
+
json_data: JSON data for request body
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
JSON response data
|
|
47
|
+
"""
|
|
48
|
+
url = f"{self.base_url}/{endpoint.lstrip('/')}"
|
|
49
|
+
|
|
50
|
+
if json_data is not None:
|
|
51
|
+
response = requests.request(
|
|
52
|
+
method=method,
|
|
53
|
+
url=url,
|
|
54
|
+
auth=(self.api_key, ""),
|
|
55
|
+
timeout=self.timeout,
|
|
56
|
+
headers={"Content-Type": "application/json"},
|
|
57
|
+
json=json_data,
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
response = requests.request(
|
|
61
|
+
method=method,
|
|
62
|
+
url=url,
|
|
63
|
+
auth=(self.api_key, ""),
|
|
64
|
+
timeout=self.timeout,
|
|
65
|
+
headers={"Content-Type": "application/json"},
|
|
66
|
+
json={},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
return response.json()
|
|
71
|
+
|
|
72
|
+
def _paginate(
|
|
73
|
+
self,
|
|
74
|
+
endpoint: str,
|
|
75
|
+
data_key: str,
|
|
76
|
+
base_payload: Optional[Dict[str, Any]] = None,
|
|
77
|
+
page_size: Optional[int] = 100,
|
|
78
|
+
has_next_page_check: Optional[Callable[[Dict[str, Any]], bool]] = None,
|
|
79
|
+
) -> Iterator[Dict[str, Any]]:
|
|
80
|
+
"""
|
|
81
|
+
Generic pagination helper for API endpoints.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
endpoint: API endpoint to call
|
|
85
|
+
data_key: Key in response containing the data array
|
|
86
|
+
base_payload: Base payload to include in each request
|
|
87
|
+
page_size: Number of results per page (default: 100)
|
|
88
|
+
has_next_page_check: Optional function to check if there's a next page from response
|
|
89
|
+
|
|
90
|
+
Yields:
|
|
91
|
+
Individual records from the paginated response
|
|
92
|
+
"""
|
|
93
|
+
page = 1
|
|
94
|
+
base_payload = base_payload or {}
|
|
95
|
+
|
|
96
|
+
while True:
|
|
97
|
+
payload = base_payload.copy()
|
|
98
|
+
|
|
99
|
+
if page_size:
|
|
100
|
+
payload["pageSize"] = page_size
|
|
101
|
+
payload["page"] = page
|
|
102
|
+
|
|
103
|
+
result = self._make_request(endpoint, json_data=payload)
|
|
104
|
+
data = result.get(data_key, [])
|
|
105
|
+
|
|
106
|
+
if not data:
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
for record in data:
|
|
110
|
+
yield record
|
|
111
|
+
|
|
112
|
+
# If page_size is not set, we get all data in one request
|
|
113
|
+
if not page_size:
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
# Custom check for next page if provided
|
|
117
|
+
if has_next_page_check:
|
|
118
|
+
if not has_next_page_check(result):
|
|
119
|
+
break
|
|
120
|
+
# Default: if we got less data than page_size, we've reached the end
|
|
121
|
+
elif len(data) < page_size:
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
page += 1
|
|
125
|
+
|
|
126
|
+
def get_team_members(self) -> List[Dict[str, Any]]:
|
|
127
|
+
response = self._make_request("teams/members", method="GET")
|
|
128
|
+
return response.get("teamMembers", [])
|
|
129
|
+
|
|
130
|
+
def get_daily_usage_data(
|
|
131
|
+
self,
|
|
132
|
+
start_date: Optional[int] = None,
|
|
133
|
+
end_date: Optional[int] = None,
|
|
134
|
+
page_size: Optional[int] = 100,
|
|
135
|
+
) -> Iterator[Dict[str, Any]]:
|
|
136
|
+
base_payload = {}
|
|
137
|
+
if start_date is not None:
|
|
138
|
+
base_payload["startDate"] = start_date
|
|
139
|
+
if end_date is not None:
|
|
140
|
+
base_payload["endDate"] = end_date
|
|
141
|
+
|
|
142
|
+
yield from self._paginate(
|
|
143
|
+
endpoint="teams/daily-usage-data",
|
|
144
|
+
data_key="data",
|
|
145
|
+
base_payload=base_payload,
|
|
146
|
+
page_size=page_size,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def get_team_spend(
|
|
150
|
+
self,
|
|
151
|
+
page_size: Optional[int] = 100,
|
|
152
|
+
) -> Iterator[Dict[str, Any]]:
|
|
153
|
+
def check_has_next_page(response: Dict[str, Any]) -> bool:
|
|
154
|
+
current_page = response.get("currentPage", 1)
|
|
155
|
+
total_pages = response.get("totalPages", 1)
|
|
156
|
+
return current_page < total_pages
|
|
157
|
+
|
|
158
|
+
yield from self._paginate(
|
|
159
|
+
endpoint="teams/spend",
|
|
160
|
+
data_key="teamMemberSpend",
|
|
161
|
+
page_size=page_size,
|
|
162
|
+
has_next_page_check=check_has_next_page,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def get_filtered_usage_events(
|
|
166
|
+
self,
|
|
167
|
+
start_date: Optional[int] = None,
|
|
168
|
+
end_date: Optional[int] = None,
|
|
169
|
+
page_size: Optional[int] = 100,
|
|
170
|
+
) -> Iterator[Dict[str, Any]]:
|
|
171
|
+
base_payload = {}
|
|
172
|
+
if start_date is not None:
|
|
173
|
+
base_payload["startDate"] = start_date
|
|
174
|
+
if end_date is not None:
|
|
175
|
+
base_payload["endDate"] = end_date
|
|
176
|
+
|
|
177
|
+
# Custom check for hasNextPage
|
|
178
|
+
def check_has_next_page(response: Dict[str, Any]) -> bool:
|
|
179
|
+
pagination = response.get("pagination", {})
|
|
180
|
+
return pagination.get("hasNextPage", False)
|
|
181
|
+
|
|
182
|
+
yield from self._paginate(
|
|
183
|
+
endpoint="teams/filtered-usage-events",
|
|
184
|
+
data_key="usageEvents",
|
|
185
|
+
base_payload=base_payload,
|
|
186
|
+
page_size=page_size,
|
|
187
|
+
has_next_page_check=check_has_next_page,
|
|
188
|
+
)
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
from typing import Iterable
|
|
2
|
+
|
|
3
|
+
import dlt
|
|
4
|
+
import pendulum
|
|
5
|
+
import requests
|
|
6
|
+
from dlt.common.typing import TAnyDateTime, TDataItem
|
|
7
|
+
from dlt.sources import DltResource
|
|
8
|
+
from dlt.sources.helpers.requests import Client
|
|
9
|
+
|
|
10
|
+
from omniload.src.customer_io.helpers import CustomerIoClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def retry_on_limit(
|
|
14
|
+
response: requests.Response | None, exception: BaseException | None
|
|
15
|
+
) -> bool:
|
|
16
|
+
if response is None:
|
|
17
|
+
return False
|
|
18
|
+
return response.status_code == 429
|
|
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
|
+
def _get_incremental_bounds(incremental_state) -> tuple:
|
|
31
|
+
"""Extract last_value and end_value from incremental state as pendulum datetimes."""
|
|
32
|
+
last_value = (
|
|
33
|
+
pendulum.instance(incremental_state.last_value).in_tz("UTC")
|
|
34
|
+
if incremental_state.last_value
|
|
35
|
+
else pendulum.datetime(1970, 1, 1, tz="UTC")
|
|
36
|
+
)
|
|
37
|
+
end_value = (
|
|
38
|
+
pendulum.instance(incremental_state.end_value).in_tz("UTC")
|
|
39
|
+
if incremental_state.end_value
|
|
40
|
+
else None
|
|
41
|
+
)
|
|
42
|
+
return last_value, end_value
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _filter_by_timestamp(
|
|
46
|
+
items: Iterable, timestamp_field: str, last_value, end_value
|
|
47
|
+
) -> Iterable[TDataItem]:
|
|
48
|
+
"""Filter items by timestamp field within the given bounds."""
|
|
49
|
+
for item in items:
|
|
50
|
+
item_updated = pendulum.from_timestamp(item.get(timestamp_field, 0))
|
|
51
|
+
if item_updated >= last_value:
|
|
52
|
+
if end_value is None or item_updated <= end_value:
|
|
53
|
+
item[timestamp_field] = item_updated
|
|
54
|
+
yield item
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _to_timestamp(dt: TAnyDateTime | None) -> int | None:
|
|
58
|
+
"""Convert datetime to unix timestamp, or return None."""
|
|
59
|
+
if dt is None:
|
|
60
|
+
return None
|
|
61
|
+
if hasattr(dt, "timestamp"):
|
|
62
|
+
return int(dt.timestamp()) # type: ignore[union-attr, attr-defined]
|
|
63
|
+
# Handle other types by converting through pendulum
|
|
64
|
+
return int(pendulum.instance(dt).timestamp()) # type: ignore[arg-type, attr-defined]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dlt.source(max_table_nesting=0)
|
|
68
|
+
def customer_io_source(
|
|
69
|
+
api_key: str,
|
|
70
|
+
region: str = "us",
|
|
71
|
+
start_date: TAnyDateTime | None = None,
|
|
72
|
+
end_date: TAnyDateTime | None = None,
|
|
73
|
+
) -> Iterable[DltResource]:
|
|
74
|
+
client = CustomerIoClient(api_key, region)
|
|
75
|
+
|
|
76
|
+
# Pre-compute timestamps for reuse
|
|
77
|
+
start_ts = _to_timestamp(start_date)
|
|
78
|
+
end_ts = _to_timestamp(end_date)
|
|
79
|
+
|
|
80
|
+
@dlt.resource(write_disposition="replace", primary_key="id")
|
|
81
|
+
def activities() -> Iterable[TDataItem]:
|
|
82
|
+
yield from client.fetch_activities(create_client())
|
|
83
|
+
|
|
84
|
+
@dlt.resource(write_disposition="merge", primary_key="id")
|
|
85
|
+
def broadcasts(
|
|
86
|
+
updated=dlt.sources.incremental(
|
|
87
|
+
"updated",
|
|
88
|
+
initial_value=start_date or pendulum.datetime(1970, 1, 1, tz="UTC"),
|
|
89
|
+
end_value=end_date,
|
|
90
|
+
),
|
|
91
|
+
) -> Iterable[TDataItem]:
|
|
92
|
+
last_value, end_value_dt = _get_incremental_bounds(updated)
|
|
93
|
+
yield from _filter_by_timestamp(
|
|
94
|
+
client.fetch_broadcasts(create_client()),
|
|
95
|
+
"updated",
|
|
96
|
+
last_value,
|
|
97
|
+
end_value_dt,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@dlt.transformer(data_from=broadcasts, write_disposition="merge", primary_key="id")
|
|
101
|
+
def broadcast_actions(broadcast: TDataItem) -> Iterable[TDataItem]:
|
|
102
|
+
broadcast_id = broadcast.get("id")
|
|
103
|
+
start_val = (
|
|
104
|
+
pendulum.instance(start_date).in_tz("UTC") # type: ignore[arg-type, union-attr, attr-defined]
|
|
105
|
+
if start_date
|
|
106
|
+
else pendulum.datetime(1970, 1, 1, tz="UTC")
|
|
107
|
+
)
|
|
108
|
+
end_val = pendulum.instance(end_date).in_tz("UTC") if end_date else None # type: ignore[arg-type, union-attr, attr-defined]
|
|
109
|
+
yield from _filter_by_timestamp(
|
|
110
|
+
client.fetch_broadcast_actions(create_client(), broadcast_id),
|
|
111
|
+
"updated",
|
|
112
|
+
start_val,
|
|
113
|
+
end_val,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@dlt.transformer(data_from=broadcasts, write_disposition="merge", primary_key="id")
|
|
117
|
+
def broadcast_messages(broadcast: TDataItem) -> Iterable[TDataItem]:
|
|
118
|
+
broadcast_id = broadcast.get("id")
|
|
119
|
+
yield from client.fetch_broadcast_messages(
|
|
120
|
+
create_client(), broadcast_id, start_ts, end_ts
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@dlt.resource(write_disposition="merge", primary_key="id")
|
|
124
|
+
def campaigns(
|
|
125
|
+
updated=dlt.sources.incremental(
|
|
126
|
+
"updated",
|
|
127
|
+
initial_value=start_date or pendulum.datetime(1970, 1, 1, tz="UTC"),
|
|
128
|
+
end_value=end_date,
|
|
129
|
+
),
|
|
130
|
+
) -> Iterable[TDataItem]:
|
|
131
|
+
last_value, end_value_dt = _get_incremental_bounds(updated)
|
|
132
|
+
yield from _filter_by_timestamp(
|
|
133
|
+
client.fetch_campaigns(create_client()), "updated", last_value, end_value_dt
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
@dlt.transformer(data_from=campaigns, write_disposition="merge", primary_key="id")
|
|
137
|
+
def campaign_actions(campaign: TDataItem) -> Iterable[TDataItem]:
|
|
138
|
+
campaign_id = campaign.get("id")
|
|
139
|
+
for item in client.fetch_campaign_actions(create_client(), campaign_id):
|
|
140
|
+
item["updated"] = pendulum.from_timestamp(item.get("updated", 0))
|
|
141
|
+
yield item
|
|
142
|
+
|
|
143
|
+
@dlt.resource(write_disposition="merge", primary_key="id")
|
|
144
|
+
def collections(
|
|
145
|
+
updated_at=dlt.sources.incremental(
|
|
146
|
+
"updated_at",
|
|
147
|
+
initial_value=start_date or pendulum.datetime(1970, 1, 1, tz="UTC"),
|
|
148
|
+
end_value=end_date,
|
|
149
|
+
),
|
|
150
|
+
) -> Iterable[TDataItem]:
|
|
151
|
+
last_value, end_value_dt = _get_incremental_bounds(updated_at)
|
|
152
|
+
yield from _filter_by_timestamp(
|
|
153
|
+
client.fetch_collections(create_client()),
|
|
154
|
+
"updated_at",
|
|
155
|
+
last_value,
|
|
156
|
+
end_value_dt,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
@dlt.resource(write_disposition="merge", primary_key="id")
|
|
160
|
+
def exports(
|
|
161
|
+
updated_at=dlt.sources.incremental(
|
|
162
|
+
"updated_at",
|
|
163
|
+
initial_value=start_date or pendulum.datetime(1970, 1, 1, tz="UTC"),
|
|
164
|
+
end_value=end_date,
|
|
165
|
+
),
|
|
166
|
+
) -> Iterable[TDataItem]:
|
|
167
|
+
last_value, end_value_dt = _get_incremental_bounds(updated_at)
|
|
168
|
+
yield from _filter_by_timestamp(
|
|
169
|
+
client.fetch_exports(create_client()),
|
|
170
|
+
"updated_at",
|
|
171
|
+
last_value,
|
|
172
|
+
end_value_dt,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
@dlt.resource(write_disposition="replace", primary_key="ip")
|
|
176
|
+
def info_ip_addresses() -> Iterable[TDataItem]:
|
|
177
|
+
yield from client.fetch_info_ip_addresses(create_client())
|
|
178
|
+
|
|
179
|
+
@dlt.resource(write_disposition="merge", primary_key="id")
|
|
180
|
+
def messages() -> Iterable[TDataItem]:
|
|
181
|
+
yield from client.fetch_messages(create_client(), start_ts, end_ts)
|
|
182
|
+
|
|
183
|
+
@dlt.resource(write_disposition="merge", primary_key="id")
|
|
184
|
+
def newsletters(
|
|
185
|
+
updated=dlt.sources.incremental(
|
|
186
|
+
"updated",
|
|
187
|
+
initial_value=start_date or pendulum.datetime(1970, 1, 1, tz="UTC"),
|
|
188
|
+
end_value=end_date,
|
|
189
|
+
),
|
|
190
|
+
) -> Iterable[TDataItem]:
|
|
191
|
+
last_value, end_value_dt = _get_incremental_bounds(updated)
|
|
192
|
+
yield from _filter_by_timestamp(
|
|
193
|
+
client.fetch_newsletters(create_client()),
|
|
194
|
+
"updated",
|
|
195
|
+
last_value,
|
|
196
|
+
end_value_dt,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
@dlt.transformer(
|
|
200
|
+
data_from=newsletters, write_disposition="replace", primary_key="id"
|
|
201
|
+
)
|
|
202
|
+
def newsletter_test_groups(newsletter: TDataItem) -> Iterable[TDataItem]:
|
|
203
|
+
newsletter_id = newsletter.get("id")
|
|
204
|
+
yield from client.fetch_newsletter_test_groups(create_client(), newsletter_id)
|
|
205
|
+
|
|
206
|
+
@dlt.resource(write_disposition="replace", primary_key="id")
|
|
207
|
+
def reporting_webhooks() -> Iterable[TDataItem]:
|
|
208
|
+
yield from client.fetch_reporting_webhooks(create_client())
|
|
209
|
+
|
|
210
|
+
@dlt.resource(write_disposition="merge", primary_key="id")
|
|
211
|
+
def segments(
|
|
212
|
+
updated_at=dlt.sources.incremental(
|
|
213
|
+
"updated_at",
|
|
214
|
+
initial_value=start_date or pendulum.datetime(1970, 1, 1, tz="UTC"),
|
|
215
|
+
end_value=end_date,
|
|
216
|
+
),
|
|
217
|
+
) -> Iterable[TDataItem]:
|
|
218
|
+
last_value, end_value_dt = _get_incremental_bounds(updated_at)
|
|
219
|
+
yield from _filter_by_timestamp(
|
|
220
|
+
client.fetch_segments(create_client()),
|
|
221
|
+
"updated_at",
|
|
222
|
+
last_value,
|
|
223
|
+
end_value_dt,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
@dlt.resource(write_disposition="replace", primary_key="id")
|
|
227
|
+
def transactional_messages() -> Iterable[TDataItem]:
|
|
228
|
+
yield from client.fetch_transactional_messages(create_client())
|
|
229
|
+
|
|
230
|
+
@dlt.resource(write_disposition="replace", primary_key="id")
|
|
231
|
+
def workspaces() -> Iterable[TDataItem]:
|
|
232
|
+
yield from client.fetch_workspaces(create_client())
|
|
233
|
+
|
|
234
|
+
@dlt.resource(write_disposition="replace", primary_key="id")
|
|
235
|
+
def sender_identities() -> Iterable[TDataItem]:
|
|
236
|
+
yield from client.fetch_sender_identities(create_client())
|
|
237
|
+
|
|
238
|
+
@dlt.resource(write_disposition="replace", primary_key="cio_id")
|
|
239
|
+
def customers() -> Iterable[TDataItem]:
|
|
240
|
+
yield from client.fetch_customers(create_client())
|
|
241
|
+
|
|
242
|
+
@dlt.transformer(
|
|
243
|
+
data_from=customers, write_disposition="replace", primary_key="customer_id"
|
|
244
|
+
)
|
|
245
|
+
def customer_attributes(customer: TDataItem) -> Iterable[TDataItem]:
|
|
246
|
+
customer_id = customer.get("cio_id") or customer.get("id")
|
|
247
|
+
if customer_id:
|
|
248
|
+
result = client.fetch_customer_attributes(create_client(), customer_id)
|
|
249
|
+
if result:
|
|
250
|
+
yield result
|
|
251
|
+
|
|
252
|
+
@dlt.transformer(data_from=customers, write_disposition="merge", primary_key="id")
|
|
253
|
+
def customer_messages(customer: TDataItem) -> Iterable[TDataItem]:
|
|
254
|
+
customer_id = customer.get("cio_id") or customer.get("id")
|
|
255
|
+
if customer_id:
|
|
256
|
+
yield from client.fetch_customer_messages(
|
|
257
|
+
create_client(), customer_id, start_ts, end_ts
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
@dlt.transformer(data_from=customers, write_disposition="replace", primary_key="id")
|
|
261
|
+
def customer_activities(customer: TDataItem) -> Iterable[TDataItem]:
|
|
262
|
+
customer_id = customer.get("cio_id") or customer.get("id")
|
|
263
|
+
if customer_id:
|
|
264
|
+
yield from client.fetch_customer_activities(create_client(), customer_id)
|
|
265
|
+
|
|
266
|
+
@dlt.transformer(
|
|
267
|
+
data_from=customers,
|
|
268
|
+
write_disposition="replace",
|
|
269
|
+
primary_key=["customer_id", "object_type_id", "object_id"],
|
|
270
|
+
)
|
|
271
|
+
def customer_relationships(customer: TDataItem) -> Iterable[TDataItem]:
|
|
272
|
+
customer_id = customer.get("cio_id") or customer.get("id")
|
|
273
|
+
if customer_id:
|
|
274
|
+
for rel in client.fetch_customer_relationships(
|
|
275
|
+
create_client(), customer_id
|
|
276
|
+
):
|
|
277
|
+
identifiers = rel.get("identifiers", {})
|
|
278
|
+
rel["object_id"] = identifiers.get("object_id") or identifiers.get(
|
|
279
|
+
"cio_object_id"
|
|
280
|
+
)
|
|
281
|
+
yield rel
|
|
282
|
+
|
|
283
|
+
@dlt.resource(write_disposition="replace", primary_key="id")
|
|
284
|
+
def object_types() -> Iterable[TDataItem]:
|
|
285
|
+
yield from client.fetch_object_types(create_client())
|
|
286
|
+
|
|
287
|
+
@dlt.transformer(
|
|
288
|
+
data_from=object_types,
|
|
289
|
+
write_disposition="replace",
|
|
290
|
+
primary_key=["object_type_id", "object_id"],
|
|
291
|
+
)
|
|
292
|
+
def objects(obj_type: TDataItem) -> Iterable[TDataItem]:
|
|
293
|
+
object_type_id = obj_type.get("id")
|
|
294
|
+
if object_type_id:
|
|
295
|
+
yield from client.fetch_objects(create_client(), str(object_type_id))
|
|
296
|
+
|
|
297
|
+
@dlt.resource(write_disposition="replace", primary_key="id")
|
|
298
|
+
def subscription_topics() -> Iterable[TDataItem]:
|
|
299
|
+
yield from client.fetch_subscription_topics(create_client())
|
|
300
|
+
|
|
301
|
+
@dlt.transformer(data_from=campaigns, write_disposition="merge", primary_key="id")
|
|
302
|
+
def campaign_messages(campaign: TDataItem) -> Iterable[TDataItem]:
|
|
303
|
+
campaign_id = campaign.get("id")
|
|
304
|
+
yield from client.fetch_campaign_messages(
|
|
305
|
+
create_client(), campaign_id, start_ts, end_ts
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
activities,
|
|
310
|
+
broadcasts,
|
|
311
|
+
broadcast_actions,
|
|
312
|
+
broadcast_messages,
|
|
313
|
+
campaigns,
|
|
314
|
+
campaign_actions,
|
|
315
|
+
campaign_messages,
|
|
316
|
+
collections,
|
|
317
|
+
exports,
|
|
318
|
+
info_ip_addresses,
|
|
319
|
+
messages,
|
|
320
|
+
newsletters,
|
|
321
|
+
newsletter_test_groups,
|
|
322
|
+
reporting_webhooks,
|
|
323
|
+
segments,
|
|
324
|
+
sender_identities,
|
|
325
|
+
transactional_messages,
|
|
326
|
+
workspaces,
|
|
327
|
+
customers,
|
|
328
|
+
customer_attributes,
|
|
329
|
+
customer_messages,
|
|
330
|
+
customer_activities,
|
|
331
|
+
customer_relationships,
|
|
332
|
+
object_types,
|
|
333
|
+
objects,
|
|
334
|
+
subscription_topics,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@dlt.source(max_table_nesting=0)
|
|
339
|
+
def customer_io_broadcast_metrics_source(
|
|
340
|
+
api_key: str,
|
|
341
|
+
region: str = "us",
|
|
342
|
+
period: str = "days",
|
|
343
|
+
) -> Iterable[DltResource]:
|
|
344
|
+
client = CustomerIoClient(api_key, region)
|
|
345
|
+
|
|
346
|
+
@dlt.resource(write_disposition="replace")
|
|
347
|
+
def broadcast_metrics() -> Iterable[TDataItem]:
|
|
348
|
+
yield from client.fetch_broadcast_metrics(create_client(), period)
|
|
349
|
+
|
|
350
|
+
return (broadcast_metrics,)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@dlt.source(max_table_nesting=0)
|
|
354
|
+
def customer_io_broadcast_action_metrics_source(
|
|
355
|
+
api_key: str,
|
|
356
|
+
region: str = "us",
|
|
357
|
+
period: str = "days",
|
|
358
|
+
) -> Iterable[DltResource]:
|
|
359
|
+
client = CustomerIoClient(api_key, region)
|
|
360
|
+
|
|
361
|
+
@dlt.resource(write_disposition="replace", primary_key="id", selected=False)
|
|
362
|
+
def broadcasts() -> Iterable[TDataItem]:
|
|
363
|
+
for item in client.fetch_broadcasts(create_client()):
|
|
364
|
+
yield item
|
|
365
|
+
|
|
366
|
+
@dlt.transformer(data_from=broadcasts, write_disposition="replace", selected=False)
|
|
367
|
+
def broadcast_actions(broadcast: TDataItem) -> Iterable[TDataItem]:
|
|
368
|
+
broadcast_id = broadcast.get("id")
|
|
369
|
+
for item in client.fetch_broadcast_actions(create_client(), broadcast_id):
|
|
370
|
+
yield item
|
|
371
|
+
|
|
372
|
+
@dlt.transformer(
|
|
373
|
+
data_from=broadcast_actions,
|
|
374
|
+
write_disposition="replace",
|
|
375
|
+
primary_key=["broadcast_id", "action_id", "period", "step_index"],
|
|
376
|
+
)
|
|
377
|
+
def broadcast_action_metrics(action: TDataItem) -> Iterable[TDataItem]:
|
|
378
|
+
broadcast_id = action.get("broadcast_id")
|
|
379
|
+
action_id = action.get("id")
|
|
380
|
+
for item in client.fetch_broadcast_action_metrics(
|
|
381
|
+
create_client(), broadcast_id, action_id, period
|
|
382
|
+
):
|
|
383
|
+
yield item
|
|
384
|
+
|
|
385
|
+
return (broadcasts | broadcast_actions | broadcast_action_metrics,)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
@dlt.source(max_table_nesting=0)
|
|
389
|
+
def customer_io_campaign_metrics_source(
|
|
390
|
+
api_key: str,
|
|
391
|
+
region: str = "us",
|
|
392
|
+
period: str = "days",
|
|
393
|
+
start_date: TAnyDateTime | None = None,
|
|
394
|
+
end_date: TAnyDateTime | None = None,
|
|
395
|
+
) -> Iterable[DltResource]:
|
|
396
|
+
client = CustomerIoClient(api_key, region)
|
|
397
|
+
|
|
398
|
+
start_ts = _to_timestamp(start_date)
|
|
399
|
+
end_ts = _to_timestamp(end_date)
|
|
400
|
+
|
|
401
|
+
@dlt.resource(write_disposition="replace", primary_key="id", selected=False)
|
|
402
|
+
def campaigns() -> Iterable[TDataItem]:
|
|
403
|
+
for item in client.fetch_campaigns(create_client()):
|
|
404
|
+
yield item
|
|
405
|
+
|
|
406
|
+
@dlt.transformer(
|
|
407
|
+
data_from=campaigns,
|
|
408
|
+
write_disposition="replace",
|
|
409
|
+
primary_key=["campaign_id", "period", "step_index"],
|
|
410
|
+
)
|
|
411
|
+
def campaign_metrics(campaign: TDataItem) -> Iterable[TDataItem]:
|
|
412
|
+
campaign_id = campaign.get("id")
|
|
413
|
+
for item in client.fetch_campaign_metrics(
|
|
414
|
+
create_client(), campaign_id, period, start_ts, end_ts
|
|
415
|
+
):
|
|
416
|
+
yield item
|
|
417
|
+
|
|
418
|
+
return (campaigns | campaign_metrics,)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@dlt.source(max_table_nesting=0)
|
|
422
|
+
def customer_io_campaign_action_metrics_source(
|
|
423
|
+
api_key: str,
|
|
424
|
+
region: str = "us",
|
|
425
|
+
period: str = "days",
|
|
426
|
+
start_date: TAnyDateTime | None = None,
|
|
427
|
+
end_date: TAnyDateTime | None = None,
|
|
428
|
+
) -> Iterable[DltResource]:
|
|
429
|
+
client = CustomerIoClient(api_key, region)
|
|
430
|
+
|
|
431
|
+
start_ts = _to_timestamp(start_date)
|
|
432
|
+
end_ts = _to_timestamp(end_date)
|
|
433
|
+
|
|
434
|
+
@dlt.resource(write_disposition="replace", primary_key="id", selected=False)
|
|
435
|
+
def campaigns() -> Iterable[TDataItem]:
|
|
436
|
+
for item in client.fetch_campaigns(create_client()):
|
|
437
|
+
yield item
|
|
438
|
+
|
|
439
|
+
@dlt.transformer(data_from=campaigns, write_disposition="replace", selected=False)
|
|
440
|
+
def campaign_actions(campaign: TDataItem) -> Iterable[TDataItem]:
|
|
441
|
+
campaign_id = campaign.get("id")
|
|
442
|
+
for item in client.fetch_campaign_actions(create_client(), campaign_id):
|
|
443
|
+
yield item
|
|
444
|
+
|
|
445
|
+
@dlt.transformer(
|
|
446
|
+
data_from=campaign_actions,
|
|
447
|
+
write_disposition="replace",
|
|
448
|
+
primary_key=["campaign_id", "action_id", "period", "step_index"],
|
|
449
|
+
)
|
|
450
|
+
def campaign_action_metrics(action: TDataItem) -> Iterable[TDataItem]:
|
|
451
|
+
campaign_id = action.get("campaign_id")
|
|
452
|
+
action_id = action.get("id")
|
|
453
|
+
for item in client.fetch_campaign_action_metrics(
|
|
454
|
+
create_client(), campaign_id, action_id, period, start_ts, end_ts
|
|
455
|
+
):
|
|
456
|
+
yield item
|
|
457
|
+
|
|
458
|
+
return (campaigns | campaign_actions | campaign_action_metrics,)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@dlt.source(max_table_nesting=0)
|
|
462
|
+
def customer_io_newsletter_metrics_source(
|
|
463
|
+
api_key: str,
|
|
464
|
+
region: str = "us",
|
|
465
|
+
period: str = "days",
|
|
466
|
+
) -> Iterable[DltResource]:
|
|
467
|
+
client = CustomerIoClient(api_key, region)
|
|
468
|
+
|
|
469
|
+
@dlt.resource(write_disposition="replace", primary_key="id", selected=False)
|
|
470
|
+
def newsletters() -> Iterable[TDataItem]:
|
|
471
|
+
for item in client.fetch_newsletters(create_client()):
|
|
472
|
+
yield item
|
|
473
|
+
|
|
474
|
+
@dlt.transformer(
|
|
475
|
+
data_from=newsletters,
|
|
476
|
+
write_disposition="replace",
|
|
477
|
+
primary_key=["newsletter_id", "period", "step_index"],
|
|
478
|
+
)
|
|
479
|
+
def newsletter_metrics(newsletter: TDataItem) -> Iterable[TDataItem]:
|
|
480
|
+
newsletter_id = newsletter.get("id")
|
|
481
|
+
for item in client.fetch_newsletter_metrics(
|
|
482
|
+
create_client(), newsletter_id, period
|
|
483
|
+
):
|
|
484
|
+
yield item
|
|
485
|
+
|
|
486
|
+
return (newsletters | newsletter_metrics,)
|