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,411 @@
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 typing import Dict, List
16
+
17
+ from . import field
18
+
19
+
20
+ class Report:
21
+ resource: str
22
+ unfilterable: bool
23
+ dimensions: List[str]
24
+ metrics: List[str]
25
+ segments: List[str]
26
+
27
+ def __init__(
28
+ self,
29
+ resource: str = "",
30
+ dimensions: List[str] = [],
31
+ metrics: List[str] = [],
32
+ segments: List[str] = [],
33
+ unfilterable: bool = False,
34
+ ):
35
+ self.resource = resource
36
+ self.dimensions = dimensions
37
+ self.metrics = metrics
38
+ self.segments = segments
39
+ self.unfilterable = unfilterable
40
+
41
+ def primary_keys(self) -> List[str]:
42
+ dims = self.dimensions + self.segments
43
+ filtered = []
44
+ for d in dims:
45
+ filtered.append(field.to_column(d))
46
+ return filtered
47
+
48
+ @classmethod
49
+ def from_spec(cls, spec: str):
50
+ """
51
+ Parse a report specification string into a Report object.
52
+ The expected format is:
53
+ daily:{resource}:{dimensions}:{metrics}:{customer_ids}
54
+
55
+ Example:
56
+ daily:ad_group_ad_asset_view:ad_group.id,campaign.id:clicks,conversions:1234567890,9876543210
57
+
58
+ customer_ids is optional - if not provided, falls back to URI hostname.
59
+ """
60
+ colon_count = spec.count(":")
61
+ if colon_count not in (3, 4):
62
+ raise ValueError(
63
+ "Invalid report specification format. Expected daily:{resource}:{dimensions}:{metrics} or daily:{resource}:{dimensions}:{metrics}:{customer_ids}"
64
+ )
65
+
66
+ parts = spec.split(":")
67
+ _, resource, dimensions, metrics = parts[:4]
68
+ customer_ids_str = parts[4] if len(parts) > 4 else None
69
+
70
+ if dimensions.strip() == "":
71
+ raise ValueError("Dimensions are required")
72
+ if metrics.strip() == "":
73
+ raise ValueError("Metrics are required")
74
+
75
+ report = cls()
76
+ report.segments = ["segments.date"]
77
+ report.resource = resource
78
+ report.dimensions = [
79
+ d for d in map(cls._parse_dimension, dimensions.split(","))
80
+ ]
81
+ report.metrics = [m for m in map(cls._parse_metric, metrics.split(","))]
82
+
83
+ customer_ids = None
84
+ if customer_ids_str and customer_ids_str.strip() != "":
85
+ customer_ids = [cid.strip() for cid in customer_ids_str.split(",")]
86
+
87
+ return report, customer_ids
88
+
89
+ @classmethod
90
+ def _parse_dimension(self, dim: str):
91
+ dim = dim.strip()
92
+ if dim.count(".") == 0:
93
+ raise ValueError("Invalid dimension format. Expected {resource}.{field}")
94
+ if dim.startswith("segments."):
95
+ raise ValueError(
96
+ "Invalid dimension format. Segments are not allowed in dimensions."
97
+ )
98
+ return dim
99
+
100
+ @classmethod
101
+ def _parse_metric(self, metric: str):
102
+ metric = metric.strip()
103
+ if not metric.startswith("metrics."):
104
+ metric = f"metrics.{metric.strip()}"
105
+ return metric
106
+
107
+
108
+ BUILTIN_REPORTS: Dict[str, Report] = {
109
+ "account_report_daily": Report(
110
+ resource="campaign",
111
+ dimensions=[
112
+ "customer.id",
113
+ ],
114
+ metrics=[
115
+ "metrics.active_view_impressions",
116
+ "metrics.active_view_measurability",
117
+ "metrics.active_view_measurable_cost_micros",
118
+ "metrics.active_view_measurable_impressions",
119
+ "metrics.active_view_viewability",
120
+ "metrics.clicks",
121
+ "metrics.conversions",
122
+ "metrics.conversions_value",
123
+ "metrics.cost_micros",
124
+ "metrics.impressions",
125
+ "metrics.interactions",
126
+ "metrics.interaction_event_types",
127
+ "metrics.view_through_conversions",
128
+ ],
129
+ segments=[
130
+ "segments.date",
131
+ "segments.ad_network_type",
132
+ "segments.device",
133
+ ],
134
+ ),
135
+ "campaign_report_daily": Report(
136
+ resource="campaign",
137
+ dimensions=[
138
+ "campaign.id",
139
+ "customer.id",
140
+ ],
141
+ metrics=[
142
+ "metrics.active_view_impressions",
143
+ "metrics.active_view_measurability",
144
+ "metrics.active_view_measurable_cost_micros",
145
+ "metrics.active_view_measurable_impressions",
146
+ "metrics.active_view_viewability",
147
+ "metrics.clicks",
148
+ "metrics.conversions",
149
+ "metrics.conversions_value",
150
+ "metrics.cost_micros",
151
+ "metrics.impressions",
152
+ "metrics.interactions",
153
+ "metrics.interaction_event_types",
154
+ "metrics.view_through_conversions",
155
+ ],
156
+ segments=[
157
+ "segments.date",
158
+ "segments.ad_network_type",
159
+ "segments.device",
160
+ ],
161
+ ),
162
+ "ad_group_report_daily": Report(
163
+ resource="ad_group",
164
+ dimensions=[
165
+ "ad_group.id",
166
+ "customer.id",
167
+ "campaign.id",
168
+ ],
169
+ metrics=[
170
+ "metrics.active_view_impressions",
171
+ "metrics.active_view_measurability",
172
+ "metrics.active_view_measurable_cost_micros",
173
+ "metrics.active_view_measurable_impressions",
174
+ "metrics.active_view_viewability",
175
+ "metrics.clicks",
176
+ "metrics.conversions",
177
+ "metrics.conversions_value",
178
+ "metrics.cost_micros",
179
+ "metrics.impressions",
180
+ "metrics.interactions",
181
+ "metrics.interaction_event_types",
182
+ "metrics.view_through_conversions",
183
+ ],
184
+ segments=[
185
+ "segments.date",
186
+ "segments.ad_network_type",
187
+ "segments.device",
188
+ ],
189
+ ),
190
+ "ad_report_daily": Report(
191
+ resource="ad_group_ad",
192
+ dimensions=[
193
+ "ad_group.id",
194
+ "ad_group_ad.ad.id",
195
+ "customer.id",
196
+ "campaign.id",
197
+ ],
198
+ segments=[
199
+ "segments.date",
200
+ "segments.ad_network_type",
201
+ "segments.device",
202
+ ],
203
+ metrics=[
204
+ "metrics.active_view_impressions",
205
+ "metrics.active_view_measurability",
206
+ "metrics.active_view_measurable_cost_micros",
207
+ "metrics.active_view_measurable_impressions",
208
+ "metrics.active_view_viewability",
209
+ "metrics.clicks",
210
+ "metrics.conversions",
211
+ "metrics.conversions_value",
212
+ "metrics.cost_micros",
213
+ "metrics.impressions",
214
+ "metrics.interactions",
215
+ "metrics.interaction_event_types",
216
+ "metrics.view_through_conversions",
217
+ ],
218
+ ),
219
+ "audience_report_daily": Report(
220
+ resource="ad_group_audience_view",
221
+ dimensions=[
222
+ "ad_group.id",
223
+ "customer.id",
224
+ "campaign.id",
225
+ "ad_group_criterion.criterion_id",
226
+ ],
227
+ segments=[
228
+ "segments.date",
229
+ "segments.ad_network_type",
230
+ "segments.device",
231
+ ],
232
+ metrics=[
233
+ "metrics.active_view_impressions",
234
+ "metrics.active_view_measurability",
235
+ "metrics.active_view_measurable_cost_micros",
236
+ "metrics.active_view_measurable_impressions",
237
+ "metrics.active_view_viewability",
238
+ "metrics.clicks",
239
+ "metrics.conversions",
240
+ "metrics.conversions_value",
241
+ "metrics.cost_micros",
242
+ "metrics.impressions",
243
+ "metrics.interactions",
244
+ "metrics.interaction_event_types",
245
+ "metrics.view_through_conversions",
246
+ ],
247
+ ),
248
+ "keyword_report_daily": Report(
249
+ resource="keyword_view",
250
+ dimensions=[
251
+ "ad_group.id",
252
+ "customer.id",
253
+ "campaign.id",
254
+ "ad_group_criterion.criterion_id",
255
+ ],
256
+ segments=[
257
+ "segments.date",
258
+ "segments.ad_network_type",
259
+ "segments.device",
260
+ ],
261
+ metrics=[
262
+ "metrics.active_view_impressions",
263
+ "metrics.active_view_measurability",
264
+ "metrics.active_view_measurable_cost_micros",
265
+ "metrics.active_view_measurable_impressions",
266
+ "metrics.active_view_viewability",
267
+ "metrics.clicks",
268
+ "metrics.conversions",
269
+ "metrics.conversions_value",
270
+ "metrics.cost_micros",
271
+ "metrics.impressions",
272
+ "metrics.interactions",
273
+ "metrics.interaction_event_types",
274
+ "metrics.view_through_conversions",
275
+ ],
276
+ ),
277
+ "click_report_daily": Report(
278
+ resource="click_view",
279
+ dimensions=[
280
+ "click_view.gclid",
281
+ "customer.id",
282
+ "ad_group.id",
283
+ "campaign.id",
284
+ "segments.date",
285
+ ],
286
+ metrics=[
287
+ "metrics.clicks",
288
+ ],
289
+ ),
290
+ "landing_page_report_daily": Report(
291
+ resource="landing_page_view",
292
+ dimensions=[
293
+ "landing_page_view.unexpanded_final_url",
294
+ "landing_page_view.resource_name",
295
+ "customer.id",
296
+ "ad_group.id",
297
+ "campaign.id",
298
+ "segments.date",
299
+ ],
300
+ metrics=[
301
+ "metrics.average_cpc",
302
+ "metrics.clicks",
303
+ "metrics.cost_micros",
304
+ "metrics.ctr",
305
+ "metrics.impressions",
306
+ "metrics.mobile_friendly_clicks_percentage",
307
+ "metrics.speed_score",
308
+ "metrics.valid_accelerated_mobile_pages_clicks_percentage",
309
+ ],
310
+ ),
311
+ "search_keyword_report_daily": Report(
312
+ resource="keyword_view",
313
+ dimensions=[
314
+ "customer.id",
315
+ "ad_group.id",
316
+ "campaign.id",
317
+ "keyword_view.resource_name",
318
+ "ad_group_criterion.criterion_id",
319
+ "segments.date",
320
+ ],
321
+ metrics=[
322
+ "metrics.absolute_top_impression_percentage",
323
+ "metrics.average_cpc",
324
+ "metrics.average_cpm",
325
+ "metrics.clicks",
326
+ "metrics.conversions_from_interactions_rate",
327
+ "metrics.conversions_value",
328
+ "metrics.cost_micros",
329
+ "metrics.ctr",
330
+ "metrics.impressions",
331
+ "metrics.top_impression_percentage",
332
+ "metrics.view_through_conversions",
333
+ ],
334
+ ),
335
+ "search_term_report_daily": Report(
336
+ resource="search_term_view",
337
+ dimensions=[
338
+ "customer.id",
339
+ "ad_group.id",
340
+ "campaign.id",
341
+ "search_term_view.resource_name",
342
+ "search_term_view.search_term",
343
+ "search_term_view.status",
344
+ "segments.date",
345
+ ],
346
+ segments=[
347
+ "segments.search_term_match_type",
348
+ ],
349
+ metrics=[
350
+ "metrics.absolute_top_impression_percentage",
351
+ "metrics.average_cpc",
352
+ "metrics.clicks",
353
+ "metrics.conversions",
354
+ "metrics.conversions_from_interactions_rate",
355
+ "metrics.conversions_from_interactions_value_per_interaction",
356
+ "metrics.cost_micros",
357
+ "metrics.ctr",
358
+ "metrics.impressions",
359
+ "metrics.top_impression_percentage",
360
+ "metrics.view_through_conversions",
361
+ ],
362
+ ),
363
+ "lead_form_submission_data_report_daily": Report(
364
+ resource="lead_form_submission_data",
365
+ dimensions=[
366
+ "lead_form_submission_data.gclid",
367
+ "lead_form_submission_data.submission_date_time",
368
+ "lead_form_submission_data.lead_form_submission_fields",
369
+ "lead_form_submission_data.custom_lead_form_submission_fields",
370
+ "lead_form_submission_data.resource_name",
371
+ "customer.id",
372
+ "ad_group_ad.ad.id",
373
+ "ad_group.id",
374
+ "campaign.id",
375
+ ],
376
+ unfilterable=True,
377
+ ),
378
+ "local_services_lead_report_daily": Report(
379
+ resource="local_services_lead",
380
+ dimensions=[
381
+ "customer.id",
382
+ "local_services_lead.creation_date_time",
383
+ "local_services_lead.contact_details",
384
+ "local_services_lead.credit_details.credit_state",
385
+ "local_services_lead.credit_details.credit_state_last_update_date_time",
386
+ "local_services_lead.lead_charged",
387
+ "local_services_lead.lead_status",
388
+ "local_services_lead.lead_type",
389
+ "local_services_lead.locale",
390
+ "local_services_lead.note.description",
391
+ "local_services_lead.note.edit_date_time",
392
+ "local_services_lead.service_id",
393
+ ],
394
+ unfilterable=True,
395
+ ),
396
+ "local_services_lead_conversations_report_daily": Report(
397
+ resource="local_services_lead_conversation",
398
+ dimensions=[
399
+ "customer.id",
400
+ "local_services_lead_conversation.id",
401
+ "local_services_lead_conversation.event_date_time",
402
+ "local_services_lead_conversation.conversation_channel",
403
+ "local_services_lead_conversation.message_details.attachment_urls",
404
+ "local_services_lead_conversation.message_details.text",
405
+ "local_services_lead_conversation.participant_type",
406
+ "local_services_lead_conversation.phone_call_details.call_duration_millis",
407
+ "local_services_lead_conversation.phone_call_details.call_recording_url",
408
+ ],
409
+ unfilterable=True,
410
+ ),
411
+ }
@@ -0,0 +1,184 @@
1
+ from omniload.src.google_ads import extract_fields
2
+
3
+ FIELD_PATHS = [
4
+ "customer.id",
5
+ "campaign.id",
6
+ "campaign.name",
7
+ "ad_group.id",
8
+ "ad_group.name",
9
+ "ad_group_ad.resource_name",
10
+ "ad_group_ad.status",
11
+ "ad_group_ad.ad.id",
12
+ "ad_group_ad.ad.type",
13
+ "ad_group_ad.ad.name",
14
+ "ad_group_ad.ad.final_urls",
15
+ "ad_group_ad.ad.responsive_search_ad.path1",
16
+ "ad_group_ad.ad.responsive_search_ad.path2",
17
+ "ad_group_ad.ad.responsive_display_ad.long_headline",
18
+ "ad_group_ad.ad.responsive_display_ad.call_to_action_text",
19
+ "ad_group_ad.ad.responsive_display_ad.format_setting",
20
+ "ad_group_ad.ad.responsive_display_ad.headlines",
21
+ "ad_group_ad.ad.responsive_display_ad.descriptions",
22
+ ]
23
+
24
+ EXPECTED_KEYS = {path.replace(".", "_") for path in FIELD_PATHS}
25
+
26
+
27
+ def test_extract_fields():
28
+ display_ad_data = {
29
+ "customer": {
30
+ "resource_name": "customers/1234567890",
31
+ "id": "1234567890",
32
+ },
33
+ "campaign": {
34
+ "resource_name": "customers/1234567890/campaigns/111",
35
+ "name": "Summer Display Campaign",
36
+ "id": "111",
37
+ },
38
+ "ad_group": {
39
+ "resource_name": "customers/1234567890/adGroups/222",
40
+ "id": "222",
41
+ "name": "Display Ad Group",
42
+ },
43
+ "ad_group_ad": {
44
+ "resource_name": "customers/1234567890/adGroupAds/222~333",
45
+ "status": "ENABLED",
46
+ "ad": {
47
+ "type": "RESPONSIVE_DISPLAY_AD",
48
+ "responsive_display_ad": {
49
+ "headlines": [{"text": "Buy Now"}],
50
+ "long_headline": {"text": "Great deals on summer products"},
51
+ "descriptions": [
52
+ {"text": "Free shipping on all orders"},
53
+ {"text": "Limited time offer"},
54
+ ],
55
+ "format_setting": "ALL_FORMATS",
56
+ },
57
+ "resource_name": "customers/1234567890/ads/333",
58
+ "id": "333",
59
+ "final_urls": ["https://example.com/summer"],
60
+ },
61
+ },
62
+ }
63
+
64
+ call_ad_data = {
65
+ "customer": {
66
+ "resource_name": "customers/1234567890",
67
+ "id": "1234567890",
68
+ },
69
+ "campaign": {
70
+ "resource_name": "customers/1234567890/campaigns/444",
71
+ "name": "Call Campaign",
72
+ "id": "444",
73
+ },
74
+ "ad_group": {
75
+ "resource_name": "customers/1234567890/adGroups/555",
76
+ "id": "555",
77
+ "name": "Call Ad Group",
78
+ },
79
+ "ad_group_ad": {
80
+ "resource_name": "customers/1234567890/adGroupAds/555~666",
81
+ "status": "PAUSED",
82
+ "ad": {
83
+ "type": "CALL_AD",
84
+ "resource_name": "customers/1234567890/ads/666",
85
+ "id": "666",
86
+ "final_urls": ["https://example.com/contact"],
87
+ },
88
+ },
89
+ }
90
+
91
+ search_ad_data = {
92
+ "customer": {
93
+ "resource_name": "customers/1234567890",
94
+ "id": "1234567890",
95
+ },
96
+ "campaign": {
97
+ "resource_name": "customers/1234567890/campaigns/777",
98
+ "name": "Search Campaign",
99
+ "id": "777",
100
+ },
101
+ "ad_group": {
102
+ "resource_name": "customers/1234567890/adGroups/888",
103
+ "id": "888",
104
+ "name": "Search Ad Group",
105
+ },
106
+ "ad_group_ad": {
107
+ "resource_name": "customers/1234567890/adGroupAds/888~999",
108
+ "status": "PAUSED",
109
+ "ad": {
110
+ "type": "RESPONSIVE_SEARCH_AD",
111
+ "responsive_search_ad": {
112
+ "path1": "deals",
113
+ "path2": "today",
114
+ },
115
+ "resource_name": "customers/1234567890/ads/999",
116
+ "id": "999",
117
+ "final_urls": ["https://example.com/search"],
118
+ },
119
+ },
120
+ }
121
+
122
+ for row_data in [display_ad_data, call_ad_data, search_ad_data]:
123
+ result = extract_fields(row_data, FIELD_PATHS)
124
+ assert set(result.keys()) == EXPECTED_KEYS
125
+
126
+ # display ad
127
+ display = extract_fields(display_ad_data, FIELD_PATHS)
128
+ assert display["customer_id"] == "1234567890"
129
+ assert display["campaign_id"] == "111"
130
+ assert display["campaign_name"] == "Summer Display Campaign"
131
+ assert display["ad_group_id"] == "222"
132
+ assert display["ad_group_name"] == "Display Ad Group"
133
+ assert (
134
+ display["ad_group_ad_resource_name"]
135
+ == "customers/1234567890/adGroupAds/222~333"
136
+ )
137
+ assert display["ad_group_ad_status"] == "ENABLED"
138
+ assert display["ad_group_ad_ad_id"] == "333"
139
+ assert display["ad_group_ad_ad_type"] == "RESPONSIVE_DISPLAY_AD"
140
+ assert display["ad_group_ad_ad_final_urls"] == ["https://example.com/summer"]
141
+ assert display["ad_group_ad_ad_responsive_display_ad_headlines"] == [
142
+ {"text": "Buy Now"}
143
+ ]
144
+ assert display["ad_group_ad_ad_responsive_display_ad_long_headline"] == {
145
+ "text": "Great deals on summer products"
146
+ }
147
+ assert display["ad_group_ad_ad_responsive_display_ad_descriptions"] == [
148
+ {"text": "Free shipping on all orders"},
149
+ {"text": "Limited time offer"},
150
+ ]
151
+ assert (
152
+ display["ad_group_ad_ad_responsive_display_ad_format_setting"] == "ALL_FORMATS"
153
+ )
154
+ assert display["ad_group_ad_ad_responsive_search_ad_path1"] is None
155
+ assert display["ad_group_ad_ad_responsive_search_ad_path2"] is None
156
+ assert display["ad_group_ad_ad_name"] is None
157
+
158
+ # call ad
159
+ call = extract_fields(call_ad_data, FIELD_PATHS)
160
+ assert call["customer_id"] == "1234567890"
161
+ assert call["campaign_name"] == "Call Campaign"
162
+ assert call["ad_group_ad_status"] == "PAUSED"
163
+ assert call["ad_group_ad_ad_type"] == "CALL_AD"
164
+ assert call["ad_group_ad_ad_id"] == "666"
165
+ assert call["ad_group_ad_ad_final_urls"] == ["https://example.com/contact"]
166
+ assert call["ad_group_ad_ad_responsive_display_ad_headlines"] is None
167
+ assert call["ad_group_ad_ad_responsive_display_ad_descriptions"] is None
168
+ assert call["ad_group_ad_ad_responsive_display_ad_long_headline"] is None
169
+ assert call["ad_group_ad_ad_responsive_search_ad_path1"] is None
170
+ assert call["ad_group_ad_ad_responsive_search_ad_path2"] is None
171
+ assert call["ad_group_ad_ad_name"] is None
172
+
173
+ # search ad
174
+ search = extract_fields(search_ad_data, FIELD_PATHS)
175
+ assert search["customer_id"] == "1234567890"
176
+ assert search["campaign_name"] == "Search Campaign"
177
+ assert search["ad_group_ad_ad_type"] == "RESPONSIVE_SEARCH_AD"
178
+ assert search["ad_group_ad_ad_responsive_search_ad_path1"] == "deals"
179
+ assert search["ad_group_ad_ad_responsive_search_ad_path2"] == "today"
180
+ assert search["ad_group_ad_ad_final_urls"] == ["https://example.com/search"]
181
+ assert search["ad_group_ad_ad_responsive_display_ad_headlines"] is None
182
+ assert search["ad_group_ad_ad_responsive_display_ad_descriptions"] is None
183
+ assert search["ad_group_ad_ad_responsive_display_ad_long_headline"] is None
184
+ assert search["ad_group_ad_ad_name"] is None