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,530 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
BASE_URLS = {
|
|
6
|
+
"us": "https://api.customer.io",
|
|
7
|
+
"eu": "https://api-eu.customer.io",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
# Maximum steps for each metrics period type
|
|
11
|
+
MAX_STEPS_BY_PERIOD = {
|
|
12
|
+
"hours": 24,
|
|
13
|
+
"days": 45,
|
|
14
|
+
"weeks": 12,
|
|
15
|
+
"months": 12,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# For newsletter metrics, months has a different max
|
|
19
|
+
MAX_STEPS_NEWSLETTER = {
|
|
20
|
+
"hours": 24,
|
|
21
|
+
"days": 45,
|
|
22
|
+
"weeks": 12,
|
|
23
|
+
"months": 121,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CustomerIoClient:
|
|
28
|
+
def __init__(self, api_key: str, region: str = "us"):
|
|
29
|
+
self.api_key = api_key
|
|
30
|
+
self.base_url = BASE_URLS.get(region.lower(), BASE_URLS["us"])
|
|
31
|
+
|
|
32
|
+
def _get_headers(self):
|
|
33
|
+
return {
|
|
34
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
def _extract_metrics(
|
|
39
|
+
self,
|
|
40
|
+
result: dict,
|
|
41
|
+
period: str,
|
|
42
|
+
extra_fields: dict | None = None,
|
|
43
|
+
) -> list:
|
|
44
|
+
"""Extract metrics from API response and add common fields."""
|
|
45
|
+
metrics = []
|
|
46
|
+
for i, metric in enumerate(result.get("series", {}).get("series", [])):
|
|
47
|
+
metric["period"] = period
|
|
48
|
+
metric["step_index"] = i
|
|
49
|
+
if extra_fields:
|
|
50
|
+
metric.update(extra_fields)
|
|
51
|
+
metrics.append(metric)
|
|
52
|
+
return metrics
|
|
53
|
+
|
|
54
|
+
def _fetch_pages(
|
|
55
|
+
self,
|
|
56
|
+
session: requests.Session,
|
|
57
|
+
url: str,
|
|
58
|
+
params: dict[str, Any] | None = None,
|
|
59
|
+
data_key: str = "activities",
|
|
60
|
+
) -> list:
|
|
61
|
+
all_items: list[Any] = []
|
|
62
|
+
if params is None:
|
|
63
|
+
params = {}
|
|
64
|
+
|
|
65
|
+
while True:
|
|
66
|
+
response = session.get(url=url, headers=self._get_headers(), params=params)
|
|
67
|
+
response.raise_for_status()
|
|
68
|
+
result = response.json()
|
|
69
|
+
|
|
70
|
+
items = result.get(data_key) or []
|
|
71
|
+
all_items.extend(items)
|
|
72
|
+
|
|
73
|
+
next_token = result.get("next")
|
|
74
|
+
if not next_token:
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
params["start"] = next_token
|
|
78
|
+
|
|
79
|
+
return all_items
|
|
80
|
+
|
|
81
|
+
def fetch_activities(
|
|
82
|
+
self,
|
|
83
|
+
session: requests.Session,
|
|
84
|
+
activity_type: str | None = None,
|
|
85
|
+
name: str | None = None,
|
|
86
|
+
customer_id: str | None = None,
|
|
87
|
+
id_type: str | None = None,
|
|
88
|
+
deleted: bool = False,
|
|
89
|
+
limit: int = 100,
|
|
90
|
+
):
|
|
91
|
+
url = f"{self.base_url}/v1/activities"
|
|
92
|
+
params = {"limit": limit, "deleted": str(deleted).lower()}
|
|
93
|
+
|
|
94
|
+
if activity_type:
|
|
95
|
+
params["type"] = activity_type
|
|
96
|
+
if name:
|
|
97
|
+
params["name"] = name
|
|
98
|
+
if customer_id:
|
|
99
|
+
params["customer_id"] = customer_id
|
|
100
|
+
if id_type:
|
|
101
|
+
params["id_type"] = id_type
|
|
102
|
+
|
|
103
|
+
return self._fetch_pages(session, url, params, data_key="activities")
|
|
104
|
+
|
|
105
|
+
def fetch_broadcasts(self, session: requests.Session):
|
|
106
|
+
url = f"{self.base_url}/v1/broadcasts"
|
|
107
|
+
return self._fetch_pages(session, url, data_key="broadcasts")
|
|
108
|
+
|
|
109
|
+
def fetch_campaigns(self, session: requests.Session):
|
|
110
|
+
url = f"{self.base_url}/v1/campaigns"
|
|
111
|
+
return self._fetch_pages(session, url, data_key="campaigns")
|
|
112
|
+
|
|
113
|
+
def fetch_collections(self, session: requests.Session):
|
|
114
|
+
url = f"{self.base_url}/v1/collections"
|
|
115
|
+
return self._fetch_pages(session, url, data_key="collections")
|
|
116
|
+
|
|
117
|
+
def fetch_exports(self, session: requests.Session):
|
|
118
|
+
url = f"{self.base_url}/v1/exports"
|
|
119
|
+
return self._fetch_pages(session, url, data_key="exports")
|
|
120
|
+
|
|
121
|
+
def fetch_info_ip_addresses(self, session: requests.Session):
|
|
122
|
+
url = f"{self.base_url}/v1/info/ip_addresses"
|
|
123
|
+
response = session.get(url=url, headers=self._get_headers())
|
|
124
|
+
response.raise_for_status()
|
|
125
|
+
result = response.json()
|
|
126
|
+
ips = result.get("ips", [])
|
|
127
|
+
return [{"ip": ip} for ip in ips]
|
|
128
|
+
|
|
129
|
+
def fetch_messages(
|
|
130
|
+
self,
|
|
131
|
+
session: requests.Session,
|
|
132
|
+
start_ts: int | None = None,
|
|
133
|
+
end_ts: int | None = None,
|
|
134
|
+
) -> list:
|
|
135
|
+
url = f"{self.base_url}/v1/messages"
|
|
136
|
+
params = {"limit": 1000}
|
|
137
|
+
|
|
138
|
+
if start_ts:
|
|
139
|
+
params["start_ts"] = start_ts
|
|
140
|
+
if end_ts:
|
|
141
|
+
params["end_ts"] = end_ts
|
|
142
|
+
|
|
143
|
+
return self._fetch_pages(session, url, params, data_key="messages")
|
|
144
|
+
|
|
145
|
+
def fetch_newsletters(self, session: requests.Session) -> list:
|
|
146
|
+
url = f"{self.base_url}/v1/newsletters"
|
|
147
|
+
params = {"limit": 100}
|
|
148
|
+
return self._fetch_pages(session, url, params, data_key="newsletters")
|
|
149
|
+
|
|
150
|
+
def fetch_reporting_webhooks(self, session: requests.Session) -> list:
|
|
151
|
+
url = f"{self.base_url}/v1/reporting_webhooks"
|
|
152
|
+
response = session.get(url=url, headers=self._get_headers())
|
|
153
|
+
response.raise_for_status()
|
|
154
|
+
result = response.json()
|
|
155
|
+
return result.get("reporting_webhooks") or []
|
|
156
|
+
|
|
157
|
+
def fetch_segments(self, session: requests.Session) -> list:
|
|
158
|
+
url = f"{self.base_url}/v1/segments"
|
|
159
|
+
return self._fetch_pages(session, url, data_key="segments")
|
|
160
|
+
|
|
161
|
+
def fetch_transactional_messages(self, session: requests.Session) -> list:
|
|
162
|
+
url = f"{self.base_url}/v1/transactional"
|
|
163
|
+
return self._fetch_pages(session, url, data_key="transactional")
|
|
164
|
+
|
|
165
|
+
def fetch_workspaces(self, session: requests.Session) -> list:
|
|
166
|
+
url = f"{self.base_url}/v1/workspaces"
|
|
167
|
+
response = session.get(url=url, headers=self._get_headers())
|
|
168
|
+
response.raise_for_status()
|
|
169
|
+
result = response.json()
|
|
170
|
+
return result.get("workspaces", [])
|
|
171
|
+
|
|
172
|
+
def fetch_newsletter_test_groups(
|
|
173
|
+
self, session: requests.Session, newsletter_id: int
|
|
174
|
+
) -> list:
|
|
175
|
+
url = f"{self.base_url}/v1/newsletters/{newsletter_id}/test_groups"
|
|
176
|
+
response = session.get(url=url, headers=self._get_headers())
|
|
177
|
+
response.raise_for_status()
|
|
178
|
+
result = response.json()
|
|
179
|
+
|
|
180
|
+
groups = []
|
|
181
|
+
for group in result.get("test_groups", []):
|
|
182
|
+
group["newsletter_id"] = newsletter_id
|
|
183
|
+
groups.append(group)
|
|
184
|
+
|
|
185
|
+
return groups
|
|
186
|
+
|
|
187
|
+
def fetch_newsletter_metrics(
|
|
188
|
+
self,
|
|
189
|
+
session: requests.Session,
|
|
190
|
+
newsletter_id: int,
|
|
191
|
+
period: str = "days",
|
|
192
|
+
) -> list:
|
|
193
|
+
steps = MAX_STEPS_NEWSLETTER.get(period, 45)
|
|
194
|
+
url = f"{self.base_url}/v1/newsletters/{newsletter_id}/metrics"
|
|
195
|
+
params: dict[str, Any] = {"period": period, "steps": steps}
|
|
196
|
+
|
|
197
|
+
response = session.get(url=url, headers=self._get_headers(), params=params)
|
|
198
|
+
response.raise_for_status()
|
|
199
|
+
return self._extract_metrics(
|
|
200
|
+
response.json(), period, {"newsletter_id": newsletter_id}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def fetch_broadcast_metrics(
|
|
204
|
+
self,
|
|
205
|
+
session: requests.Session,
|
|
206
|
+
period: str = "days",
|
|
207
|
+
) -> list:
|
|
208
|
+
steps = MAX_STEPS_BY_PERIOD.get(period, 45)
|
|
209
|
+
broadcasts = self.fetch_broadcasts(session)
|
|
210
|
+
all_metrics = []
|
|
211
|
+
|
|
212
|
+
for broadcast in broadcasts:
|
|
213
|
+
broadcast_id = broadcast.get("id")
|
|
214
|
+
url = f"{self.base_url}/v1/broadcasts/{broadcast_id}/metrics"
|
|
215
|
+
params: dict[str, Any] = {"period": period, "steps": steps}
|
|
216
|
+
|
|
217
|
+
response = session.get(url=url, headers=self._get_headers(), params=params)
|
|
218
|
+
response.raise_for_status()
|
|
219
|
+
all_metrics.extend(
|
|
220
|
+
self._extract_metrics(
|
|
221
|
+
response.json(), period, {"broadcast_id": broadcast_id}
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return all_metrics
|
|
226
|
+
|
|
227
|
+
def fetch_broadcast_actions(
|
|
228
|
+
self, session: requests.Session, broadcast_id: int
|
|
229
|
+
) -> list:
|
|
230
|
+
url = f"{self.base_url}/v1/broadcasts/{broadcast_id}/actions"
|
|
231
|
+
|
|
232
|
+
response = session.get(url=url, headers=self._get_headers())
|
|
233
|
+
response.raise_for_status()
|
|
234
|
+
result = response.json()
|
|
235
|
+
|
|
236
|
+
actions = []
|
|
237
|
+
for action in result.get("actions", []):
|
|
238
|
+
action["broadcast_id"] = broadcast_id
|
|
239
|
+
actions.append(action)
|
|
240
|
+
|
|
241
|
+
return actions
|
|
242
|
+
|
|
243
|
+
def fetch_broadcast_messages(
|
|
244
|
+
self,
|
|
245
|
+
session: requests.Session,
|
|
246
|
+
broadcast_id: int,
|
|
247
|
+
start_ts: int | None = None,
|
|
248
|
+
end_ts: int | None = None,
|
|
249
|
+
) -> list:
|
|
250
|
+
url = f"{self.base_url}/v1/broadcasts/{broadcast_id}/messages"
|
|
251
|
+
params: dict[str, Any] = {}
|
|
252
|
+
|
|
253
|
+
if start_ts:
|
|
254
|
+
params["start_ts"] = start_ts
|
|
255
|
+
if end_ts:
|
|
256
|
+
params["end_ts"] = end_ts
|
|
257
|
+
|
|
258
|
+
return self._fetch_pages(session, url, params, data_key="messages")
|
|
259
|
+
|
|
260
|
+
def fetch_broadcast_action_metrics(
|
|
261
|
+
self,
|
|
262
|
+
session: requests.Session,
|
|
263
|
+
broadcast_id: int,
|
|
264
|
+
action_id: int,
|
|
265
|
+
period: str = "days",
|
|
266
|
+
) -> list:
|
|
267
|
+
steps = MAX_STEPS_BY_PERIOD.get(period, 45)
|
|
268
|
+
url = (
|
|
269
|
+
f"{self.base_url}/v1/broadcasts/{broadcast_id}/actions/{action_id}/metrics"
|
|
270
|
+
)
|
|
271
|
+
params: dict[str, Any] = {"period": period, "steps": steps}
|
|
272
|
+
|
|
273
|
+
response = session.get(url=url, headers=self._get_headers(), params=params)
|
|
274
|
+
response.raise_for_status()
|
|
275
|
+
return self._extract_metrics(
|
|
276
|
+
response.json(),
|
|
277
|
+
period,
|
|
278
|
+
{"broadcast_id": broadcast_id, "action_id": action_id},
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def fetch_campaign_metrics(
|
|
282
|
+
self,
|
|
283
|
+
session: requests.Session,
|
|
284
|
+
campaign_id: int,
|
|
285
|
+
period: str = "days",
|
|
286
|
+
start_ts: int | None = None,
|
|
287
|
+
end_ts: int | None = None,
|
|
288
|
+
) -> list:
|
|
289
|
+
url = f"{self.base_url}/v1/campaigns/{campaign_id}/metrics"
|
|
290
|
+
params: dict[str, Any] = {"version": 2, "res": period}
|
|
291
|
+
|
|
292
|
+
if start_ts:
|
|
293
|
+
params["start"] = start_ts
|
|
294
|
+
if end_ts:
|
|
295
|
+
params["end"] = end_ts
|
|
296
|
+
|
|
297
|
+
response = session.get(url=url, headers=self._get_headers(), params=params)
|
|
298
|
+
response.raise_for_status()
|
|
299
|
+
return self._extract_metrics(
|
|
300
|
+
response.json(), period, {"campaign_id": campaign_id}
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def fetch_campaign_actions(
|
|
304
|
+
self, session: requests.Session, campaign_id: int
|
|
305
|
+
) -> list:
|
|
306
|
+
url = f"{self.base_url}/v1/campaigns/{campaign_id}/actions"
|
|
307
|
+
actions = self._fetch_pages(session, url, data_key="actions")
|
|
308
|
+
for action in actions:
|
|
309
|
+
action["campaign_id"] = campaign_id
|
|
310
|
+
return actions
|
|
311
|
+
|
|
312
|
+
def fetch_sender_identities(self, session: requests.Session) -> list:
|
|
313
|
+
url = f"{self.base_url}/v1/sender_identities"
|
|
314
|
+
return self._fetch_pages(session, url, data_key="sender_identities")
|
|
315
|
+
|
|
316
|
+
def fetch_campaign_action_metrics(
|
|
317
|
+
self,
|
|
318
|
+
session: requests.Session,
|
|
319
|
+
campaign_id: int,
|
|
320
|
+
action_id: int,
|
|
321
|
+
period: str = "days",
|
|
322
|
+
start_ts: int | None = None,
|
|
323
|
+
end_ts: int | None = None,
|
|
324
|
+
) -> list:
|
|
325
|
+
url = f"{self.base_url}/v1/campaigns/{campaign_id}/actions/{action_id}/metrics"
|
|
326
|
+
params: dict[str, Any] = {"version": 2, "res": period}
|
|
327
|
+
|
|
328
|
+
if start_ts:
|
|
329
|
+
params["start"] = start_ts
|
|
330
|
+
if end_ts:
|
|
331
|
+
params["end"] = end_ts
|
|
332
|
+
|
|
333
|
+
response = session.get(url=url, headers=self._get_headers(), params=params)
|
|
334
|
+
response.raise_for_status()
|
|
335
|
+
return self._extract_metrics(
|
|
336
|
+
response.json(),
|
|
337
|
+
period,
|
|
338
|
+
{"campaign_id": campaign_id, "action_id": action_id},
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
def fetch_customers(
|
|
342
|
+
self,
|
|
343
|
+
session: requests.Session,
|
|
344
|
+
limit: int = 1000,
|
|
345
|
+
) -> list:
|
|
346
|
+
"""Fetch customers by iterating through all segments and getting their members."""
|
|
347
|
+
# The POST /v1/customers endpoint requires a filter, so we fetch segment members instead
|
|
348
|
+
all_customers = {}
|
|
349
|
+
segments = self.fetch_segments(session)
|
|
350
|
+
|
|
351
|
+
for segment in segments:
|
|
352
|
+
segment_id = segment.get("id")
|
|
353
|
+
if segment_id:
|
|
354
|
+
members = self.fetch_segment_members(session, segment_id, limit)
|
|
355
|
+
for member in members:
|
|
356
|
+
# Use cio_id as unique key to deduplicate across segments
|
|
357
|
+
cio_id = member.get("cio_id")
|
|
358
|
+
if cio_id and cio_id not in all_customers:
|
|
359
|
+
all_customers[cio_id] = member
|
|
360
|
+
|
|
361
|
+
return list(all_customers.values())
|
|
362
|
+
|
|
363
|
+
def fetch_segment_members(
|
|
364
|
+
self,
|
|
365
|
+
session: requests.Session,
|
|
366
|
+
segment_id: int,
|
|
367
|
+
limit: int = 1000,
|
|
368
|
+
) -> list:
|
|
369
|
+
"""Fetch members of a specific segment."""
|
|
370
|
+
url = f"{self.base_url}/v1/segments/{segment_id}/membership"
|
|
371
|
+
params = {"limit": limit}
|
|
372
|
+
return self._fetch_pages(session, url, params, data_key="identifiers")
|
|
373
|
+
|
|
374
|
+
def _fetch_pages_post(
|
|
375
|
+
self,
|
|
376
|
+
session: requests.Session,
|
|
377
|
+
url: str,
|
|
378
|
+
params: dict[str, Any] | None = None,
|
|
379
|
+
data_key: str = "identifiers",
|
|
380
|
+
) -> list:
|
|
381
|
+
"""Pagination helper for POST requests."""
|
|
382
|
+
all_items: list[Any] = []
|
|
383
|
+
if params is None:
|
|
384
|
+
params = {}
|
|
385
|
+
query_params: dict[str, Any] = {}
|
|
386
|
+
|
|
387
|
+
while True:
|
|
388
|
+
response = session.post(
|
|
389
|
+
url=url, headers=self._get_headers(), json=params, params=query_params
|
|
390
|
+
)
|
|
391
|
+
response.raise_for_status()
|
|
392
|
+
result = response.json()
|
|
393
|
+
|
|
394
|
+
items = result.get(data_key) or []
|
|
395
|
+
all_items.extend(items)
|
|
396
|
+
|
|
397
|
+
next_token = result.get("next")
|
|
398
|
+
if not next_token:
|
|
399
|
+
break
|
|
400
|
+
|
|
401
|
+
query_params["start"] = next_token
|
|
402
|
+
|
|
403
|
+
return all_items
|
|
404
|
+
|
|
405
|
+
def fetch_customer_attributes(
|
|
406
|
+
self, session: requests.Session, customer_id: str
|
|
407
|
+
) -> dict | None:
|
|
408
|
+
"""Fetch attributes for a specific customer."""
|
|
409
|
+
url = f"{self.base_url}/v1/customers/{customer_id}/attributes"
|
|
410
|
+
response = session.get(url=url, headers=self._get_headers())
|
|
411
|
+
if response.status_code == 404:
|
|
412
|
+
return None
|
|
413
|
+
response.raise_for_status()
|
|
414
|
+
result = response.json()
|
|
415
|
+
customer = result.get("customer", {})
|
|
416
|
+
customer["customer_id"] = customer_id
|
|
417
|
+
return customer
|
|
418
|
+
|
|
419
|
+
def fetch_customer_messages(
|
|
420
|
+
self,
|
|
421
|
+
session: requests.Session,
|
|
422
|
+
customer_id: str,
|
|
423
|
+
start_ts: int | None = None,
|
|
424
|
+
end_ts: int | None = None,
|
|
425
|
+
) -> list:
|
|
426
|
+
"""Fetch messages sent to a specific customer."""
|
|
427
|
+
url = f"{self.base_url}/v1/customers/{customer_id}/messages"
|
|
428
|
+
params = {}
|
|
429
|
+
if start_ts:
|
|
430
|
+
params["start_ts"] = start_ts
|
|
431
|
+
if end_ts:
|
|
432
|
+
params["end_ts"] = end_ts
|
|
433
|
+
|
|
434
|
+
messages = self._fetch_pages(session, url, params, data_key="messages")
|
|
435
|
+
for msg in messages:
|
|
436
|
+
msg["customer_id"] = customer_id
|
|
437
|
+
return messages
|
|
438
|
+
|
|
439
|
+
def fetch_customer_activities(
|
|
440
|
+
self, session: requests.Session, customer_id: str
|
|
441
|
+
) -> list:
|
|
442
|
+
"""Fetch activities for a specific customer."""
|
|
443
|
+
url = f"{self.base_url}/v1/customers/{customer_id}/activities"
|
|
444
|
+
activities = self._fetch_pages(session, url, data_key="activities")
|
|
445
|
+
for activity in activities:
|
|
446
|
+
activity["customer_id"] = customer_id
|
|
447
|
+
return activities
|
|
448
|
+
|
|
449
|
+
def fetch_customer_relationships(
|
|
450
|
+
self, session: requests.Session, customer_id: str
|
|
451
|
+
) -> list:
|
|
452
|
+
"""Fetch object relationships for a specific customer."""
|
|
453
|
+
url = f"{self.base_url}/v1/customers/{customer_id}/relationships"
|
|
454
|
+
relationships = self._fetch_pages(session, url, data_key="cio_relationships")
|
|
455
|
+
for rel in relationships:
|
|
456
|
+
rel["customer_id"] = customer_id
|
|
457
|
+
return relationships
|
|
458
|
+
|
|
459
|
+
def fetch_object_types(self, session: requests.Session) -> list:
|
|
460
|
+
"""Fetch all object types in the workspace."""
|
|
461
|
+
url = f"{self.base_url}/v1/object_types"
|
|
462
|
+
response = session.get(url=url, headers=self._get_headers())
|
|
463
|
+
response.raise_for_status()
|
|
464
|
+
result = response.json()
|
|
465
|
+
return result.get("types", [])
|
|
466
|
+
|
|
467
|
+
def fetch_objects(
|
|
468
|
+
self, session: requests.Session, object_type_id: str, limit: int = 1000
|
|
469
|
+
) -> list:
|
|
470
|
+
"""Fetch objects of a specific type."""
|
|
471
|
+
url = f"{self.base_url}/v1/objects"
|
|
472
|
+
params = {"object_type_id": object_type_id, "limit": limit}
|
|
473
|
+
objects = self._fetch_pages_post(session, url, params, data_key="identifiers")
|
|
474
|
+
for obj in objects:
|
|
475
|
+
obj["object_type_id"] = object_type_id
|
|
476
|
+
return objects
|
|
477
|
+
|
|
478
|
+
def fetch_object_attributes(
|
|
479
|
+
self, session: requests.Session, object_type_id: str, object_id: str
|
|
480
|
+
) -> dict | None:
|
|
481
|
+
"""Fetch attributes for a specific object."""
|
|
482
|
+
url = f"{self.base_url}/v1/objects/{object_type_id}/{object_id}/attributes"
|
|
483
|
+
response = session.get(url=url, headers=self._get_headers())
|
|
484
|
+
if response.status_code == 404:
|
|
485
|
+
return None
|
|
486
|
+
response.raise_for_status()
|
|
487
|
+
result = response.json()
|
|
488
|
+
obj = result.get("object", {})
|
|
489
|
+
obj["object_type_id"] = object_type_id
|
|
490
|
+
obj["object_id"] = object_id
|
|
491
|
+
return obj
|
|
492
|
+
|
|
493
|
+
def fetch_object_relationships(
|
|
494
|
+
self, session: requests.Session, object_type_id: str, object_id: str
|
|
495
|
+
) -> list:
|
|
496
|
+
"""Fetch people related to a specific object."""
|
|
497
|
+
url = f"{self.base_url}/v1/objects/{object_type_id}/{object_id}/relationships"
|
|
498
|
+
relationships = self._fetch_pages(session, url, data_key="cio_relationships")
|
|
499
|
+
for rel in relationships:
|
|
500
|
+
rel["object_type_id"] = object_type_id
|
|
501
|
+
rel["object_id"] = object_id
|
|
502
|
+
return relationships
|
|
503
|
+
|
|
504
|
+
def fetch_campaign_messages(
|
|
505
|
+
self,
|
|
506
|
+
session: requests.Session,
|
|
507
|
+
campaign_id: int,
|
|
508
|
+
start_ts: int | None = None,
|
|
509
|
+
end_ts: int | None = None,
|
|
510
|
+
) -> list:
|
|
511
|
+
"""Fetch messages/deliveries for a specific campaign."""
|
|
512
|
+
url = f"{self.base_url}/v1/campaigns/{campaign_id}/messages"
|
|
513
|
+
params = {}
|
|
514
|
+
if start_ts:
|
|
515
|
+
params["start_ts"] = start_ts
|
|
516
|
+
if end_ts:
|
|
517
|
+
params["end_ts"] = end_ts
|
|
518
|
+
|
|
519
|
+
messages = self._fetch_pages(session, url, params, data_key="messages")
|
|
520
|
+
for msg in messages:
|
|
521
|
+
msg["campaign_id"] = campaign_id
|
|
522
|
+
return messages
|
|
523
|
+
|
|
524
|
+
def fetch_subscription_topics(self, session: requests.Session) -> list:
|
|
525
|
+
"""Fetch subscription topics in the workspace."""
|
|
526
|
+
url = f"{self.base_url}/v1/subscription_topics"
|
|
527
|
+
response = session.get(url=url, headers=self._get_headers())
|
|
528
|
+
response.raise_for_status()
|
|
529
|
+
result = response.json()
|
|
530
|
+
return result.get("topics", [])
|