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.
Files changed (218) hide show
  1. omniload/conftest.py +72 -0
  2. omniload/main.py +810 -0
  3. omniload/src/.gitignore +10 -0
  4. omniload/src/adjust/__init__.py +108 -0
  5. omniload/src/adjust/adjust_helpers.py +122 -0
  6. omniload/src/airtable/__init__.py +84 -0
  7. omniload/src/allium/__init__.py +128 -0
  8. omniload/src/anthropic/__init__.py +277 -0
  9. omniload/src/anthropic/helpers.py +525 -0
  10. omniload/src/applovin/__init__.py +316 -0
  11. omniload/src/applovin_max/__init__.py +117 -0
  12. omniload/src/appsflyer/__init__.py +325 -0
  13. omniload/src/appsflyer/client.py +110 -0
  14. omniload/src/appstore/__init__.py +142 -0
  15. omniload/src/appstore/client.py +126 -0
  16. omniload/src/appstore/errors.py +15 -0
  17. omniload/src/appstore/models.py +117 -0
  18. omniload/src/appstore/resources.py +179 -0
  19. omniload/src/arrow/__init__.py +81 -0
  20. omniload/src/asana_source/__init__.py +281 -0
  21. omniload/src/asana_source/helpers.py +30 -0
  22. omniload/src/asana_source/settings.py +158 -0
  23. omniload/src/attio/__init__.py +102 -0
  24. omniload/src/attio/helpers.py +65 -0
  25. omniload/src/blob.py +95 -0
  26. omniload/src/bruin/__init__.py +76 -0
  27. omniload/src/chess/__init__.py +180 -0
  28. omniload/src/chess/helpers.py +35 -0
  29. omniload/src/chess/settings.py +18 -0
  30. omniload/src/clickup/__init__.py +85 -0
  31. omniload/src/clickup/helpers.py +47 -0
  32. omniload/src/collector/spinner.py +43 -0
  33. omniload/src/couchbase_source/__init__.py +118 -0
  34. omniload/src/couchbase_source/helpers.py +135 -0
  35. omniload/src/cursor/__init__.py +83 -0
  36. omniload/src/cursor/helpers.py +188 -0
  37. omniload/src/customer_io/__init__.py +486 -0
  38. omniload/src/customer_io/helpers.py +530 -0
  39. omniload/src/destinations.py +982 -0
  40. omniload/src/docebo/__init__.py +589 -0
  41. omniload/src/docebo/client.py +435 -0
  42. omniload/src/docebo/helpers.py +97 -0
  43. omniload/src/dune/__init__.py +104 -0
  44. omniload/src/dune/helpers.py +108 -0
  45. omniload/src/dynamodb/__init__.py +86 -0
  46. omniload/src/elasticsearch/__init__.py +80 -0
  47. omniload/src/elasticsearch/helpers.py +141 -0
  48. omniload/src/errors.py +26 -0
  49. omniload/src/facebook_ads/__init__.py +403 -0
  50. omniload/src/facebook_ads/exceptions.py +19 -0
  51. omniload/src/facebook_ads/helpers.py +296 -0
  52. omniload/src/facebook_ads/settings.py +224 -0
  53. omniload/src/facebook_ads/utils.py +53 -0
  54. omniload/src/factory.py +305 -0
  55. omniload/src/filesystem/__init__.py +133 -0
  56. omniload/src/filesystem/helpers.py +114 -0
  57. omniload/src/filesystem/readers.py +187 -0
  58. omniload/src/filters.py +62 -0
  59. omniload/src/fireflies/__init__.py +151 -0
  60. omniload/src/fireflies/helpers.py +753 -0
  61. omniload/src/fluxx/__init__.py +10013 -0
  62. omniload/src/fluxx/helpers.py +233 -0
  63. omniload/src/frankfurter/__init__.py +157 -0
  64. omniload/src/frankfurter/helpers.py +48 -0
  65. omniload/src/freshdesk/__init__.py +103 -0
  66. omniload/src/freshdesk/freshdesk_client.py +151 -0
  67. omniload/src/freshdesk/settings.py +23 -0
  68. omniload/src/fundraiseup/__init__.py +95 -0
  69. omniload/src/fundraiseup/client.py +81 -0
  70. omniload/src/github/__init__.py +202 -0
  71. omniload/src/github/helpers.py +207 -0
  72. omniload/src/github/queries.py +129 -0
  73. omniload/src/github/settings.py +24 -0
  74. omniload/src/google_ads/__init__.py +198 -0
  75. omniload/src/google_ads/field.py +17 -0
  76. omniload/src/google_ads/metrics.py +254 -0
  77. omniload/src/google_ads/predicates.py +37 -0
  78. omniload/src/google_ads/reports.py +411 -0
  79. omniload/src/google_ads/test_google_ads.py +184 -0
  80. omniload/src/google_analytics/__init__.py +144 -0
  81. omniload/src/google_analytics/helpers.py +312 -0
  82. omniload/src/google_sheets/README.md +95 -0
  83. omniload/src/google_sheets/__init__.py +166 -0
  84. omniload/src/google_sheets/helpers/__init__.py +15 -0
  85. omniload/src/google_sheets/helpers/api_calls.py +160 -0
  86. omniload/src/google_sheets/helpers/data_processing.py +316 -0
  87. omniload/src/gorgias/__init__.py +595 -0
  88. omniload/src/gorgias/helpers.py +166 -0
  89. omniload/src/hostaway/__init__.py +302 -0
  90. omniload/src/hostaway/client.py +288 -0
  91. omniload/src/http/__init__.py +38 -0
  92. omniload/src/http/readers.py +146 -0
  93. omniload/src/http_client.py +24 -0
  94. omniload/src/hubspot/__init__.py +800 -0
  95. omniload/src/hubspot/helpers.py +417 -0
  96. omniload/src/hubspot/settings.py +329 -0
  97. omniload/src/indeed/__init__.py +153 -0
  98. omniload/src/indeed/helpers.py +228 -0
  99. omniload/src/influxdb/__init__.py +46 -0
  100. omniload/src/influxdb/client.py +34 -0
  101. omniload/src/intercom/__init__.py +142 -0
  102. omniload/src/intercom/helpers.py +674 -0
  103. omniload/src/intercom/settings.py +279 -0
  104. omniload/src/isoc_pulse/__init__.py +159 -0
  105. omniload/src/jira_source/__init__.py +377 -0
  106. omniload/src/jira_source/helpers.py +510 -0
  107. omniload/src/jira_source/settings.py +184 -0
  108. omniload/src/kafka/__init__.py +120 -0
  109. omniload/src/kafka/helpers.py +241 -0
  110. omniload/src/kinesis/__init__.py +153 -0
  111. omniload/src/kinesis/helpers.py +96 -0
  112. omniload/src/klaviyo/__init__.py +237 -0
  113. omniload/src/klaviyo/client.py +212 -0
  114. omniload/src/klaviyo/helpers.py +19 -0
  115. omniload/src/linear/__init__.py +634 -0
  116. omniload/src/linear/helpers.py +111 -0
  117. omniload/src/linkedin_ads/__init__.py +266 -0
  118. omniload/src/linkedin_ads/dimension_time_enum.py +17 -0
  119. omniload/src/linkedin_ads/helpers.py +246 -0
  120. omniload/src/loader.py +69 -0
  121. omniload/src/mailchimp/__init__.py +126 -0
  122. omniload/src/mailchimp/helpers.py +226 -0
  123. omniload/src/mailchimp/settings.py +164 -0
  124. omniload/src/masking.py +344 -0
  125. omniload/src/mixpanel/__init__.py +62 -0
  126. omniload/src/mixpanel/client.py +104 -0
  127. omniload/src/monday/__init__.py +246 -0
  128. omniload/src/monday/helpers.py +392 -0
  129. omniload/src/monday/settings.py +325 -0
  130. omniload/src/mongodb/__init__.py +281 -0
  131. omniload/src/mongodb/helpers.py +975 -0
  132. omniload/src/notion/__init__.py +69 -0
  133. omniload/src/notion/helpers/__init__.py +14 -0
  134. omniload/src/notion/helpers/client.py +178 -0
  135. omniload/src/notion/helpers/database.py +92 -0
  136. omniload/src/notion/settings.py +17 -0
  137. omniload/src/partition.py +32 -0
  138. omniload/src/personio/__init__.py +345 -0
  139. omniload/src/personio/helpers.py +100 -0
  140. omniload/src/phantombuster/__init__.py +65 -0
  141. omniload/src/phantombuster/client.py +87 -0
  142. omniload/src/pinterest/__init__.py +82 -0
  143. omniload/src/pipedrive/__init__.py +212 -0
  144. omniload/src/pipedrive/helpers/__init__.py +37 -0
  145. omniload/src/pipedrive/helpers/custom_fields_munger.py +116 -0
  146. omniload/src/pipedrive/helpers/pages.py +129 -0
  147. omniload/src/pipedrive/settings.py +41 -0
  148. omniload/src/pipedrive/typing.py +17 -0
  149. omniload/src/plusvibeai/__init__.py +335 -0
  150. omniload/src/plusvibeai/helpers.py +544 -0
  151. omniload/src/plusvibeai/settings.py +252 -0
  152. omniload/src/primer/__init__.py +45 -0
  153. omniload/src/primer/helpers.py +79 -0
  154. omniload/src/quickbooks/__init__.py +117 -0
  155. omniload/src/reddit_ads/__init__.py +183 -0
  156. omniload/src/reddit_ads/helpers.py +232 -0
  157. omniload/src/resource.py +40 -0
  158. omniload/src/revenuecat/__init__.py +83 -0
  159. omniload/src/revenuecat/helpers.py +237 -0
  160. omniload/src/salesforce/__init__.py +170 -0
  161. omniload/src/salesforce/helpers.py +78 -0
  162. omniload/src/shopify/__init__.py +1953 -0
  163. omniload/src/shopify/exceptions.py +17 -0
  164. omniload/src/shopify/helpers.py +202 -0
  165. omniload/src/shopify/settings.py +19 -0
  166. omniload/src/slack/__init__.py +290 -0
  167. omniload/src/slack/helpers.py +218 -0
  168. omniload/src/slack/settings.py +36 -0
  169. omniload/src/smartsheets/__init__.py +82 -0
  170. omniload/src/snapchat_ads/__init__.py +455 -0
  171. omniload/src/snapchat_ads/client.py +72 -0
  172. omniload/src/snapchat_ads/helpers.py +630 -0
  173. omniload/src/snapchat_ads/settings.py +130 -0
  174. omniload/src/socrata_source/__init__.py +83 -0
  175. omniload/src/socrata_source/helpers.py +85 -0
  176. omniload/src/socrata_source/settings.py +8 -0
  177. omniload/src/solidgate/__init__.py +219 -0
  178. omniload/src/solidgate/helpers.py +154 -0
  179. omniload/src/sources.py +5408 -0
  180. omniload/src/sql_database/__init__.py +0 -0
  181. omniload/src/sql_database/callbacks.py +66 -0
  182. omniload/src/stripe_analytics/__init__.py +183 -0
  183. omniload/src/stripe_analytics/helpers.py +386 -0
  184. omniload/src/stripe_analytics/settings.py +80 -0
  185. omniload/src/table_definition.py +15 -0
  186. omniload/src/testdata/fakebqcredentials.json +14 -0
  187. omniload/src/tiktok_ads/__init__.py +150 -0
  188. omniload/src/tiktok_ads/tiktok_helpers.py +130 -0
  189. omniload/src/time.py +11 -0
  190. omniload/src/trustpilot/__init__.py +48 -0
  191. omniload/src/trustpilot/client.py +48 -0
  192. omniload/src/version.py +6 -0
  193. omniload/src/wise/__init__.py +68 -0
  194. omniload/src/wise/client.py +63 -0
  195. omniload/src/zendesk/__init__.py +480 -0
  196. omniload/src/zendesk/helpers/__init__.py +39 -0
  197. omniload/src/zendesk/helpers/api_helpers.py +119 -0
  198. omniload/src/zendesk/helpers/credentials.py +68 -0
  199. omniload/src/zendesk/helpers/talk_api.py +132 -0
  200. omniload/src/zendesk/settings.py +71 -0
  201. omniload/src/zoom/__init__.py +99 -0
  202. omniload/src/zoom/helpers.py +102 -0
  203. omniload/testdata/.gitignore +2 -0
  204. omniload/testdata/create_replace.csv +21 -0
  205. omniload/testdata/delete_insert_expected.csv +6 -0
  206. omniload/testdata/delete_insert_part1.csv +5 -0
  207. omniload/testdata/delete_insert_part2.csv +6 -0
  208. omniload/testdata/merge_expected.csv +5 -0
  209. omniload/testdata/merge_part1.csv +4 -0
  210. omniload/testdata/merge_part2.csv +5 -0
  211. omniload/tests/unit/test_smartsheets.py +133 -0
  212. omniload-0.0.0.dev0.dist-info/METADATA +439 -0
  213. omniload-0.0.0.dev0.dist-info/RECORD +218 -0
  214. omniload-0.0.0.dev0.dist-info/WHEEL +4 -0
  215. omniload-0.0.0.dev0.dist-info/entry_points.txt +2 -0
  216. omniload-0.0.0.dev0.dist-info/licenses/LICENSE.Apache-2.0 +201 -0
  217. omniload-0.0.0.dev0.dist-info/licenses/LICENSE.md +21 -0
  218. omniload-0.0.0.dev0.dist-info/licenses/NOTICE +35 -0
