airbyte-agent-facebook-marketing 0.1.0__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.
- airbyte_agent_facebook_marketing/__init__.py +221 -0
- airbyte_agent_facebook_marketing/_vendored/__init__.py +1 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/__init__.py +82 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/auth_strategies.py +1171 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/auth_template.py +135 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/cloud_utils/client.py +213 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/connector_model_loader.py +1120 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/constants.py +78 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/exceptions.py +23 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/executor/__init__.py +31 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/executor/hosted_executor.py +201 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/executor/local_executor.py +1854 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/executor/models.py +202 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/extensions.py +693 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/__init__.py +37 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/adapters/__init__.py +9 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/adapters/httpx_adapter.py +251 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/config.py +98 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/exceptions.py +119 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/protocols.py +114 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/response.py +104 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/http_client.py +693 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/introspection.py +481 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/logging/__init__.py +11 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/logging/logger.py +273 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/logging/types.py +93 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/observability/__init__.py +11 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/observability/config.py +179 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/observability/models.py +19 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/observability/redactor.py +81 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/observability/session.py +103 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/performance/__init__.py +6 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/performance/instrumentation.py +57 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/performance/metrics.py +93 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/__init__.py +75 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/base.py +201 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/components.py +244 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/connector.py +120 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/extensions.py +301 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/operations.py +156 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/security.py +236 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/secrets.py +182 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/telemetry/__init__.py +10 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/telemetry/config.py +32 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/telemetry/events.py +59 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/telemetry/tracker.py +155 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/types.py +270 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/utils.py +60 -0
- airbyte_agent_facebook_marketing/_vendored/connector_sdk/validation.py +848 -0
- airbyte_agent_facebook_marketing/connector.py +1553 -0
- airbyte_agent_facebook_marketing/connector_model.py +3120 -0
- airbyte_agent_facebook_marketing/models.py +814 -0
- airbyte_agent_facebook_marketing/types.py +1957 -0
- airbyte_agent_facebook_marketing-0.1.0.dist-info/METADATA +148 -0
- airbyte_agent_facebook_marketing-0.1.0.dist-info/RECORD +57 -0
- airbyte_agent_facebook_marketing-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,1553 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Facebook-Marketing connector.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import inspect
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from functools import wraps
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, Mapping, TypeVar, overload
|
|
12
|
+
try:
|
|
13
|
+
from typing import Literal
|
|
14
|
+
except ImportError:
|
|
15
|
+
from typing_extensions import Literal
|
|
16
|
+
|
|
17
|
+
from .connector_model import FacebookMarketingConnectorModel
|
|
18
|
+
from ._vendored.connector_sdk.introspection import describe_entities, generate_tool_description
|
|
19
|
+
from .types import (
|
|
20
|
+
AdCreativesListParams,
|
|
21
|
+
AdSetsGetParams,
|
|
22
|
+
AdSetsListParams,
|
|
23
|
+
AdsGetParams,
|
|
24
|
+
AdsInsightsListParams,
|
|
25
|
+
AdsListParams,
|
|
26
|
+
CampaignsGetParams,
|
|
27
|
+
CampaignsListParams,
|
|
28
|
+
CustomConversionsListParams,
|
|
29
|
+
ImagesListParams,
|
|
30
|
+
VideosListParams,
|
|
31
|
+
AirbyteSearchParams,
|
|
32
|
+
CampaignsSearchFilter,
|
|
33
|
+
CampaignsSearchQuery,
|
|
34
|
+
AdSetsSearchFilter,
|
|
35
|
+
AdSetsSearchQuery,
|
|
36
|
+
AdsSearchFilter,
|
|
37
|
+
AdsSearchQuery,
|
|
38
|
+
AdCreativesSearchFilter,
|
|
39
|
+
AdCreativesSearchQuery,
|
|
40
|
+
AdsInsightsSearchFilter,
|
|
41
|
+
AdsInsightsSearchQuery,
|
|
42
|
+
CustomConversionsSearchFilter,
|
|
43
|
+
CustomConversionsSearchQuery,
|
|
44
|
+
ImagesSearchFilter,
|
|
45
|
+
ImagesSearchQuery,
|
|
46
|
+
VideosSearchFilter,
|
|
47
|
+
VideosSearchQuery,
|
|
48
|
+
)
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from .models import FacebookMarketingAuthConfig
|
|
51
|
+
# Import response models and envelope models at runtime
|
|
52
|
+
from .models import (
|
|
53
|
+
FacebookMarketingCheckResult,
|
|
54
|
+
FacebookMarketingExecuteResult,
|
|
55
|
+
FacebookMarketingExecuteResultWithMeta,
|
|
56
|
+
CampaignsListResult,
|
|
57
|
+
AdSetsListResult,
|
|
58
|
+
AdsListResult,
|
|
59
|
+
AdCreativesListResult,
|
|
60
|
+
AdsInsightsListResult,
|
|
61
|
+
CustomConversionsListResult,
|
|
62
|
+
ImagesListResult,
|
|
63
|
+
VideosListResult,
|
|
64
|
+
Ad,
|
|
65
|
+
AdCreative,
|
|
66
|
+
AdSet,
|
|
67
|
+
AdsInsight,
|
|
68
|
+
Campaign,
|
|
69
|
+
CustomConversion,
|
|
70
|
+
Image,
|
|
71
|
+
Video,
|
|
72
|
+
AirbyteSearchHit,
|
|
73
|
+
AirbyteSearchResult,
|
|
74
|
+
CampaignsSearchData,
|
|
75
|
+
CampaignsSearchResult,
|
|
76
|
+
AdSetsSearchData,
|
|
77
|
+
AdSetsSearchResult,
|
|
78
|
+
AdsSearchData,
|
|
79
|
+
AdsSearchResult,
|
|
80
|
+
AdCreativesSearchData,
|
|
81
|
+
AdCreativesSearchResult,
|
|
82
|
+
AdsInsightsSearchData,
|
|
83
|
+
AdsInsightsSearchResult,
|
|
84
|
+
CustomConversionsSearchData,
|
|
85
|
+
CustomConversionsSearchResult,
|
|
86
|
+
ImagesSearchData,
|
|
87
|
+
ImagesSearchResult,
|
|
88
|
+
VideosSearchData,
|
|
89
|
+
VideosSearchResult,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# TypeVar for decorator type preservation
|
|
93
|
+
_F = TypeVar("_F", bound=Callable[..., Any])
|
|
94
|
+
|
|
95
|
+
DEFAULT_MAX_OUTPUT_CHARS = 50_000 # ~50KB default, configurable per-tool
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _raise_output_too_large(message: str) -> None:
|
|
99
|
+
try:
|
|
100
|
+
from pydantic_ai import ModelRetry # type: ignore[import-not-found]
|
|
101
|
+
except Exception as exc:
|
|
102
|
+
raise RuntimeError(message) from exc
|
|
103
|
+
raise ModelRetry(message)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _check_output_size(result: Any, max_chars: int | None, tool_name: str) -> Any:
|
|
107
|
+
if max_chars is None or max_chars <= 0:
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
serialized = json.dumps(result, default=str)
|
|
112
|
+
except (TypeError, ValueError):
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
if len(serialized) > max_chars:
|
|
116
|
+
truncated_preview = serialized[:500] + "..." if len(serialized) > 500 else serialized
|
|
117
|
+
_raise_output_too_large(
|
|
118
|
+
f"Tool '{tool_name}' output too large ({len(serialized):,} chars, limit {max_chars:,}). "
|
|
119
|
+
"Please narrow your query by: using the 'fields' parameter to select only needed fields, "
|
|
120
|
+
"adding filters, or reducing the 'limit'. "
|
|
121
|
+
f"Preview: {truncated_preview}"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class FacebookMarketingConnector:
|
|
130
|
+
"""
|
|
131
|
+
Type-safe Facebook-Marketing API connector.
|
|
132
|
+
|
|
133
|
+
Auto-generated from OpenAPI specification with full type safety.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
connector_name = "facebook-marketing"
|
|
137
|
+
connector_version = "1.0.1"
|
|
138
|
+
vendored_sdk_version = "0.1.0" # Version of vendored connector-sdk
|
|
139
|
+
|
|
140
|
+
# Map of (entity, action) -> needs_envelope for envelope wrapping decision
|
|
141
|
+
_ENVELOPE_MAP = {
|
|
142
|
+
("campaigns", "list"): True,
|
|
143
|
+
("ad_sets", "list"): True,
|
|
144
|
+
("ads", "list"): True,
|
|
145
|
+
("ad_creatives", "list"): True,
|
|
146
|
+
("ads_insights", "list"): True,
|
|
147
|
+
("custom_conversions", "list"): True,
|
|
148
|
+
("images", "list"): True,
|
|
149
|
+
("videos", "list"): True,
|
|
150
|
+
("campaigns", "get"): None,
|
|
151
|
+
("ad_sets", "get"): None,
|
|
152
|
+
("ads", "get"): None,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# Map of (entity, action) -> {python_param_name: api_param_name}
|
|
156
|
+
# Used to convert snake_case TypedDict keys to API parameter names in execute()
|
|
157
|
+
_PARAM_MAP = {
|
|
158
|
+
('campaigns', 'list'): {'account_id': 'account_id', 'fields': 'fields', 'limit': 'limit', 'after': 'after'},
|
|
159
|
+
('ad_sets', 'list'): {'account_id': 'account_id', 'fields': 'fields', 'limit': 'limit', 'after': 'after'},
|
|
160
|
+
('ads', 'list'): {'account_id': 'account_id', 'fields': 'fields', 'limit': 'limit', 'after': 'after'},
|
|
161
|
+
('ad_creatives', 'list'): {'account_id': 'account_id', 'fields': 'fields', 'limit': 'limit', 'after': 'after'},
|
|
162
|
+
('ads_insights', 'list'): {'account_id': 'account_id', 'fields': 'fields', 'date_preset': 'date_preset', 'time_range': 'time_range', 'level': 'level', 'limit': 'limit', 'after': 'after'},
|
|
163
|
+
('custom_conversions', 'list'): {'account_id': 'account_id', 'fields': 'fields', 'limit': 'limit', 'after': 'after'},
|
|
164
|
+
('images', 'list'): {'account_id': 'account_id', 'fields': 'fields', 'limit': 'limit', 'after': 'after'},
|
|
165
|
+
('videos', 'list'): {'account_id': 'account_id', 'fields': 'fields', 'limit': 'limit', 'after': 'after'},
|
|
166
|
+
('campaigns', 'get'): {'campaign_id': 'campaign_id', 'fields': 'fields'},
|
|
167
|
+
('ad_sets', 'get'): {'adset_id': 'adset_id', 'fields': 'fields'},
|
|
168
|
+
('ads', 'get'): {'ad_id': 'ad_id', 'fields': 'fields'},
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
auth_config: FacebookMarketingAuthConfig | None = None,
|
|
174
|
+
external_user_id: str | None = None,
|
|
175
|
+
airbyte_client_id: str | None = None,
|
|
176
|
+
airbyte_client_secret: str | None = None,
|
|
177
|
+
on_token_refresh: Any | None = None ):
|
|
178
|
+
"""
|
|
179
|
+
Initialize a new facebook-marketing connector instance.
|
|
180
|
+
|
|
181
|
+
Supports both local and hosted execution modes:
|
|
182
|
+
- Local mode: Provide `auth_config` for direct API calls
|
|
183
|
+
- Hosted mode: Provide `external_user_id`, `airbyte_client_id`, and `airbyte_client_secret` for hosted execution
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
auth_config: Typed authentication configuration (required for local mode)
|
|
187
|
+
external_user_id: External user ID (required for hosted mode)
|
|
188
|
+
airbyte_client_id: Airbyte OAuth client ID (required for hosted mode)
|
|
189
|
+
airbyte_client_secret: Airbyte OAuth client secret (required for hosted mode)
|
|
190
|
+
on_token_refresh: Optional callback for OAuth2 token refresh persistence.
|
|
191
|
+
Called with new_tokens dict when tokens are refreshed. Can be sync or async.
|
|
192
|
+
Example: lambda tokens: save_to_database(tokens)
|
|
193
|
+
Examples:
|
|
194
|
+
# Local mode (direct API calls)
|
|
195
|
+
connector = FacebookMarketingConnector(auth_config=FacebookMarketingAuthConfig(access_token="...", account_id="..."))
|
|
196
|
+
# Hosted mode (executed on Airbyte cloud)
|
|
197
|
+
connector = FacebookMarketingConnector(
|
|
198
|
+
external_user_id="user-123",
|
|
199
|
+
airbyte_client_id="client_abc123",
|
|
200
|
+
airbyte_client_secret="secret_xyz789"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Local mode with OAuth2 token refresh callback
|
|
204
|
+
def save_tokens(new_tokens: dict) -> None:
|
|
205
|
+
# Persist updated tokens to your storage (file, database, etc.)
|
|
206
|
+
with open("tokens.json", "w") as f:
|
|
207
|
+
json.dump(new_tokens, f)
|
|
208
|
+
|
|
209
|
+
connector = FacebookMarketingConnector(
|
|
210
|
+
auth_config=FacebookMarketingAuthConfig(access_token="...", refresh_token="..."),
|
|
211
|
+
on_token_refresh=save_tokens
|
|
212
|
+
)
|
|
213
|
+
"""
|
|
214
|
+
# Hosted mode: external_user_id, airbyte_client_id, and airbyte_client_secret provided
|
|
215
|
+
if external_user_id and airbyte_client_id and airbyte_client_secret:
|
|
216
|
+
from ._vendored.connector_sdk.executor import HostedExecutor
|
|
217
|
+
self._executor = HostedExecutor(
|
|
218
|
+
external_user_id=external_user_id,
|
|
219
|
+
airbyte_client_id=airbyte_client_id,
|
|
220
|
+
airbyte_client_secret=airbyte_client_secret,
|
|
221
|
+
connector_definition_id=str(FacebookMarketingConnectorModel.id),
|
|
222
|
+
)
|
|
223
|
+
else:
|
|
224
|
+
# Local mode: auth_config required
|
|
225
|
+
if not auth_config:
|
|
226
|
+
raise ValueError(
|
|
227
|
+
"Either provide (external_user_id, airbyte_client_id, airbyte_client_secret) for hosted mode "
|
|
228
|
+
"or auth_config for local mode"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
from ._vendored.connector_sdk.executor import LocalExecutor
|
|
232
|
+
|
|
233
|
+
# Build config_values dict from server variables
|
|
234
|
+
config_values = None
|
|
235
|
+
|
|
236
|
+
self._executor = LocalExecutor(
|
|
237
|
+
model=FacebookMarketingConnectorModel,
|
|
238
|
+
auth_config=auth_config.model_dump() if auth_config else None,
|
|
239
|
+
config_values=config_values,
|
|
240
|
+
on_token_refresh=on_token_refresh
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Update base_url with server variables if provided
|
|
244
|
+
|
|
245
|
+
# Initialize entity query objects
|
|
246
|
+
self.campaigns = CampaignsQuery(self)
|
|
247
|
+
self.ad_sets = AdSetsQuery(self)
|
|
248
|
+
self.ads = AdsQuery(self)
|
|
249
|
+
self.ad_creatives = AdCreativesQuery(self)
|
|
250
|
+
self.ads_insights = AdsInsightsQuery(self)
|
|
251
|
+
self.custom_conversions = CustomConversionsQuery(self)
|
|
252
|
+
self.images = ImagesQuery(self)
|
|
253
|
+
self.videos = VideosQuery(self)
|
|
254
|
+
|
|
255
|
+
# ===== TYPED EXECUTE METHOD (Recommended Interface) =====
|
|
256
|
+
|
|
257
|
+
@overload
|
|
258
|
+
async def execute(
|
|
259
|
+
self,
|
|
260
|
+
entity: Literal["campaigns"],
|
|
261
|
+
action: Literal["list"],
|
|
262
|
+
params: "CampaignsListParams"
|
|
263
|
+
) -> "CampaignsListResult": ...
|
|
264
|
+
|
|
265
|
+
@overload
|
|
266
|
+
async def execute(
|
|
267
|
+
self,
|
|
268
|
+
entity: Literal["ad_sets"],
|
|
269
|
+
action: Literal["list"],
|
|
270
|
+
params: "AdSetsListParams"
|
|
271
|
+
) -> "AdSetsListResult": ...
|
|
272
|
+
|
|
273
|
+
@overload
|
|
274
|
+
async def execute(
|
|
275
|
+
self,
|
|
276
|
+
entity: Literal["ads"],
|
|
277
|
+
action: Literal["list"],
|
|
278
|
+
params: "AdsListParams"
|
|
279
|
+
) -> "AdsListResult": ...
|
|
280
|
+
|
|
281
|
+
@overload
|
|
282
|
+
async def execute(
|
|
283
|
+
self,
|
|
284
|
+
entity: Literal["ad_creatives"],
|
|
285
|
+
action: Literal["list"],
|
|
286
|
+
params: "AdCreativesListParams"
|
|
287
|
+
) -> "AdCreativesListResult": ...
|
|
288
|
+
|
|
289
|
+
@overload
|
|
290
|
+
async def execute(
|
|
291
|
+
self,
|
|
292
|
+
entity: Literal["ads_insights"],
|
|
293
|
+
action: Literal["list"],
|
|
294
|
+
params: "AdsInsightsListParams"
|
|
295
|
+
) -> "AdsInsightsListResult": ...
|
|
296
|
+
|
|
297
|
+
@overload
|
|
298
|
+
async def execute(
|
|
299
|
+
self,
|
|
300
|
+
entity: Literal["custom_conversions"],
|
|
301
|
+
action: Literal["list"],
|
|
302
|
+
params: "CustomConversionsListParams"
|
|
303
|
+
) -> "CustomConversionsListResult": ...
|
|
304
|
+
|
|
305
|
+
@overload
|
|
306
|
+
async def execute(
|
|
307
|
+
self,
|
|
308
|
+
entity: Literal["images"],
|
|
309
|
+
action: Literal["list"],
|
|
310
|
+
params: "ImagesListParams"
|
|
311
|
+
) -> "ImagesListResult": ...
|
|
312
|
+
|
|
313
|
+
@overload
|
|
314
|
+
async def execute(
|
|
315
|
+
self,
|
|
316
|
+
entity: Literal["videos"],
|
|
317
|
+
action: Literal["list"],
|
|
318
|
+
params: "VideosListParams"
|
|
319
|
+
) -> "VideosListResult": ...
|
|
320
|
+
|
|
321
|
+
@overload
|
|
322
|
+
async def execute(
|
|
323
|
+
self,
|
|
324
|
+
entity: Literal["campaigns"],
|
|
325
|
+
action: Literal["get"],
|
|
326
|
+
params: "CampaignsGetParams"
|
|
327
|
+
) -> "Campaign": ...
|
|
328
|
+
|
|
329
|
+
@overload
|
|
330
|
+
async def execute(
|
|
331
|
+
self,
|
|
332
|
+
entity: Literal["ad_sets"],
|
|
333
|
+
action: Literal["get"],
|
|
334
|
+
params: "AdSetsGetParams"
|
|
335
|
+
) -> "AdSet": ...
|
|
336
|
+
|
|
337
|
+
@overload
|
|
338
|
+
async def execute(
|
|
339
|
+
self,
|
|
340
|
+
entity: Literal["ads"],
|
|
341
|
+
action: Literal["get"],
|
|
342
|
+
params: "AdsGetParams"
|
|
343
|
+
) -> "Ad": ...
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@overload
|
|
347
|
+
async def execute(
|
|
348
|
+
self,
|
|
349
|
+
entity: str,
|
|
350
|
+
action: Literal["list", "get", "search"],
|
|
351
|
+
params: Mapping[str, Any]
|
|
352
|
+
) -> FacebookMarketingExecuteResult[Any] | FacebookMarketingExecuteResultWithMeta[Any, Any] | Any: ...
|
|
353
|
+
|
|
354
|
+
async def execute(
|
|
355
|
+
self,
|
|
356
|
+
entity: str,
|
|
357
|
+
action: Literal["list", "get", "search"],
|
|
358
|
+
params: Mapping[str, Any] | None = None
|
|
359
|
+
) -> Any:
|
|
360
|
+
"""
|
|
361
|
+
Execute an entity operation with full type safety.
|
|
362
|
+
|
|
363
|
+
This is the recommended interface for blessed connectors as it:
|
|
364
|
+
- Uses the same signature as non-blessed connectors
|
|
365
|
+
- Provides full IDE autocomplete for entity/action/params
|
|
366
|
+
- Makes migration from generic to blessed connectors seamless
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
entity: Entity name (e.g., "customers")
|
|
370
|
+
action: Operation action (e.g., "create", "get", "list")
|
|
371
|
+
params: Operation parameters (typed based on entity+action)
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Typed response based on the operation
|
|
375
|
+
|
|
376
|
+
Example:
|
|
377
|
+
customer = await connector.execute(
|
|
378
|
+
entity="customers",
|
|
379
|
+
action="get",
|
|
380
|
+
params={"id": "cus_123"}
|
|
381
|
+
)
|
|
382
|
+
"""
|
|
383
|
+
from ._vendored.connector_sdk.executor import ExecutionConfig
|
|
384
|
+
|
|
385
|
+
# Remap parameter names from snake_case (TypedDict keys) to API parameter names
|
|
386
|
+
resolved_params = dict(params) if params is not None else None
|
|
387
|
+
if resolved_params:
|
|
388
|
+
param_map = self._PARAM_MAP.get((entity, action), {})
|
|
389
|
+
if param_map:
|
|
390
|
+
resolved_params = {param_map.get(k, k): v for k, v in resolved_params.items()}
|
|
391
|
+
|
|
392
|
+
# Use ExecutionConfig for both local and hosted executors
|
|
393
|
+
config = ExecutionConfig(
|
|
394
|
+
entity=entity,
|
|
395
|
+
action=action,
|
|
396
|
+
params=resolved_params
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
result = await self._executor.execute(config)
|
|
400
|
+
|
|
401
|
+
if not result.success:
|
|
402
|
+
raise RuntimeError(f"Execution failed: {result.error}")
|
|
403
|
+
|
|
404
|
+
# Check if this operation has extractors configured
|
|
405
|
+
has_extractors = self._ENVELOPE_MAP.get((entity, action), False)
|
|
406
|
+
|
|
407
|
+
if has_extractors:
|
|
408
|
+
# With extractors - return Pydantic envelope with data and meta
|
|
409
|
+
if result.meta is not None:
|
|
410
|
+
return FacebookMarketingExecuteResultWithMeta[Any, Any](
|
|
411
|
+
data=result.data,
|
|
412
|
+
meta=result.meta
|
|
413
|
+
)
|
|
414
|
+
else:
|
|
415
|
+
return FacebookMarketingExecuteResult[Any](data=result.data)
|
|
416
|
+
else:
|
|
417
|
+
# No extractors - return raw response data
|
|
418
|
+
return result.data
|
|
419
|
+
|
|
420
|
+
# ===== HEALTH CHECK METHOD =====
|
|
421
|
+
|
|
422
|
+
async def check(self) -> FacebookMarketingCheckResult:
|
|
423
|
+
"""
|
|
424
|
+
Perform a health check to verify connectivity and credentials.
|
|
425
|
+
|
|
426
|
+
Executes a lightweight list operation (limit=1) to validate that
|
|
427
|
+
the connector can communicate with the API and credentials are valid.
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
FacebookMarketingCheckResult with status ("healthy" or "unhealthy") and optional error message
|
|
431
|
+
|
|
432
|
+
Example:
|
|
433
|
+
result = await connector.check()
|
|
434
|
+
if result.status == "healthy":
|
|
435
|
+
print("Connection verified!")
|
|
436
|
+
else:
|
|
437
|
+
print(f"Check failed: {result.error}")
|
|
438
|
+
"""
|
|
439
|
+
result = await self._executor.check()
|
|
440
|
+
|
|
441
|
+
if result.success and isinstance(result.data, dict):
|
|
442
|
+
return FacebookMarketingCheckResult(
|
|
443
|
+
status=result.data.get("status", "unhealthy"),
|
|
444
|
+
error=result.data.get("error"),
|
|
445
|
+
checked_entity=result.data.get("checked_entity"),
|
|
446
|
+
checked_action=result.data.get("checked_action"),
|
|
447
|
+
)
|
|
448
|
+
else:
|
|
449
|
+
return FacebookMarketingCheckResult(
|
|
450
|
+
status="unhealthy",
|
|
451
|
+
error=result.error or "Unknown error during health check",
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# ===== INTROSPECTION METHODS =====
|
|
455
|
+
|
|
456
|
+
@classmethod
|
|
457
|
+
def tool_utils(
|
|
458
|
+
cls,
|
|
459
|
+
func: _F | None = None,
|
|
460
|
+
*,
|
|
461
|
+
update_docstring: bool = True,
|
|
462
|
+
enable_hosted_mode_features: bool = True,
|
|
463
|
+
max_output_chars: int | None = DEFAULT_MAX_OUTPUT_CHARS,
|
|
464
|
+
) -> _F | Callable[[_F], _F]:
|
|
465
|
+
"""
|
|
466
|
+
Decorator that adds tool utilities like docstring augmentation and output limits.
|
|
467
|
+
|
|
468
|
+
Usage:
|
|
469
|
+
@mcp.tool()
|
|
470
|
+
@FacebookMarketingConnector.tool_utils
|
|
471
|
+
async def execute(entity: str, action: str, params: dict):
|
|
472
|
+
...
|
|
473
|
+
|
|
474
|
+
@mcp.tool()
|
|
475
|
+
@FacebookMarketingConnector.tool_utils(update_docstring=False, max_output_chars=None)
|
|
476
|
+
async def execute(entity: str, action: str, params: dict):
|
|
477
|
+
...
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
update_docstring: When True, append connector capabilities to __doc__.
|
|
481
|
+
enable_hosted_mode_features: When False, omit hosted-mode search sections from docstrings.
|
|
482
|
+
max_output_chars: Max serialized output size before raising. Use None to disable.
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
def decorate(inner: _F) -> _F:
|
|
486
|
+
if update_docstring:
|
|
487
|
+
description = generate_tool_description(
|
|
488
|
+
FacebookMarketingConnectorModel,
|
|
489
|
+
enable_hosted_mode_features=enable_hosted_mode_features,
|
|
490
|
+
)
|
|
491
|
+
original_doc = inner.__doc__ or ""
|
|
492
|
+
if original_doc.strip():
|
|
493
|
+
full_doc = f"{original_doc.strip()}\n{description}"
|
|
494
|
+
else:
|
|
495
|
+
full_doc = description
|
|
496
|
+
else:
|
|
497
|
+
full_doc = ""
|
|
498
|
+
|
|
499
|
+
if inspect.iscoroutinefunction(inner):
|
|
500
|
+
|
|
501
|
+
@wraps(inner)
|
|
502
|
+
async def aw(*args: Any, **kwargs: Any) -> Any:
|
|
503
|
+
result = await inner(*args, **kwargs)
|
|
504
|
+
return _check_output_size(result, max_output_chars, inner.__name__)
|
|
505
|
+
|
|
506
|
+
wrapped = aw
|
|
507
|
+
else:
|
|
508
|
+
|
|
509
|
+
@wraps(inner)
|
|
510
|
+
def sw(*args: Any, **kwargs: Any) -> Any:
|
|
511
|
+
result = inner(*args, **kwargs)
|
|
512
|
+
return _check_output_size(result, max_output_chars, inner.__name__)
|
|
513
|
+
|
|
514
|
+
wrapped = sw
|
|
515
|
+
|
|
516
|
+
if update_docstring:
|
|
517
|
+
wrapped.__doc__ = full_doc
|
|
518
|
+
return wrapped # type: ignore[return-value]
|
|
519
|
+
|
|
520
|
+
if func is not None:
|
|
521
|
+
return decorate(func)
|
|
522
|
+
return decorate
|
|
523
|
+
|
|
524
|
+
def list_entities(self) -> list[dict[str, Any]]:
|
|
525
|
+
"""
|
|
526
|
+
Get structured data about available entities, actions, and parameters.
|
|
527
|
+
|
|
528
|
+
Returns a list of entity descriptions with:
|
|
529
|
+
- entity_name: Name of the entity (e.g., "contacts", "deals")
|
|
530
|
+
- description: Entity description from the first endpoint
|
|
531
|
+
- available_actions: List of actions (e.g., ["list", "get", "create"])
|
|
532
|
+
- parameters: Dict mapping action -> list of parameter dicts
|
|
533
|
+
|
|
534
|
+
Example:
|
|
535
|
+
entities = connector.list_entities()
|
|
536
|
+
for entity in entities:
|
|
537
|
+
print(f"{entity['entity_name']}: {entity['available_actions']}")
|
|
538
|
+
"""
|
|
539
|
+
return describe_entities(FacebookMarketingConnectorModel)
|
|
540
|
+
|
|
541
|
+
def entity_schema(self, entity: str) -> dict[str, Any] | None:
|
|
542
|
+
"""
|
|
543
|
+
Get the JSON schema for an entity.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
entity: Entity name (e.g., "contacts", "companies")
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
JSON schema dict describing the entity structure, or None if not found.
|
|
550
|
+
|
|
551
|
+
Example:
|
|
552
|
+
schema = connector.entity_schema("contacts")
|
|
553
|
+
if schema:
|
|
554
|
+
print(f"Contact properties: {list(schema.get('properties', {}).keys())}")
|
|
555
|
+
"""
|
|
556
|
+
entity_def = next(
|
|
557
|
+
(e for e in FacebookMarketingConnectorModel.entities if e.name == entity),
|
|
558
|
+
None
|
|
559
|
+
)
|
|
560
|
+
if entity_def is None:
|
|
561
|
+
logging.getLogger(__name__).warning(
|
|
562
|
+
f"Entity '{entity}' not found. Available entities: "
|
|
563
|
+
f"{[e.name for e in FacebookMarketingConnectorModel.entities]}"
|
|
564
|
+
)
|
|
565
|
+
return entity_def.entity_schema if entity_def else None
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
class CampaignsQuery:
|
|
570
|
+
"""
|
|
571
|
+
Query class for Campaigns entity operations.
|
|
572
|
+
"""
|
|
573
|
+
|
|
574
|
+
def __init__(self, connector: FacebookMarketingConnector):
|
|
575
|
+
"""Initialize query with connector reference."""
|
|
576
|
+
self._connector = connector
|
|
577
|
+
|
|
578
|
+
async def list(
|
|
579
|
+
self,
|
|
580
|
+
account_id: str,
|
|
581
|
+
fields: str | None = None,
|
|
582
|
+
limit: int | None = None,
|
|
583
|
+
after: str | None = None,
|
|
584
|
+
**kwargs
|
|
585
|
+
) -> CampaignsListResult:
|
|
586
|
+
"""
|
|
587
|
+
Returns a list of campaigns for the specified ad account
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
account_id: The Facebook Ad Account ID (without act_ prefix)
|
|
591
|
+
fields: Comma-separated list of fields to return
|
|
592
|
+
limit: Maximum number of results to return
|
|
593
|
+
after: Cursor for pagination
|
|
594
|
+
**kwargs: Additional parameters
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
CampaignsListResult
|
|
598
|
+
"""
|
|
599
|
+
params = {k: v for k, v in {
|
|
600
|
+
"account_id": account_id,
|
|
601
|
+
"fields": fields,
|
|
602
|
+
"limit": limit,
|
|
603
|
+
"after": after,
|
|
604
|
+
**kwargs
|
|
605
|
+
}.items() if v is not None}
|
|
606
|
+
|
|
607
|
+
result = await self._connector.execute("campaigns", "list", params)
|
|
608
|
+
# Cast generic envelope to concrete typed result
|
|
609
|
+
return CampaignsListResult(
|
|
610
|
+
data=result.data,
|
|
611
|
+
meta=result.meta
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
async def get(
|
|
617
|
+
self,
|
|
618
|
+
campaign_id: str,
|
|
619
|
+
fields: str | None = None,
|
|
620
|
+
**kwargs
|
|
621
|
+
) -> Campaign:
|
|
622
|
+
"""
|
|
623
|
+
Returns a single campaign by ID
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
campaign_id: The campaign ID
|
|
627
|
+
fields: Comma-separated list of fields to return
|
|
628
|
+
**kwargs: Additional parameters
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
Campaign
|
|
632
|
+
"""
|
|
633
|
+
params = {k: v for k, v in {
|
|
634
|
+
"campaign_id": campaign_id,
|
|
635
|
+
"fields": fields,
|
|
636
|
+
**kwargs
|
|
637
|
+
}.items() if v is not None}
|
|
638
|
+
|
|
639
|
+
result = await self._connector.execute("campaigns", "get", params)
|
|
640
|
+
return result
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
async def search(
|
|
645
|
+
self,
|
|
646
|
+
query: CampaignsSearchQuery,
|
|
647
|
+
limit: int | None = None,
|
|
648
|
+
cursor: str | None = None,
|
|
649
|
+
fields: list[list[str]] | None = None,
|
|
650
|
+
) -> CampaignsSearchResult:
|
|
651
|
+
"""
|
|
652
|
+
Search campaigns records from Airbyte cache.
|
|
653
|
+
|
|
654
|
+
This operation searches cached data from Airbyte syncs.
|
|
655
|
+
Only available in hosted execution mode.
|
|
656
|
+
|
|
657
|
+
Available filter fields (CampaignsSearchFilter):
|
|
658
|
+
- id: Campaign ID
|
|
659
|
+
- name: Campaign name
|
|
660
|
+
- account_id: Ad account ID
|
|
661
|
+
- status: Campaign status
|
|
662
|
+
- effective_status: Effective status
|
|
663
|
+
- objective: Campaign objective
|
|
664
|
+
- daily_budget: Daily budget in account currency
|
|
665
|
+
- lifetime_budget: Lifetime budget
|
|
666
|
+
- budget_remaining: Remaining budget
|
|
667
|
+
- created_time: Campaign creation time
|
|
668
|
+
- start_time: Campaign start time
|
|
669
|
+
- stop_time: Campaign stop time
|
|
670
|
+
- updated_time: Last update time
|
|
671
|
+
|
|
672
|
+
Args:
|
|
673
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
674
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
675
|
+
limit: Maximum results to return (default 1000)
|
|
676
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
677
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
678
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
679
|
+
|
|
680
|
+
Returns:
|
|
681
|
+
CampaignsSearchResult with hits (list of AirbyteSearchHit[CampaignsSearchData]) and pagination info
|
|
682
|
+
|
|
683
|
+
Raises:
|
|
684
|
+
NotImplementedError: If called in local execution mode
|
|
685
|
+
"""
|
|
686
|
+
params: dict[str, Any] = {"query": query}
|
|
687
|
+
if limit is not None:
|
|
688
|
+
params["limit"] = limit
|
|
689
|
+
if cursor is not None:
|
|
690
|
+
params["cursor"] = cursor
|
|
691
|
+
if fields is not None:
|
|
692
|
+
params["fields"] = fields
|
|
693
|
+
|
|
694
|
+
result = await self._connector.execute("campaigns", "search", params)
|
|
695
|
+
|
|
696
|
+
# Parse response into typed result
|
|
697
|
+
return CampaignsSearchResult(
|
|
698
|
+
hits=[
|
|
699
|
+
AirbyteSearchHit[CampaignsSearchData](
|
|
700
|
+
id=hit.get("id"),
|
|
701
|
+
score=hit.get("score"),
|
|
702
|
+
data=CampaignsSearchData(**hit.get("data", {}))
|
|
703
|
+
)
|
|
704
|
+
for hit in result.get("hits", [])
|
|
705
|
+
],
|
|
706
|
+
next_cursor=result.get("next_cursor"),
|
|
707
|
+
took_ms=result.get("took_ms")
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
class AdSetsQuery:
|
|
711
|
+
"""
|
|
712
|
+
Query class for AdSets entity operations.
|
|
713
|
+
"""
|
|
714
|
+
|
|
715
|
+
def __init__(self, connector: FacebookMarketingConnector):
|
|
716
|
+
"""Initialize query with connector reference."""
|
|
717
|
+
self._connector = connector
|
|
718
|
+
|
|
719
|
+
async def list(
|
|
720
|
+
self,
|
|
721
|
+
account_id: str,
|
|
722
|
+
fields: str | None = None,
|
|
723
|
+
limit: int | None = None,
|
|
724
|
+
after: str | None = None,
|
|
725
|
+
**kwargs
|
|
726
|
+
) -> AdSetsListResult:
|
|
727
|
+
"""
|
|
728
|
+
Returns a list of ad sets for the specified ad account
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
account_id: The Facebook Ad Account ID (without act_ prefix)
|
|
732
|
+
fields: Comma-separated list of fields to return
|
|
733
|
+
limit: Maximum number of results to return
|
|
734
|
+
after: Cursor for pagination
|
|
735
|
+
**kwargs: Additional parameters
|
|
736
|
+
|
|
737
|
+
Returns:
|
|
738
|
+
AdSetsListResult
|
|
739
|
+
"""
|
|
740
|
+
params = {k: v for k, v in {
|
|
741
|
+
"account_id": account_id,
|
|
742
|
+
"fields": fields,
|
|
743
|
+
"limit": limit,
|
|
744
|
+
"after": after,
|
|
745
|
+
**kwargs
|
|
746
|
+
}.items() if v is not None}
|
|
747
|
+
|
|
748
|
+
result = await self._connector.execute("ad_sets", "list", params)
|
|
749
|
+
# Cast generic envelope to concrete typed result
|
|
750
|
+
return AdSetsListResult(
|
|
751
|
+
data=result.data,
|
|
752
|
+
meta=result.meta
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
async def get(
|
|
758
|
+
self,
|
|
759
|
+
adset_id: str,
|
|
760
|
+
fields: str | None = None,
|
|
761
|
+
**kwargs
|
|
762
|
+
) -> AdSet:
|
|
763
|
+
"""
|
|
764
|
+
Returns a single ad set by ID
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
adset_id: The ad set ID
|
|
768
|
+
fields: Comma-separated list of fields to return
|
|
769
|
+
**kwargs: Additional parameters
|
|
770
|
+
|
|
771
|
+
Returns:
|
|
772
|
+
AdSet
|
|
773
|
+
"""
|
|
774
|
+
params = {k: v for k, v in {
|
|
775
|
+
"adset_id": adset_id,
|
|
776
|
+
"fields": fields,
|
|
777
|
+
**kwargs
|
|
778
|
+
}.items() if v is not None}
|
|
779
|
+
|
|
780
|
+
result = await self._connector.execute("ad_sets", "get", params)
|
|
781
|
+
return result
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
async def search(
|
|
786
|
+
self,
|
|
787
|
+
query: AdSetsSearchQuery,
|
|
788
|
+
limit: int | None = None,
|
|
789
|
+
cursor: str | None = None,
|
|
790
|
+
fields: list[list[str]] | None = None,
|
|
791
|
+
) -> AdSetsSearchResult:
|
|
792
|
+
"""
|
|
793
|
+
Search ad_sets records from Airbyte cache.
|
|
794
|
+
|
|
795
|
+
This operation searches cached data from Airbyte syncs.
|
|
796
|
+
Only available in hosted execution mode.
|
|
797
|
+
|
|
798
|
+
Available filter fields (AdSetsSearchFilter):
|
|
799
|
+
- id: Ad Set ID
|
|
800
|
+
- name: Ad Set name
|
|
801
|
+
- account_id: Ad account ID
|
|
802
|
+
- campaign_id: Parent campaign ID
|
|
803
|
+
- effective_status: Effective status
|
|
804
|
+
- daily_budget: Daily budget
|
|
805
|
+
- lifetime_budget: Lifetime budget
|
|
806
|
+
- budget_remaining: Remaining budget
|
|
807
|
+
- bid_amount: Bid amount
|
|
808
|
+
- bid_strategy: Bid strategy
|
|
809
|
+
- created_time: Ad set creation time
|
|
810
|
+
- start_time: Ad set start time
|
|
811
|
+
- end_time: Ad set end time
|
|
812
|
+
- updated_time: Last update time
|
|
813
|
+
|
|
814
|
+
Args:
|
|
815
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
816
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
817
|
+
limit: Maximum results to return (default 1000)
|
|
818
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
819
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
820
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
821
|
+
|
|
822
|
+
Returns:
|
|
823
|
+
AdSetsSearchResult with hits (list of AirbyteSearchHit[AdSetsSearchData]) and pagination info
|
|
824
|
+
|
|
825
|
+
Raises:
|
|
826
|
+
NotImplementedError: If called in local execution mode
|
|
827
|
+
"""
|
|
828
|
+
params: dict[str, Any] = {"query": query}
|
|
829
|
+
if limit is not None:
|
|
830
|
+
params["limit"] = limit
|
|
831
|
+
if cursor is not None:
|
|
832
|
+
params["cursor"] = cursor
|
|
833
|
+
if fields is not None:
|
|
834
|
+
params["fields"] = fields
|
|
835
|
+
|
|
836
|
+
result = await self._connector.execute("ad_sets", "search", params)
|
|
837
|
+
|
|
838
|
+
# Parse response into typed result
|
|
839
|
+
return AdSetsSearchResult(
|
|
840
|
+
hits=[
|
|
841
|
+
AirbyteSearchHit[AdSetsSearchData](
|
|
842
|
+
id=hit.get("id"),
|
|
843
|
+
score=hit.get("score"),
|
|
844
|
+
data=AdSetsSearchData(**hit.get("data", {}))
|
|
845
|
+
)
|
|
846
|
+
for hit in result.get("hits", [])
|
|
847
|
+
],
|
|
848
|
+
next_cursor=result.get("next_cursor"),
|
|
849
|
+
took_ms=result.get("took_ms")
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
class AdsQuery:
|
|
853
|
+
"""
|
|
854
|
+
Query class for Ads entity operations.
|
|
855
|
+
"""
|
|
856
|
+
|
|
857
|
+
def __init__(self, connector: FacebookMarketingConnector):
|
|
858
|
+
"""Initialize query with connector reference."""
|
|
859
|
+
self._connector = connector
|
|
860
|
+
|
|
861
|
+
async def list(
|
|
862
|
+
self,
|
|
863
|
+
account_id: str,
|
|
864
|
+
fields: str | None = None,
|
|
865
|
+
limit: int | None = None,
|
|
866
|
+
after: str | None = None,
|
|
867
|
+
**kwargs
|
|
868
|
+
) -> AdsListResult:
|
|
869
|
+
"""
|
|
870
|
+
Returns a list of ads for the specified ad account
|
|
871
|
+
|
|
872
|
+
Args:
|
|
873
|
+
account_id: The Facebook Ad Account ID (without act_ prefix)
|
|
874
|
+
fields: Comma-separated list of fields to return
|
|
875
|
+
limit: Maximum number of results to return
|
|
876
|
+
after: Cursor for pagination
|
|
877
|
+
**kwargs: Additional parameters
|
|
878
|
+
|
|
879
|
+
Returns:
|
|
880
|
+
AdsListResult
|
|
881
|
+
"""
|
|
882
|
+
params = {k: v for k, v in {
|
|
883
|
+
"account_id": account_id,
|
|
884
|
+
"fields": fields,
|
|
885
|
+
"limit": limit,
|
|
886
|
+
"after": after,
|
|
887
|
+
**kwargs
|
|
888
|
+
}.items() if v is not None}
|
|
889
|
+
|
|
890
|
+
result = await self._connector.execute("ads", "list", params)
|
|
891
|
+
# Cast generic envelope to concrete typed result
|
|
892
|
+
return AdsListResult(
|
|
893
|
+
data=result.data,
|
|
894
|
+
meta=result.meta
|
|
895
|
+
)
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
async def get(
|
|
900
|
+
self,
|
|
901
|
+
ad_id: str,
|
|
902
|
+
fields: str | None = None,
|
|
903
|
+
**kwargs
|
|
904
|
+
) -> Ad:
|
|
905
|
+
"""
|
|
906
|
+
Returns a single ad by ID
|
|
907
|
+
|
|
908
|
+
Args:
|
|
909
|
+
ad_id: The ad ID
|
|
910
|
+
fields: Comma-separated list of fields to return
|
|
911
|
+
**kwargs: Additional parameters
|
|
912
|
+
|
|
913
|
+
Returns:
|
|
914
|
+
Ad
|
|
915
|
+
"""
|
|
916
|
+
params = {k: v for k, v in {
|
|
917
|
+
"ad_id": ad_id,
|
|
918
|
+
"fields": fields,
|
|
919
|
+
**kwargs
|
|
920
|
+
}.items() if v is not None}
|
|
921
|
+
|
|
922
|
+
result = await self._connector.execute("ads", "get", params)
|
|
923
|
+
return result
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
async def search(
|
|
928
|
+
self,
|
|
929
|
+
query: AdsSearchQuery,
|
|
930
|
+
limit: int | None = None,
|
|
931
|
+
cursor: str | None = None,
|
|
932
|
+
fields: list[list[str]] | None = None,
|
|
933
|
+
) -> AdsSearchResult:
|
|
934
|
+
"""
|
|
935
|
+
Search ads records from Airbyte cache.
|
|
936
|
+
|
|
937
|
+
This operation searches cached data from Airbyte syncs.
|
|
938
|
+
Only available in hosted execution mode.
|
|
939
|
+
|
|
940
|
+
Available filter fields (AdsSearchFilter):
|
|
941
|
+
- id: Ad ID
|
|
942
|
+
- name: Ad name
|
|
943
|
+
- account_id: Ad account ID
|
|
944
|
+
- adset_id: Parent ad set ID
|
|
945
|
+
- campaign_id: Parent campaign ID
|
|
946
|
+
- status: Ad status
|
|
947
|
+
- effective_status: Effective status
|
|
948
|
+
- created_time: Ad creation time
|
|
949
|
+
- updated_time: Last update time
|
|
950
|
+
|
|
951
|
+
Args:
|
|
952
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
953
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
954
|
+
limit: Maximum results to return (default 1000)
|
|
955
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
956
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
957
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
958
|
+
|
|
959
|
+
Returns:
|
|
960
|
+
AdsSearchResult with hits (list of AirbyteSearchHit[AdsSearchData]) and pagination info
|
|
961
|
+
|
|
962
|
+
Raises:
|
|
963
|
+
NotImplementedError: If called in local execution mode
|
|
964
|
+
"""
|
|
965
|
+
params: dict[str, Any] = {"query": query}
|
|
966
|
+
if limit is not None:
|
|
967
|
+
params["limit"] = limit
|
|
968
|
+
if cursor is not None:
|
|
969
|
+
params["cursor"] = cursor
|
|
970
|
+
if fields is not None:
|
|
971
|
+
params["fields"] = fields
|
|
972
|
+
|
|
973
|
+
result = await self._connector.execute("ads", "search", params)
|
|
974
|
+
|
|
975
|
+
# Parse response into typed result
|
|
976
|
+
return AdsSearchResult(
|
|
977
|
+
hits=[
|
|
978
|
+
AirbyteSearchHit[AdsSearchData](
|
|
979
|
+
id=hit.get("id"),
|
|
980
|
+
score=hit.get("score"),
|
|
981
|
+
data=AdsSearchData(**hit.get("data", {}))
|
|
982
|
+
)
|
|
983
|
+
for hit in result.get("hits", [])
|
|
984
|
+
],
|
|
985
|
+
next_cursor=result.get("next_cursor"),
|
|
986
|
+
took_ms=result.get("took_ms")
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
class AdCreativesQuery:
|
|
990
|
+
"""
|
|
991
|
+
Query class for AdCreatives entity operations.
|
|
992
|
+
"""
|
|
993
|
+
|
|
994
|
+
def __init__(self, connector: FacebookMarketingConnector):
|
|
995
|
+
"""Initialize query with connector reference."""
|
|
996
|
+
self._connector = connector
|
|
997
|
+
|
|
998
|
+
async def list(
|
|
999
|
+
self,
|
|
1000
|
+
account_id: str,
|
|
1001
|
+
fields: str | None = None,
|
|
1002
|
+
limit: int | None = None,
|
|
1003
|
+
after: str | None = None,
|
|
1004
|
+
**kwargs
|
|
1005
|
+
) -> AdCreativesListResult:
|
|
1006
|
+
"""
|
|
1007
|
+
Returns a list of ad creatives for the specified ad account
|
|
1008
|
+
|
|
1009
|
+
Args:
|
|
1010
|
+
account_id: The Facebook Ad Account ID (without act_ prefix)
|
|
1011
|
+
fields: Comma-separated list of fields to return
|
|
1012
|
+
limit: Maximum number of results to return
|
|
1013
|
+
after: Cursor for pagination
|
|
1014
|
+
**kwargs: Additional parameters
|
|
1015
|
+
|
|
1016
|
+
Returns:
|
|
1017
|
+
AdCreativesListResult
|
|
1018
|
+
"""
|
|
1019
|
+
params = {k: v for k, v in {
|
|
1020
|
+
"account_id": account_id,
|
|
1021
|
+
"fields": fields,
|
|
1022
|
+
"limit": limit,
|
|
1023
|
+
"after": after,
|
|
1024
|
+
**kwargs
|
|
1025
|
+
}.items() if v is not None}
|
|
1026
|
+
|
|
1027
|
+
result = await self._connector.execute("ad_creatives", "list", params)
|
|
1028
|
+
# Cast generic envelope to concrete typed result
|
|
1029
|
+
return AdCreativesListResult(
|
|
1030
|
+
data=result.data,
|
|
1031
|
+
meta=result.meta
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
async def search(
|
|
1037
|
+
self,
|
|
1038
|
+
query: AdCreativesSearchQuery,
|
|
1039
|
+
limit: int | None = None,
|
|
1040
|
+
cursor: str | None = None,
|
|
1041
|
+
fields: list[list[str]] | None = None,
|
|
1042
|
+
) -> AdCreativesSearchResult:
|
|
1043
|
+
"""
|
|
1044
|
+
Search ad_creatives records from Airbyte cache.
|
|
1045
|
+
|
|
1046
|
+
This operation searches cached data from Airbyte syncs.
|
|
1047
|
+
Only available in hosted execution mode.
|
|
1048
|
+
|
|
1049
|
+
Available filter fields (AdCreativesSearchFilter):
|
|
1050
|
+
- id: Ad Creative ID
|
|
1051
|
+
- name: Ad Creative name
|
|
1052
|
+
- account_id: Ad account ID
|
|
1053
|
+
- body: Ad body text
|
|
1054
|
+
- title: Ad title
|
|
1055
|
+
- status: Creative status
|
|
1056
|
+
- image_url: Image URL
|
|
1057
|
+
- thumbnail_url: Thumbnail URL
|
|
1058
|
+
- link_url: Link URL
|
|
1059
|
+
- call_to_action_type: Call to action type
|
|
1060
|
+
|
|
1061
|
+
Args:
|
|
1062
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
1063
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
1064
|
+
limit: Maximum results to return (default 1000)
|
|
1065
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
1066
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
1067
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
1068
|
+
|
|
1069
|
+
Returns:
|
|
1070
|
+
AdCreativesSearchResult with hits (list of AirbyteSearchHit[AdCreativesSearchData]) and pagination info
|
|
1071
|
+
|
|
1072
|
+
Raises:
|
|
1073
|
+
NotImplementedError: If called in local execution mode
|
|
1074
|
+
"""
|
|
1075
|
+
params: dict[str, Any] = {"query": query}
|
|
1076
|
+
if limit is not None:
|
|
1077
|
+
params["limit"] = limit
|
|
1078
|
+
if cursor is not None:
|
|
1079
|
+
params["cursor"] = cursor
|
|
1080
|
+
if fields is not None:
|
|
1081
|
+
params["fields"] = fields
|
|
1082
|
+
|
|
1083
|
+
result = await self._connector.execute("ad_creatives", "search", params)
|
|
1084
|
+
|
|
1085
|
+
# Parse response into typed result
|
|
1086
|
+
return AdCreativesSearchResult(
|
|
1087
|
+
hits=[
|
|
1088
|
+
AirbyteSearchHit[AdCreativesSearchData](
|
|
1089
|
+
id=hit.get("id"),
|
|
1090
|
+
score=hit.get("score"),
|
|
1091
|
+
data=AdCreativesSearchData(**hit.get("data", {}))
|
|
1092
|
+
)
|
|
1093
|
+
for hit in result.get("hits", [])
|
|
1094
|
+
],
|
|
1095
|
+
next_cursor=result.get("next_cursor"),
|
|
1096
|
+
took_ms=result.get("took_ms")
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
class AdsInsightsQuery:
|
|
1100
|
+
"""
|
|
1101
|
+
Query class for AdsInsights entity operations.
|
|
1102
|
+
"""
|
|
1103
|
+
|
|
1104
|
+
def __init__(self, connector: FacebookMarketingConnector):
|
|
1105
|
+
"""Initialize query with connector reference."""
|
|
1106
|
+
self._connector = connector
|
|
1107
|
+
|
|
1108
|
+
async def list(
|
|
1109
|
+
self,
|
|
1110
|
+
account_id: str,
|
|
1111
|
+
fields: str | None = None,
|
|
1112
|
+
date_preset: str | None = None,
|
|
1113
|
+
time_range: str | None = None,
|
|
1114
|
+
level: str | None = None,
|
|
1115
|
+
limit: int | None = None,
|
|
1116
|
+
after: str | None = None,
|
|
1117
|
+
**kwargs
|
|
1118
|
+
) -> AdsInsightsListResult:
|
|
1119
|
+
"""
|
|
1120
|
+
Returns performance insights for the specified ad account
|
|
1121
|
+
|
|
1122
|
+
Args:
|
|
1123
|
+
account_id: The Facebook Ad Account ID (without act_ prefix)
|
|
1124
|
+
fields: Comma-separated list of fields to return
|
|
1125
|
+
date_preset: Predefined date range
|
|
1126
|
+
time_range: Time range as JSON object with since and until dates (YYYY-MM-DD)
|
|
1127
|
+
level: Level of aggregation
|
|
1128
|
+
limit: Maximum number of results to return
|
|
1129
|
+
after: Cursor for pagination
|
|
1130
|
+
**kwargs: Additional parameters
|
|
1131
|
+
|
|
1132
|
+
Returns:
|
|
1133
|
+
AdsInsightsListResult
|
|
1134
|
+
"""
|
|
1135
|
+
params = {k: v for k, v in {
|
|
1136
|
+
"account_id": account_id,
|
|
1137
|
+
"fields": fields,
|
|
1138
|
+
"date_preset": date_preset,
|
|
1139
|
+
"time_range": time_range,
|
|
1140
|
+
"level": level,
|
|
1141
|
+
"limit": limit,
|
|
1142
|
+
"after": after,
|
|
1143
|
+
**kwargs
|
|
1144
|
+
}.items() if v is not None}
|
|
1145
|
+
|
|
1146
|
+
result = await self._connector.execute("ads_insights", "list", params)
|
|
1147
|
+
# Cast generic envelope to concrete typed result
|
|
1148
|
+
return AdsInsightsListResult(
|
|
1149
|
+
data=result.data,
|
|
1150
|
+
meta=result.meta
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
async def search(
|
|
1156
|
+
self,
|
|
1157
|
+
query: AdsInsightsSearchQuery,
|
|
1158
|
+
limit: int | None = None,
|
|
1159
|
+
cursor: str | None = None,
|
|
1160
|
+
fields: list[list[str]] | None = None,
|
|
1161
|
+
) -> AdsInsightsSearchResult:
|
|
1162
|
+
"""
|
|
1163
|
+
Search ads_insights records from Airbyte cache.
|
|
1164
|
+
|
|
1165
|
+
This operation searches cached data from Airbyte syncs.
|
|
1166
|
+
Only available in hosted execution mode.
|
|
1167
|
+
|
|
1168
|
+
Available filter fields (AdsInsightsSearchFilter):
|
|
1169
|
+
- account_id: Ad account ID
|
|
1170
|
+
- account_name: Ad account name
|
|
1171
|
+
- campaign_id: Campaign ID
|
|
1172
|
+
- campaign_name: Campaign name
|
|
1173
|
+
- adset_id: Ad set ID
|
|
1174
|
+
- adset_name: Ad set name
|
|
1175
|
+
- ad_id: Ad ID
|
|
1176
|
+
- ad_name: Ad name
|
|
1177
|
+
- clicks: Number of clicks
|
|
1178
|
+
- impressions: Number of impressions
|
|
1179
|
+
- reach: Number of unique people reached
|
|
1180
|
+
- spend: Amount spent
|
|
1181
|
+
- cpc: Cost per click
|
|
1182
|
+
- cpm: Cost per 1000 impressions
|
|
1183
|
+
- ctr: Click-through rate
|
|
1184
|
+
- date_start: Start date of the reporting period
|
|
1185
|
+
- date_stop: End date of the reporting period
|
|
1186
|
+
|
|
1187
|
+
Args:
|
|
1188
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
1189
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
1190
|
+
limit: Maximum results to return (default 1000)
|
|
1191
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
1192
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
1193
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
1194
|
+
|
|
1195
|
+
Returns:
|
|
1196
|
+
AdsInsightsSearchResult with hits (list of AirbyteSearchHit[AdsInsightsSearchData]) and pagination info
|
|
1197
|
+
|
|
1198
|
+
Raises:
|
|
1199
|
+
NotImplementedError: If called in local execution mode
|
|
1200
|
+
"""
|
|
1201
|
+
params: dict[str, Any] = {"query": query}
|
|
1202
|
+
if limit is not None:
|
|
1203
|
+
params["limit"] = limit
|
|
1204
|
+
if cursor is not None:
|
|
1205
|
+
params["cursor"] = cursor
|
|
1206
|
+
if fields is not None:
|
|
1207
|
+
params["fields"] = fields
|
|
1208
|
+
|
|
1209
|
+
result = await self._connector.execute("ads_insights", "search", params)
|
|
1210
|
+
|
|
1211
|
+
# Parse response into typed result
|
|
1212
|
+
return AdsInsightsSearchResult(
|
|
1213
|
+
hits=[
|
|
1214
|
+
AirbyteSearchHit[AdsInsightsSearchData](
|
|
1215
|
+
id=hit.get("id"),
|
|
1216
|
+
score=hit.get("score"),
|
|
1217
|
+
data=AdsInsightsSearchData(**hit.get("data", {}))
|
|
1218
|
+
)
|
|
1219
|
+
for hit in result.get("hits", [])
|
|
1220
|
+
],
|
|
1221
|
+
next_cursor=result.get("next_cursor"),
|
|
1222
|
+
took_ms=result.get("took_ms")
|
|
1223
|
+
)
|
|
1224
|
+
|
|
1225
|
+
class CustomConversionsQuery:
|
|
1226
|
+
"""
|
|
1227
|
+
Query class for CustomConversions entity operations.
|
|
1228
|
+
"""
|
|
1229
|
+
|
|
1230
|
+
def __init__(self, connector: FacebookMarketingConnector):
|
|
1231
|
+
"""Initialize query with connector reference."""
|
|
1232
|
+
self._connector = connector
|
|
1233
|
+
|
|
1234
|
+
async def list(
|
|
1235
|
+
self,
|
|
1236
|
+
account_id: str,
|
|
1237
|
+
fields: str | None = None,
|
|
1238
|
+
limit: int | None = None,
|
|
1239
|
+
after: str | None = None,
|
|
1240
|
+
**kwargs
|
|
1241
|
+
) -> CustomConversionsListResult:
|
|
1242
|
+
"""
|
|
1243
|
+
Returns a list of custom conversions for the specified ad account
|
|
1244
|
+
|
|
1245
|
+
Args:
|
|
1246
|
+
account_id: The Facebook Ad Account ID (without act_ prefix)
|
|
1247
|
+
fields: Comma-separated list of fields to return
|
|
1248
|
+
limit: Maximum number of results to return
|
|
1249
|
+
after: Cursor for pagination
|
|
1250
|
+
**kwargs: Additional parameters
|
|
1251
|
+
|
|
1252
|
+
Returns:
|
|
1253
|
+
CustomConversionsListResult
|
|
1254
|
+
"""
|
|
1255
|
+
params = {k: v for k, v in {
|
|
1256
|
+
"account_id": account_id,
|
|
1257
|
+
"fields": fields,
|
|
1258
|
+
"limit": limit,
|
|
1259
|
+
"after": after,
|
|
1260
|
+
**kwargs
|
|
1261
|
+
}.items() if v is not None}
|
|
1262
|
+
|
|
1263
|
+
result = await self._connector.execute("custom_conversions", "list", params)
|
|
1264
|
+
# Cast generic envelope to concrete typed result
|
|
1265
|
+
return CustomConversionsListResult(
|
|
1266
|
+
data=result.data,
|
|
1267
|
+
meta=result.meta
|
|
1268
|
+
)
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
async def search(
|
|
1273
|
+
self,
|
|
1274
|
+
query: CustomConversionsSearchQuery,
|
|
1275
|
+
limit: int | None = None,
|
|
1276
|
+
cursor: str | None = None,
|
|
1277
|
+
fields: list[list[str]] | None = None,
|
|
1278
|
+
) -> CustomConversionsSearchResult:
|
|
1279
|
+
"""
|
|
1280
|
+
Search custom_conversions records from Airbyte cache.
|
|
1281
|
+
|
|
1282
|
+
This operation searches cached data from Airbyte syncs.
|
|
1283
|
+
Only available in hosted execution mode.
|
|
1284
|
+
|
|
1285
|
+
Available filter fields (CustomConversionsSearchFilter):
|
|
1286
|
+
- id: Custom Conversion ID
|
|
1287
|
+
- name: Custom Conversion name
|
|
1288
|
+
- account_id: Ad account ID
|
|
1289
|
+
- description: Description
|
|
1290
|
+
- custom_event_type: Custom event type
|
|
1291
|
+
- creation_time: Creation time
|
|
1292
|
+
- first_fired_time: First fired time
|
|
1293
|
+
- last_fired_time: Last fired time
|
|
1294
|
+
- is_archived: Whether the conversion is archived
|
|
1295
|
+
|
|
1296
|
+
Args:
|
|
1297
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
1298
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
1299
|
+
limit: Maximum results to return (default 1000)
|
|
1300
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
1301
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
1302
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
1303
|
+
|
|
1304
|
+
Returns:
|
|
1305
|
+
CustomConversionsSearchResult with hits (list of AirbyteSearchHit[CustomConversionsSearchData]) and pagination info
|
|
1306
|
+
|
|
1307
|
+
Raises:
|
|
1308
|
+
NotImplementedError: If called in local execution mode
|
|
1309
|
+
"""
|
|
1310
|
+
params: dict[str, Any] = {"query": query}
|
|
1311
|
+
if limit is not None:
|
|
1312
|
+
params["limit"] = limit
|
|
1313
|
+
if cursor is not None:
|
|
1314
|
+
params["cursor"] = cursor
|
|
1315
|
+
if fields is not None:
|
|
1316
|
+
params["fields"] = fields
|
|
1317
|
+
|
|
1318
|
+
result = await self._connector.execute("custom_conversions", "search", params)
|
|
1319
|
+
|
|
1320
|
+
# Parse response into typed result
|
|
1321
|
+
return CustomConversionsSearchResult(
|
|
1322
|
+
hits=[
|
|
1323
|
+
AirbyteSearchHit[CustomConversionsSearchData](
|
|
1324
|
+
id=hit.get("id"),
|
|
1325
|
+
score=hit.get("score"),
|
|
1326
|
+
data=CustomConversionsSearchData(**hit.get("data", {}))
|
|
1327
|
+
)
|
|
1328
|
+
for hit in result.get("hits", [])
|
|
1329
|
+
],
|
|
1330
|
+
next_cursor=result.get("next_cursor"),
|
|
1331
|
+
took_ms=result.get("took_ms")
|
|
1332
|
+
)
|
|
1333
|
+
|
|
1334
|
+
class ImagesQuery:
|
|
1335
|
+
"""
|
|
1336
|
+
Query class for Images entity operations.
|
|
1337
|
+
"""
|
|
1338
|
+
|
|
1339
|
+
def __init__(self, connector: FacebookMarketingConnector):
|
|
1340
|
+
"""Initialize query with connector reference."""
|
|
1341
|
+
self._connector = connector
|
|
1342
|
+
|
|
1343
|
+
async def list(
|
|
1344
|
+
self,
|
|
1345
|
+
account_id: str,
|
|
1346
|
+
fields: str | None = None,
|
|
1347
|
+
limit: int | None = None,
|
|
1348
|
+
after: str | None = None,
|
|
1349
|
+
**kwargs
|
|
1350
|
+
) -> ImagesListResult:
|
|
1351
|
+
"""
|
|
1352
|
+
Returns a list of ad images for the specified ad account
|
|
1353
|
+
|
|
1354
|
+
Args:
|
|
1355
|
+
account_id: The Facebook Ad Account ID (without act_ prefix)
|
|
1356
|
+
fields: Comma-separated list of fields to return
|
|
1357
|
+
limit: Maximum number of results to return
|
|
1358
|
+
after: Cursor for pagination
|
|
1359
|
+
**kwargs: Additional parameters
|
|
1360
|
+
|
|
1361
|
+
Returns:
|
|
1362
|
+
ImagesListResult
|
|
1363
|
+
"""
|
|
1364
|
+
params = {k: v for k, v in {
|
|
1365
|
+
"account_id": account_id,
|
|
1366
|
+
"fields": fields,
|
|
1367
|
+
"limit": limit,
|
|
1368
|
+
"after": after,
|
|
1369
|
+
**kwargs
|
|
1370
|
+
}.items() if v is not None}
|
|
1371
|
+
|
|
1372
|
+
result = await self._connector.execute("images", "list", params)
|
|
1373
|
+
# Cast generic envelope to concrete typed result
|
|
1374
|
+
return ImagesListResult(
|
|
1375
|
+
data=result.data,
|
|
1376
|
+
meta=result.meta
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
async def search(
|
|
1382
|
+
self,
|
|
1383
|
+
query: ImagesSearchQuery,
|
|
1384
|
+
limit: int | None = None,
|
|
1385
|
+
cursor: str | None = None,
|
|
1386
|
+
fields: list[list[str]] | None = None,
|
|
1387
|
+
) -> ImagesSearchResult:
|
|
1388
|
+
"""
|
|
1389
|
+
Search images records from Airbyte cache.
|
|
1390
|
+
|
|
1391
|
+
This operation searches cached data from Airbyte syncs.
|
|
1392
|
+
Only available in hosted execution mode.
|
|
1393
|
+
|
|
1394
|
+
Available filter fields (ImagesSearchFilter):
|
|
1395
|
+
- id: Image ID
|
|
1396
|
+
- name: Image name
|
|
1397
|
+
- account_id: Ad account ID
|
|
1398
|
+
- hash: Image hash
|
|
1399
|
+
- url: Image URL
|
|
1400
|
+
- permalink_url: Permalink URL
|
|
1401
|
+
- width: Image width
|
|
1402
|
+
- height: Image height
|
|
1403
|
+
- status: Image status
|
|
1404
|
+
- created_time: Creation time
|
|
1405
|
+
- updated_time: Last update time
|
|
1406
|
+
|
|
1407
|
+
Args:
|
|
1408
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
1409
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
1410
|
+
limit: Maximum results to return (default 1000)
|
|
1411
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
1412
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
1413
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
1414
|
+
|
|
1415
|
+
Returns:
|
|
1416
|
+
ImagesSearchResult with hits (list of AirbyteSearchHit[ImagesSearchData]) and pagination info
|
|
1417
|
+
|
|
1418
|
+
Raises:
|
|
1419
|
+
NotImplementedError: If called in local execution mode
|
|
1420
|
+
"""
|
|
1421
|
+
params: dict[str, Any] = {"query": query}
|
|
1422
|
+
if limit is not None:
|
|
1423
|
+
params["limit"] = limit
|
|
1424
|
+
if cursor is not None:
|
|
1425
|
+
params["cursor"] = cursor
|
|
1426
|
+
if fields is not None:
|
|
1427
|
+
params["fields"] = fields
|
|
1428
|
+
|
|
1429
|
+
result = await self._connector.execute("images", "search", params)
|
|
1430
|
+
|
|
1431
|
+
# Parse response into typed result
|
|
1432
|
+
return ImagesSearchResult(
|
|
1433
|
+
hits=[
|
|
1434
|
+
AirbyteSearchHit[ImagesSearchData](
|
|
1435
|
+
id=hit.get("id"),
|
|
1436
|
+
score=hit.get("score"),
|
|
1437
|
+
data=ImagesSearchData(**hit.get("data", {}))
|
|
1438
|
+
)
|
|
1439
|
+
for hit in result.get("hits", [])
|
|
1440
|
+
],
|
|
1441
|
+
next_cursor=result.get("next_cursor"),
|
|
1442
|
+
took_ms=result.get("took_ms")
|
|
1443
|
+
)
|
|
1444
|
+
|
|
1445
|
+
class VideosQuery:
|
|
1446
|
+
"""
|
|
1447
|
+
Query class for Videos entity operations.
|
|
1448
|
+
"""
|
|
1449
|
+
|
|
1450
|
+
def __init__(self, connector: FacebookMarketingConnector):
|
|
1451
|
+
"""Initialize query with connector reference."""
|
|
1452
|
+
self._connector = connector
|
|
1453
|
+
|
|
1454
|
+
async def list(
|
|
1455
|
+
self,
|
|
1456
|
+
account_id: str,
|
|
1457
|
+
fields: str | None = None,
|
|
1458
|
+
limit: int | None = None,
|
|
1459
|
+
after: str | None = None,
|
|
1460
|
+
**kwargs
|
|
1461
|
+
) -> VideosListResult:
|
|
1462
|
+
"""
|
|
1463
|
+
Returns a list of ad videos for the specified ad account
|
|
1464
|
+
|
|
1465
|
+
Args:
|
|
1466
|
+
account_id: The Facebook Ad Account ID (without act_ prefix)
|
|
1467
|
+
fields: Comma-separated list of fields to return
|
|
1468
|
+
limit: Maximum number of results to return
|
|
1469
|
+
after: Cursor for pagination
|
|
1470
|
+
**kwargs: Additional parameters
|
|
1471
|
+
|
|
1472
|
+
Returns:
|
|
1473
|
+
VideosListResult
|
|
1474
|
+
"""
|
|
1475
|
+
params = {k: v for k, v in {
|
|
1476
|
+
"account_id": account_id,
|
|
1477
|
+
"fields": fields,
|
|
1478
|
+
"limit": limit,
|
|
1479
|
+
"after": after,
|
|
1480
|
+
**kwargs
|
|
1481
|
+
}.items() if v is not None}
|
|
1482
|
+
|
|
1483
|
+
result = await self._connector.execute("videos", "list", params)
|
|
1484
|
+
# Cast generic envelope to concrete typed result
|
|
1485
|
+
return VideosListResult(
|
|
1486
|
+
data=result.data,
|
|
1487
|
+
meta=result.meta
|
|
1488
|
+
)
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
async def search(
|
|
1493
|
+
self,
|
|
1494
|
+
query: VideosSearchQuery,
|
|
1495
|
+
limit: int | None = None,
|
|
1496
|
+
cursor: str | None = None,
|
|
1497
|
+
fields: list[list[str]] | None = None,
|
|
1498
|
+
) -> VideosSearchResult:
|
|
1499
|
+
"""
|
|
1500
|
+
Search videos records from Airbyte cache.
|
|
1501
|
+
|
|
1502
|
+
This operation searches cached data from Airbyte syncs.
|
|
1503
|
+
Only available in hosted execution mode.
|
|
1504
|
+
|
|
1505
|
+
Available filter fields (VideosSearchFilter):
|
|
1506
|
+
- id: Video ID
|
|
1507
|
+
- title: Video title
|
|
1508
|
+
- account_id: Ad account ID
|
|
1509
|
+
- description: Video description
|
|
1510
|
+
- length: Video length in seconds
|
|
1511
|
+
- source: Video source URL
|
|
1512
|
+
- permalink_url: Permalink URL
|
|
1513
|
+
- views: Number of views
|
|
1514
|
+
- created_time: Creation time
|
|
1515
|
+
- updated_time: Last update time
|
|
1516
|
+
|
|
1517
|
+
Args:
|
|
1518
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
1519
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
1520
|
+
limit: Maximum results to return (default 1000)
|
|
1521
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
1522
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
1523
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
1524
|
+
|
|
1525
|
+
Returns:
|
|
1526
|
+
VideosSearchResult with hits (list of AirbyteSearchHit[VideosSearchData]) and pagination info
|
|
1527
|
+
|
|
1528
|
+
Raises:
|
|
1529
|
+
NotImplementedError: If called in local execution mode
|
|
1530
|
+
"""
|
|
1531
|
+
params: dict[str, Any] = {"query": query}
|
|
1532
|
+
if limit is not None:
|
|
1533
|
+
params["limit"] = limit
|
|
1534
|
+
if cursor is not None:
|
|
1535
|
+
params["cursor"] = cursor
|
|
1536
|
+
if fields is not None:
|
|
1537
|
+
params["fields"] = fields
|
|
1538
|
+
|
|
1539
|
+
result = await self._connector.execute("videos", "search", params)
|
|
1540
|
+
|
|
1541
|
+
# Parse response into typed result
|
|
1542
|
+
return VideosSearchResult(
|
|
1543
|
+
hits=[
|
|
1544
|
+
AirbyteSearchHit[VideosSearchData](
|
|
1545
|
+
id=hit.get("id"),
|
|
1546
|
+
score=hit.get("score"),
|
|
1547
|
+
data=VideosSearchData(**hit.get("data", {}))
|
|
1548
|
+
)
|
|
1549
|
+
for hit in result.get("hits", [])
|
|
1550
|
+
],
|
|
1551
|
+
next_cursor=result.get("next_cursor"),
|
|
1552
|
+
took_ms=result.get("took_ms")
|
|
1553
|
+
)
|