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,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
|