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,403 @@
1
+ # Copyright 2022-2025 ScaleVector
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Loads campaigns, ads sets, ads, leads and insight data from Facebook Marketing API"""
16
+
17
+ from typing import Iterator, Sequence
18
+
19
+ import dlt
20
+ from dlt.common import pendulum
21
+ from dlt.common.time import ensure_pendulum_datetime
22
+ from dlt.common.typing import TDataItems
23
+ from dlt.sources import DltResource
24
+ from facebook_business.adobjects.ad import Ad
25
+
26
+ from .helpers import (
27
+ execute_job,
28
+ get_ads_account,
29
+ get_data_chunked,
30
+ process_report_item,
31
+ )
32
+ from .settings import (
33
+ ALL_ACTION_ATTRIBUTION_WINDOWS,
34
+ ALL_ACTION_BREAKDOWNS,
35
+ DEFAULT_AD_FIELDS,
36
+ DEFAULT_ADCREATIVE_FIELDS,
37
+ DEFAULT_ADSET_FIELDS,
38
+ DEFAULT_CAMPAIGN_FIELDS,
39
+ DEFAULT_LEAD_FIELDS,
40
+ INSIGHT_FIELDS_TYPES,
41
+ TInsightsLevels,
42
+ )
43
+
44
+
45
+ def _create_facebook_insights_resource(
46
+ accounts: list,
47
+ start_date,
48
+ end_date,
49
+ dimensions: Sequence[str],
50
+ fields: Sequence[str],
51
+ level,
52
+ action_breakdowns: Sequence[str],
53
+ batch_size: int,
54
+ time_increment_days: int,
55
+ action_attribution_windows: Sequence[str],
56
+ insights_max_async_sleep_seconds: int,
57
+ insights_max_wait_to_finish_seconds: int,
58
+ insights_max_wait_to_start_seconds: int,
59
+ ):
60
+ """Create a facebook_insights resource for the given accounts."""
61
+ columns = {}
62
+ for field in fields:
63
+ if field in INSIGHT_FIELDS_TYPES:
64
+ columns[field] = INSIGHT_FIELDS_TYPES[field]
65
+
66
+ @dlt.resource(
67
+ write_disposition="merge",
68
+ merge_key="date_start",
69
+ columns=columns,
70
+ )
71
+ def facebook_insights(
72
+ date_start: dlt.sources.incremental[str] = dlt.sources.incremental(
73
+ "date_start",
74
+ initial_value=ensure_pendulum_datetime(start_date).start_of("day").date(),
75
+ end_value=ensure_pendulum_datetime(end_date).end_of("day").date()
76
+ if end_date
77
+ else None,
78
+ range_end="closed",
79
+ range_start="closed",
80
+ ),
81
+ ) -> Iterator[TDataItems]:
82
+ current_start_date = date_start.last_value
83
+ if date_start.end_value:
84
+ end_date_val = pendulum.instance(date_start.end_value)
85
+ current_end_date = (
86
+ end_date_val
87
+ if isinstance(end_date_val, pendulum.Date)
88
+ else end_date_val.date()
89
+ )
90
+ else:
91
+ current_end_date = pendulum.now().date()
92
+
93
+ while current_start_date <= current_end_date:
94
+ query = {
95
+ "level": level,
96
+ "action_breakdowns": list(action_breakdowns),
97
+ "breakdowns": dimensions,
98
+ "limit": batch_size,
99
+ "fields": fields,
100
+ "time_increment": time_increment_days,
101
+ "action_attribution_windows": list(action_attribution_windows),
102
+ "time_ranges": [
103
+ {
104
+ "since": current_start_date.to_date_string(),
105
+ "until": current_start_date.add(
106
+ days=time_increment_days - 1
107
+ ).to_date_string(),
108
+ }
109
+ ],
110
+ }
111
+ for account in accounts:
112
+ job = execute_job(
113
+ account.get_insights(params=query, is_async=True),
114
+ insights_max_async_sleep_seconds=insights_max_async_sleep_seconds,
115
+ insights_max_wait_to_finish_seconds=insights_max_wait_to_finish_seconds,
116
+ insights_max_wait_to_start_seconds=insights_max_wait_to_start_seconds,
117
+ )
118
+ output = list(map(process_report_item, job.get_result()))
119
+ yield output
120
+ current_start_date = current_start_date.add(days=time_increment_days)
121
+
122
+ return facebook_insights
123
+
124
+
125
+ @dlt.source(name="facebook_ads", max_table_nesting=0)
126
+ def facebook_ads_source(
127
+ account_id: str | list[str] = dlt.config.value,
128
+ access_token: str = dlt.secrets.value,
129
+ chunk_size: int = 50,
130
+ request_timeout: float = 300.0,
131
+ app_api_version: str = "v20.0",
132
+ interval_start=None,
133
+ interval_end=None,
134
+ ) -> Sequence[DltResource]:
135
+ """Returns a list of resources to load campaigns, ad sets, ads, creatives and ad leads data from Facebook Marketing API.
136
+
137
+ All the resources have `replace` write disposition by default and define primary keys. Resources are parametrized and allow the user
138
+ to change the set of fields that will be loaded from the API and the object statuses that will be loaded. See the demonstration script for details.
139
+
140
+ You can convert the source into merge resource to keep the deleted objects. Currently Marketing API does not return deleted objects. See the demo script.
141
+
142
+ We also provide a transformation `enrich_ad_objects` that you can add to any of the resources to get additional data per object via `object.get_api`
143
+
144
+ Args:
145
+ account_id (str | list[str], optional): Account id(s) associated with ad manager. Can be a single ID or a list of IDs. See README.md
146
+ access_token (str, optional): Access token associated with the Business Facebook App. See README.md
147
+ chunk_size (int, optional): A size of the page and batch request. You may need to decrease it if you request a lot of fields. Defaults to 50.
148
+ request_timeout (float, optional): Connection timeout. Defaults to 300.0.
149
+ app_api_version(str, optional): A version of the facebook api required by the app for which the access tokens were issued ie. 'v17.0'. Defaults to the facebook_business library default version
150
+
151
+ Returns:
152
+ Sequence[DltResource]: campaigns, ads, ad_sets, ad_creatives, leads
153
+ """
154
+ # Convert interval dates to strings if they are datetime objects
155
+ if interval_start is not None and hasattr(interval_start, "strftime"):
156
+ interval_start = interval_start.strftime("%Y-%m-%d")
157
+ if interval_end is not None and hasattr(interval_end, "strftime"):
158
+ interval_end = interval_end.strftime("%Y-%m-%d")
159
+
160
+ account_ids = account_id if isinstance(account_id, list) else [account_id]
161
+ accounts = [
162
+ get_ads_account(acc_id, access_token, request_timeout, app_api_version)
163
+ for acc_id in account_ids
164
+ ]
165
+
166
+ def filter_by_end_date(records, time_field):
167
+ if not interval_end:
168
+ return records
169
+ return [
170
+ r
171
+ for r in records
172
+ if r.get(time_field) and r[time_field][:10] <= interval_end
173
+ ]
174
+
175
+ @dlt.resource(primary_key="id", write_disposition="merge")
176
+ def campaigns(
177
+ fields: Sequence[str] = DEFAULT_CAMPAIGN_FIELDS,
178
+ states: Sequence[str] = None,
179
+ updated_at: dlt.sources.incremental[str] = dlt.sources.incremental(
180
+ "updated_time", initial_value=interval_start
181
+ ),
182
+ ) -> Iterator[TDataItems]:
183
+ for account in accounts:
184
+ for chunk in get_data_chunked(
185
+ account.get_campaigns, fields, states, chunk_size
186
+ ):
187
+ filtered = filter_by_end_date(chunk, "updated_time")
188
+ if filtered:
189
+ yield filtered
190
+
191
+ @dlt.resource(primary_key="id", write_disposition="merge")
192
+ def ads(
193
+ fields: Sequence[str] = DEFAULT_AD_FIELDS,
194
+ states: Sequence[str] = None,
195
+ ) -> Iterator[TDataItems]:
196
+ updated_since = None
197
+ if interval_start:
198
+ updated_since = int(pendulum.parse(interval_start).timestamp())
199
+ for account in accounts:
200
+ for chunk in get_data_chunked(
201
+ account.get_ads, fields, states, chunk_size, updated_since=updated_since
202
+ ):
203
+ filtered = filter_by_end_date(chunk, "updated_time")
204
+ if filtered:
205
+ yield filtered
206
+
207
+ @dlt.resource(primary_key="id", write_disposition="merge")
208
+ def ad_sets(
209
+ fields: Sequence[str] = DEFAULT_ADSET_FIELDS,
210
+ states: Sequence[str] = None,
211
+ ) -> Iterator[TDataItems]:
212
+ updated_since = None
213
+ if interval_start:
214
+ updated_since = int(pendulum.parse(interval_start).timestamp())
215
+ for account in accounts:
216
+ for chunk in get_data_chunked(
217
+ account.get_ad_sets,
218
+ fields,
219
+ states,
220
+ chunk_size,
221
+ updated_since=updated_since,
222
+ ):
223
+ filtered = filter_by_end_date(chunk, "updated_time")
224
+ if filtered:
225
+ yield filtered
226
+
227
+ @dlt.transformer(
228
+ primary_key=["id", "created_time"], write_disposition="merge", selected=True
229
+ )
230
+ def leads(
231
+ items: TDataItems,
232
+ fields: Sequence[str] = DEFAULT_LEAD_FIELDS,
233
+ states: Sequence[str] = None,
234
+ updated_at: dlt.sources.incremental[str] = dlt.sources.incremental(
235
+ "created_time", initial_value=None
236
+ ),
237
+ ) -> Iterator[TDataItems]:
238
+ for item in items:
239
+ ad = Ad(item["id"])
240
+ for chunk in get_data_chunked(ad.get_leads, fields, states, chunk_size):
241
+ filtered = filter_by_end_date(chunk, "created_time")
242
+ if filtered:
243
+ yield filtered
244
+
245
+ @dlt.resource(primary_key="id", write_disposition="replace")
246
+ def ad_creatives(
247
+ fields: Sequence[str] = DEFAULT_ADCREATIVE_FIELDS,
248
+ states: Sequence[str] = None,
249
+ ) -> Iterator[TDataItems]:
250
+ for account in accounts:
251
+ for chunk in get_data_chunked(
252
+ account.get_ad_creatives, fields, states, chunk_size
253
+ ):
254
+ yield chunk
255
+
256
+ return campaigns, ads, ad_sets, ad_creatives, ads | leads
257
+
258
+
259
+ @dlt.source(name="facebook_ads", max_table_nesting=0)
260
+ def facebook_insights_source(
261
+ account_id: str = dlt.config.value,
262
+ access_token: str = dlt.secrets.value,
263
+ initial_load_past_days: int = 1,
264
+ dimensions: Sequence[str] = None,
265
+ fields: Sequence[str] = None,
266
+ time_increment_days: int = 1,
267
+ action_breakdowns: Sequence[str] = ALL_ACTION_BREAKDOWNS,
268
+ level: TInsightsLevels | None = "ad",
269
+ action_attribution_windows: Sequence[str] = ALL_ACTION_ATTRIBUTION_WINDOWS,
270
+ batch_size: int = 50,
271
+ request_timeout: int = 300,
272
+ app_api_version: str = None,
273
+ start_date: pendulum.DateTime | None = None,
274
+ end_date: pendulum.DateTime | None = None,
275
+ insights_max_wait_to_finish_seconds: int = 60 * 60 * 4,
276
+ insights_max_wait_to_start_seconds: int = 60 * 30,
277
+ insights_max_async_sleep_seconds: int = 20,
278
+ ) -> DltResource:
279
+ """Incrementally loads insight reports with defined granularity level, fields, breakdowns etc.
280
+
281
+ By default, the reports are generated one by one for each day, starting with today - attribution_window_days_lag. On subsequent runs, only the reports
282
+ from the last report date until today are loaded (incremental load). The reports from last 7 days (`attribution_window_days_lag`) are refreshed on each load to
283
+ account for changes during attribution window.
284
+
285
+ Mind that each report is a job and takes some time to execute.
286
+
287
+ Args:
288
+ account_id: str = dlt.config.value,
289
+ access_token: str = dlt.secrets.value,
290
+ initial_load_past_days (int, optional): How many past days (starting from today) to intially load. Defaults to 30.
291
+ fields (Sequence[str], optional): A list of fields to include in each reports. Note that `breakdowns` option adds fields automatically. Defaults to DEFAULT_INSIGHT_FIELDS.
292
+ attribution_window_days_lag (int, optional): Attribution window in days. The reports in attribution window are refreshed on each run.. Defaults to 7.
293
+ time_increment_days (int, optional): The report aggregation window in days. use 7 for weekly aggregation. Defaults to 1.
294
+ breakdowns (TInsightsBreakdownOptions, optional): A presents with common aggregations. See settings.py for details. Defaults to "ads_insights_age_and_gender".
295
+ action_breakdowns (Sequence[str], optional): Action aggregation types. See settings.py for details. Defaults to ALL_ACTION_BREAKDOWNS.
296
+ level (TInsightsLevels, optional): The granularity level. Defaults to "ad".
297
+ action_attribution_windows (Sequence[str], optional): Attribution windows for actions. Defaults to ALL_ACTION_ATTRIBUTION_WINDOWS.
298
+ batch_size (int, optional): Page size when reading data from particular report. Defaults to 50.
299
+ request_timeout (int, optional): Connection timeout. Defaults to 300.
300
+ app_api_version(str, optional): A version of the facebook api required by the app for which the access tokens were issued ie. 'v17.0'. Defaults to the facebook_business library default version
301
+
302
+ Returns:
303
+ DltResource: facebook_insights
304
+
305
+ """
306
+ account = get_ads_account(
307
+ account_id, access_token, request_timeout, app_api_version
308
+ )
309
+
310
+ if start_date is None:
311
+ start_date = pendulum.today().subtract(days=initial_load_past_days)
312
+
313
+ if dimensions is None:
314
+ dimensions = []
315
+ if fields is None:
316
+ fields = []
317
+
318
+ return _create_facebook_insights_resource(
319
+ accounts=[account],
320
+ start_date=start_date,
321
+ end_date=end_date,
322
+ dimensions=dimensions,
323
+ fields=fields,
324
+ level=level,
325
+ action_breakdowns=action_breakdowns,
326
+ batch_size=batch_size,
327
+ time_increment_days=time_increment_days,
328
+ action_attribution_windows=action_attribution_windows,
329
+ insights_max_async_sleep_seconds=insights_max_async_sleep_seconds,
330
+ insights_max_wait_to_finish_seconds=insights_max_wait_to_finish_seconds,
331
+ insights_max_wait_to_start_seconds=insights_max_wait_to_start_seconds,
332
+ )
333
+
334
+
335
+ @dlt.source(name="facebook_ads", max_table_nesting=0)
336
+ def facebook_insights_with_account_ids_source(
337
+ account_ids: list[str],
338
+ access_token: str = dlt.secrets.value,
339
+ initial_load_past_days: int = 1,
340
+ dimensions: Sequence[str] = None,
341
+ fields: Sequence[str] = None,
342
+ time_increment_days: int = 1,
343
+ action_breakdowns: Sequence[str] = ALL_ACTION_BREAKDOWNS,
344
+ level: TInsightsLevels | None = "ad",
345
+ action_attribution_windows: Sequence[str] = ALL_ACTION_ATTRIBUTION_WINDOWS,
346
+ batch_size: int = 50,
347
+ request_timeout: int = 300,
348
+ app_api_version: str = None,
349
+ start_date: pendulum.DateTime | None = None,
350
+ end_date: pendulum.DateTime | None = None,
351
+ insights_max_wait_to_finish_seconds: int = 60 * 60 * 4,
352
+ insights_max_wait_to_start_seconds: int = 60 * 30,
353
+ insights_max_async_sleep_seconds: int = 20,
354
+ ) -> DltResource:
355
+ """Incrementally loads insight reports for multiple account IDs.
356
+
357
+ Args:
358
+ account_ids (list[str]): List of account IDs to fetch insights for.
359
+ access_token (str): Access token associated with the Business Facebook App.
360
+ initial_load_past_days (int, optional): How many past days to initially load. Defaults to 1.
361
+ dimensions (Sequence[str], optional): Breakdown dimensions.
362
+ fields (Sequence[str], optional): Fields to include in reports.
363
+ time_increment_days (int, optional): Report aggregation window in days. Defaults to 1.
364
+ action_breakdowns (Sequence[str], optional): Action aggregation types.
365
+ level (TInsightsLevels, optional): Granularity level. Defaults to "ad".
366
+ action_attribution_windows (Sequence[str], optional): Attribution windows for actions.
367
+ batch_size (int, optional): Page size. Defaults to 50.
368
+ request_timeout (int, optional): Connection timeout. Defaults to 300.
369
+ app_api_version (str, optional): Facebook API version.
370
+ start_date: Start date for insights.
371
+ end_date: End date for insights.
372
+
373
+ Returns:
374
+ DltResource: facebook_insights
375
+ """
376
+ accounts = [
377
+ get_ads_account(acc_id, access_token, request_timeout, app_api_version)
378
+ for acc_id in account_ids
379
+ ]
380
+
381
+ if start_date is None:
382
+ start_date = pendulum.today().subtract(days=initial_load_past_days)
383
+
384
+ if dimensions is None:
385
+ dimensions = []
386
+ if fields is None:
387
+ fields = []
388
+
389
+ return _create_facebook_insights_resource(
390
+ accounts=accounts,
391
+ start_date=start_date,
392
+ end_date=end_date,
393
+ dimensions=dimensions,
394
+ fields=fields,
395
+ level=level,
396
+ action_breakdowns=action_breakdowns,
397
+ batch_size=batch_size,
398
+ time_increment_days=time_increment_days,
399
+ action_attribution_windows=action_attribution_windows,
400
+ insights_max_async_sleep_seconds=insights_max_async_sleep_seconds,
401
+ insights_max_wait_to_finish_seconds=insights_max_wait_to_finish_seconds,
402
+ insights_max_wait_to_start_seconds=insights_max_wait_to_start_seconds,
403
+ )
@@ -0,0 +1,19 @@
1
+ # Copyright 2022-2025 ScaleVector
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from dlt.extract.exceptions import DltResourceException
16
+
17
+
18
+ class InsightsJobTimeout(DltResourceException):
19
+ pass