@@ -0,0 +1,237 @@
1
+ from typing import Iterable
2
+
3
+ import dlt
4
+ import pendulum
5
+ import requests
6
+ from dlt.common.time import ensure_pendulum_datetime
7
+ from dlt.common.typing import TAnyDateTime, TDataItem
8
+ from dlt.sources import DltResource
9
+ from dlt.sources.helpers.requests import Client
10
+
11
+ from omniload.src.klaviyo.client import KlaviyoClient
12
+ from omniload.src.klaviyo.helpers import split_date_range
13
+
14
+
15
+ def retry_on_limit(response: requests.Response, exception: BaseException) -> bool:
16
+ return response.status_code == 429
17
+
18
+
19
+ def create_client() -> requests.Session:
20
+ return Client(
21
+ raise_for_status=False,
22
+ retry_condition=retry_on_limit,
23
+ request_max_attempts=12,
24
+ request_backoff_factor=2,
25
+ ).session
26
+
27
+
28
+ @dlt.source(max_table_nesting=0)
29
+ def klaviyo_source(api_key: str, start_date: TAnyDateTime) -> Iterable[DltResource]:
30
+ start_date_obj = ensure_pendulum_datetime(start_date)
31
+ client = KlaviyoClient(api_key)
32
+
33
+ @dlt.resource(write_disposition="merge", primary_key="id", parallelized=True)
34
+ def events(
35
+ datetime=dlt.sources.incremental(
36
+ "datetime",
37
+ start_date_obj.isoformat(),
38
+ range_end="closed",
39
+ range_start="closed",
40
+ ),
41
+ ) -> Iterable[TDataItem]:
42
+ intervals = split_date_range(
43
+ pendulum.parse(datetime.start_value), pendulum.now()
44
+ )
45
+
46
+ for start, end in intervals:
47
+ yield lambda s=start, e=end: client.fetch_events(create_client(), s, e)
48
+
49
+ @dlt.resource(write_disposition="merge", primary_key="id", parallelized=True)
50
+ def profiles(
51
+ updated=dlt.sources.incremental(
52
+ "updated",
53
+ start_date_obj.isoformat(),
54
+ range_end="closed",
55
+ range_start="closed",
56
+ ),
57
+ ) -> Iterable[TDataItem]:
58
+ intervals = split_date_range(
59
+ pendulum.parse(updated.start_value), pendulum.now()
60
+ )
61
+
62
+ for start, end in intervals:
63
+ yield lambda s=start, e=end: client.fetch_profiles(create_client(), s, e)
64
+
65
+ @dlt.resource(write_disposition="merge", primary_key="id", parallelized=True)
66
+ def campaigns(
67
+ updated_at=dlt.sources.incremental(
68
+ "updated_at",
69
+ start_date_obj.isoformat(),
70
+ range_end="closed",
71
+ range_start="closed",
72
+ ),
73
+ ) -> Iterable[TDataItem]:
74
+ intervals = split_date_range(
75
+ pendulum.parse(updated_at.start_value), pendulum.now()
76
+ )
77
+
78
+ for campaign_type in ["email", "sms"]:
79
+ for start, end in intervals:
80
+ yield lambda s=start, e=end, ct=campaign_type: client.fetch_campaigns(
81
+ create_client(), s, e, ct
82
+ )
83
+
84
+ @dlt.resource(write_disposition="merge", primary_key="id")
85
+ def metrics(
86
+ updated=dlt.sources.incremental(
87
+ "updated",
88
+ start_date_obj.isoformat(),
89
+ range_end="closed",
90
+ range_start="closed",
91
+ ),
92
+ ) -> Iterable[TDataItem]:
93
+ yield from client.fetch_metrics(create_client(), updated.start_value)
94
+
95
+ @dlt.resource(write_disposition="replace", primary_key="id")
96
+ def tags() -> Iterable[TAnyDateTime]:
97
+ yield from client.fetch_tag(create_client())
98
+
99
+ @dlt.resource(write_disposition="replace", primary_key="id")
100
+ def coupons() -> Iterable[TAnyDateTime]:
101
+ yield from client.fetch_coupons(create_client())
102
+
103
+ @dlt.resource(write_disposition="merge", primary_key="id", name="catalog-variants")
104
+ def catalog_variants(
105
+ updated=dlt.sources.incremental(
106
+ "updated",
107
+ start_date_obj.isoformat(),
108
+ range_end="closed",
109
+ range_start="closed",
110
+ ),
111
+ ) -> Iterable[TDataItem]:
112
+ yield from client.fetch_catalog_variant(create_client(), updated.start_value)
113
+
114
+ @dlt.resource(
115
+ write_disposition="merge", primary_key="id", name="catalog-categories"
116
+ )
117
+ def catalog_categories(
118
+ updated=dlt.sources.incremental(
119
+ "updated",
120
+ start_date_obj.isoformat(),
121
+ range_end="closed",
122
+ range_start="closed",
123
+ ),
124
+ ) -> Iterable[TDataItem]:
125
+ yield from client.fetch_catalog_categories(create_client(), updated.start_value)
126
+
127
+ @dlt.resource(write_disposition="merge", primary_key="id", name="catalog-items")
128
+ def catalog_items(
129
+ updated=dlt.sources.incremental(
130
+ "updated",
131
+ start_date_obj.isoformat(),
132
+ range_end="closed",
133
+ range_start="closed",
134
+ ),
135
+ ) -> Iterable[TDataItem]:
136
+ yield from client.fetch_catalog_item(create_client(), updated.start_value)
137
+
138
+ @dlt.resource(write_disposition="merge", primary_key="id", parallelized=True)
139
+ def forms(
140
+ updated_at=dlt.sources.incremental(
141
+ "updated_at",
142
+ start_date_obj.isoformat(),
143
+ range_end="closed",
144
+ range_start="closed",
145
+ ),
146
+ ) -> Iterable[TDataItem]:
147
+ intervals = split_date_range(
148
+ pendulum.parse(updated_at.start_value), pendulum.now()
149
+ )
150
+
151
+ for start, end in intervals:
152
+ yield lambda s=start, e=end: client.fetch_forms(create_client(), s, e)
153
+
154
+ @dlt.resource(write_disposition="merge", primary_key="id")
155
+ def lists(
156
+ updated=dlt.sources.incremental(
157
+ "updated",
158
+ start_date_obj.isoformat(),
159
+ range_end="closed",
160
+ range_start="closed",
161
+ ),
162
+ ) -> Iterable[TDataItem]:
163
+ yield from client.fetch_lists(create_client(), updated.start_value)
164
+
165
+ @dlt.resource(write_disposition="merge", primary_key="id", parallelized=True)
166
+ def images(
167
+ updated_at=dlt.sources.incremental(
168
+ "updated_at",
169
+ start_date_obj.isoformat(),
170
+ range_end="closed",
171
+ range_start="closed",
172
+ ),
173
+ ) -> Iterable[TDataItem]:
174
+ intervals = split_date_range(
175
+ pendulum.parse(updated_at.start_value), pendulum.now()
176
+ )
177
+ for start, end in intervals:
178
+ yield lambda s=start, e=end: client.fetch_images(create_client(), s, e)
179
+
180
+ @dlt.resource(write_disposition="merge", primary_key="id")
181
+ def segments(
182
+ updated=dlt.sources.incremental(
183
+ "updated",
184
+ start_date_obj.isoformat(),
185
+ range_end="closed",
186
+ range_start="closed",
187
+ ),
188
+ ) -> Iterable[TDataItem]:
189
+ yield from client.fetch_segments(create_client(), updated.start_value)
190
+
191
+ @dlt.resource(write_disposition="merge", primary_key="id", parallelized=True)
192
+ def flows(
193
+ updated=dlt.sources.incremental(
194
+ "updated",
195
+ start_date_obj.isoformat(),
196
+ range_end="closed",
197
+ range_start="closed",
198
+ ),
199
+ ) -> Iterable[TDataItem]:
200
+ intervals = split_date_range(
201
+ pendulum.parse(updated.start_value), pendulum.now()
202
+ )
203
+ for start, end in intervals:
204
+ yield lambda s=start, e=end: client.fetch_flows(create_client(), s, e)
205
+
206
+ @dlt.resource(write_disposition="merge", primary_key="id", parallelized=True)
207
+ def templates(
208
+ updated=dlt.sources.incremental(
209
+ "updated",
210
+ start_date_obj.isoformat(),
211
+ range_end="closed",
212
+ range_start="closed",
213
+ ),
214
+ ) -> Iterable[TDataItem]:
215
+ intervals = split_date_range(
216
+ pendulum.parse(updated.start_value), pendulum.now()
217
+ )
218
+ for start, end in intervals:
219
+ yield lambda s=start, e=end: client.fetch_templates(create_client(), s, e)
220
+
221
+ return (
222
+ events,
223
+ profiles,
224
+ campaigns,
225
+ metrics,
226
+ tags,
227
+ coupons,
228
+ catalog_variants,
229
+ catalog_categories,
230
+ catalog_items,
231
+ forms,
232
+ lists,
233
+ images,
234
+ segments,
235
+ flows,
236
+ templates,
237
+ )
@@ -0,0 +1,212 @@
1
+ from urllib.parse import urlencode
2
+
3
+ import pendulum
4
+ import requests
5
+
6
+ BASE_URL = "https://a.klaviyo.com/api"
7
+
8
+
9
+ class KlaviyoClient:
10
+ def __init__(self, api_key: str):
11
+ self.api_key = api_key
12
+
13
+ def __get_headers(self):
14
+ return {
15
+ "Authorization": f"Klaviyo-API-Key {self.api_key}",
16
+ "accept": "application/json",
17
+ "revision": "2024-07-15",
18
+ }
19
+
20
+ def _flatten_attributes(self, items: list):
21
+ for event in items:
22
+ if "attributes" not in event:
23
+ continue
24
+
25
+ for attribute_key in event["attributes"]:
26
+ event[attribute_key] = event["attributes"][attribute_key]
27
+
28
+ del event["attributes"]
29
+ return items
30
+
31
+ def _fetch_pages(
32
+ self, session: requests.Session, url: str, flat: bool = True
33
+ ) -> list:
34
+ all_items = []
35
+ while True:
36
+ response = session.get(url=url, headers=self.__get_headers())
37
+ result = response.json()
38
+ items = result.get("data", [])
39
+
40
+ if flat:
41
+ items = self._flatten_attributes(items)
42
+
43
+ all_items.extend(items)
44
+ nextURL = result.get("links", {}).get("next")
45
+ if nextURL is None:
46
+ break
47
+
48
+ url = nextURL
49
+
50
+ return all_items
51
+
52
+ def fetch_events(
53
+ self,
54
+ session: requests.Session,
55
+ start_date: str,
56
+ end_date: str,
57
+ ):
58
+ print(f"Fetching events for {start_date} to {end_date}")
59
+ url = f"{BASE_URL}/events/?sort=-datetime&filter=and(greater-or-equal(datetime,{start_date}),less-than(datetime,{end_date}))"
60
+ return self._fetch_pages(session, url)
61
+
62
+ def fetch_metrics(
63
+ self,
64
+ session: requests.Session,
65
+ last_updated: str,
66
+ ):
67
+ print(f"Fetching metrics since {last_updated}")
68
+ url = f"{BASE_URL}/metrics"
69
+ items = self._fetch_pages(session, url)
70
+
71
+ last_updated_obj = pendulum.parse(last_updated)
72
+ for item in items:
73
+ updated_at = pendulum.parse(item["updated"])
74
+ if updated_at > last_updated_obj:
75
+ yield item
76
+
77
+ def fetch_profiles(
78
+ self,
79
+ session: requests.Session,
80
+ start_date: str,
81
+ end_date: str,
82
+ ):
83
+ pendulum_start_date = pendulum.parse(start_date)
84
+ pendulum_start_date = pendulum_start_date.subtract(seconds=1)
85
+ url = f"{BASE_URL}/profiles/?sort=updated&filter=and(greater-than(updated,{pendulum_start_date.isoformat()}),less-than(updated,{end_date}))"
86
+ return self._fetch_pages(session, url)
87
+
88
+ def fetch_campaigns(
89
+ self,
90
+ session: requests.Session,
91
+ start_date: str,
92
+ end_date: str,
93
+ campaign_type: str,
94
+ ):
95
+ print(f"Fetching {campaign_type} campaigns for {start_date} to {end_date}")
96
+
97
+ base_url = f"{BASE_URL}/campaigns/"
98
+ params = {
99
+ "sort": "updated_at",
100
+ "filter": f"and(equals(messages.channel,'{campaign_type}'),greater-or-equal(updated_at,{start_date}),less-than(updated_at,{end_date}))",
101
+ }
102
+ url = f"{base_url}?{urlencode(params)}"
103
+ pages = self._fetch_pages(session, url)
104
+ for page in pages:
105
+ page["campaign_type"] = campaign_type
106
+
107
+ return pages
108
+
109
+ def fetch_tag(self, session: requests.Session):
110
+ url = f"{BASE_URL}/tags"
111
+ return self._fetch_pages(session, url, False)
112
+
113
+ def fetch_catalog_variant(
114
+ self,
115
+ session: requests.Session,
116
+ last_updated: str,
117
+ ):
118
+ url = f"{BASE_URL}/catalog-variants"
119
+ items = self._fetch_pages(session, url)
120
+ last_updated_obj = pendulum.parse(last_updated)
121
+
122
+ for item in items:
123
+ updated_at = pendulum.parse(item["updated"])
124
+ if updated_at > last_updated_obj:
125
+ yield item
126
+
127
+ def fetch_coupons(self, session: requests.Session):
128
+ url = f"{BASE_URL}/coupons"
129
+ return self._fetch_pages(session, url, False)
130
+
131
+ def fetch_catalog_categories(
132
+ self,
133
+ session: requests.Session,
134
+ last_updated: str,
135
+ ):
136
+ url = f"{BASE_URL}/catalog-categories"
137
+ items = self._fetch_pages(session, url)
138
+ last_updated_obj = pendulum.parse(last_updated)
139
+
140
+ for item in items:
141
+ updated_at = pendulum.parse(item["updated"])
142
+ if updated_at > last_updated_obj:
143
+ yield item
144
+
145
+ def fetch_catalog_item(
146
+ self,
147
+ session: requests.Session,
148
+ last_updated: str,
149
+ ):
150
+ url = f"{BASE_URL}/catalog-items"
151
+ items = self._fetch_pages(session, url)
152
+ last_updated_obj = pendulum.parse(last_updated)
153
+
154
+ for item in items:
155
+ updated_at = pendulum.parse(item["updated"])
156
+ if updated_at > last_updated_obj:
157
+ yield item
158
+
159
+ def fetch_forms(
160
+ self,
161
+ session: requests.Session,
162
+ start_date: str,
163
+ end_date: str,
164
+ ):
165
+ print(f"Fetching forms for {start_date} to {end_date}")
166
+ url = f"{BASE_URL}/forms/?sort=-updated_at&filter=and(greater-or-equal(updated_at,{start_date}),less-than(updated_at,{end_date}))"
167
+ return self._fetch_pages(session, url)
168
+
169
+ def fetch_lists(
170
+ self,
171
+ session: requests.Session,
172
+ updated_date: str,
173
+ ):
174
+ # https://a.klaviyo.com/api/lists/?sort=-updated&filter=greater-than(updated,2024-02-01 00:00:00+00:00)
175
+ url = f"{BASE_URL}/lists/?sort=-updated&filter=greater-than(updated,{updated_date})"
176
+ return self._fetch_pages(session, url)
177
+
178
+ def fetch_images(self, session: requests.Session, start_date: str, end_date: str):
179
+ # https://a.klaviyo.com/api/images/?sort=-updated_at&filter=greater-or-equal(updated_at,2024-06-01 00:00:00+00:00),less-than(updated_at,2024-09-01 00:00:00+00:00)
180
+ url = f"{BASE_URL}/images/?sort=-updated_at&filter=and(greater-or-equal(updated_at,{start_date}),less-than(updated_at,{end_date}))"
181
+ return self._fetch_pages(session, url)
182
+
183
+ def fetch_segments(
184
+ self,
185
+ session: requests.Session,
186
+ updated_date: str,
187
+ ):
188
+ # https://a.klaviyo.com/api/segments/?sort=-updated&filter=greater-than(updated,2024-04-01 00:00:00+00:00)
189
+ url = f"{BASE_URL}/segments/?sort=-updated&filter=greater-than(updated,{updated_date})"
190
+ print("url", url)
191
+ return self._fetch_pages(session, url)
192
+
193
+ def fetch_flows(
194
+ self,
195
+ session: requests.Session,
196
+ start_date: str,
197
+ end_date: str,
198
+ ):
199
+ print(f"Fetching events for {start_date} to {end_date}")
200
+ # https://a.klaviyo.com/api/flows/?sort=-updated&filter=and(greater-or-equal(updated,2024-06-01 00:00:00+00:00),less-than(updated,2024-09-01 00:00:00+00:00))
201
+ url = f"{BASE_URL}/flows/?sort=-updated&filter=and(greater-or-equal(updated,{start_date}),less-than(updated,{end_date}))"
202
+ return self._fetch_pages(session, url)
203
+
204
+ def fetch_templates(
205
+ self,
206
+ session: requests.Session,
207
+ start_date: str,
208
+ end_date: str,
209
+ ):
210
+ # https://a.klaviyo.com/api/templates/?sort=-updated&filter=and(greater-or-equal(updated,2024-06-01 00:00:00+00:00),less-than(updated,2024-09-01 00:00:00+00:00))
211
+ url = f"{BASE_URL}/templates/?sort=-updated&filter=and(greater-or-equal(updated,{start_date}),less-than(updated,{end_date}))"
212
+ return self._fetch_pages(session, url)
@@ -0,0 +1,19 @@
1
+ from typing import List
2
+
3
+ import pendulum
4
+
5
+
6
+ def split_date_range(
7
+ start_date: pendulum.DateTime, end_date: pendulum.DateTime
8
+ ) -> List[tuple]:
9
+ interval = "days"
10
+ if (end_date - start_date).days <= 1:
11
+ interval = "hours"
12
+
13
+ intervals = []
14
+ current = start_date
15
+ while current < end_date:
16
+ next_date = min(current.add(**{interval: 1}), end_date)
17
+ intervals.append((current.isoformat(), next_date.isoformat()))
18
+ current = next_date
19
+ return intervals