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.
Files changed (57) hide show
  1. airbyte_agent_facebook_marketing/__init__.py +221 -0
  2. airbyte_agent_facebook_marketing/_vendored/__init__.py +1 -0
  3. airbyte_agent_facebook_marketing/_vendored/connector_sdk/__init__.py +82 -0
  4. airbyte_agent_facebook_marketing/_vendored/connector_sdk/auth_strategies.py +1171 -0
  5. airbyte_agent_facebook_marketing/_vendored/connector_sdk/auth_template.py +135 -0
  6. airbyte_agent_facebook_marketing/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
  7. airbyte_agent_facebook_marketing/_vendored/connector_sdk/cloud_utils/client.py +213 -0
  8. airbyte_agent_facebook_marketing/_vendored/connector_sdk/connector_model_loader.py +1120 -0
  9. airbyte_agent_facebook_marketing/_vendored/connector_sdk/constants.py +78 -0
  10. airbyte_agent_facebook_marketing/_vendored/connector_sdk/exceptions.py +23 -0
  11. airbyte_agent_facebook_marketing/_vendored/connector_sdk/executor/__init__.py +31 -0
  12. airbyte_agent_facebook_marketing/_vendored/connector_sdk/executor/hosted_executor.py +201 -0
  13. airbyte_agent_facebook_marketing/_vendored/connector_sdk/executor/local_executor.py +1854 -0
  14. airbyte_agent_facebook_marketing/_vendored/connector_sdk/executor/models.py +202 -0
  15. airbyte_agent_facebook_marketing/_vendored/connector_sdk/extensions.py +693 -0
  16. airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/__init__.py +37 -0
  17. airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/adapters/__init__.py +9 -0
  18. airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/adapters/httpx_adapter.py +251 -0
  19. airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/config.py +98 -0
  20. airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/exceptions.py +119 -0
  21. airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/protocols.py +114 -0
  22. airbyte_agent_facebook_marketing/_vendored/connector_sdk/http/response.py +104 -0
  23. airbyte_agent_facebook_marketing/_vendored/connector_sdk/http_client.py +693 -0
  24. airbyte_agent_facebook_marketing/_vendored/connector_sdk/introspection.py +481 -0
  25. airbyte_agent_facebook_marketing/_vendored/connector_sdk/logging/__init__.py +11 -0
  26. airbyte_agent_facebook_marketing/_vendored/connector_sdk/logging/logger.py +273 -0
  27. airbyte_agent_facebook_marketing/_vendored/connector_sdk/logging/types.py +93 -0
  28. airbyte_agent_facebook_marketing/_vendored/connector_sdk/observability/__init__.py +11 -0
  29. airbyte_agent_facebook_marketing/_vendored/connector_sdk/observability/config.py +179 -0
  30. airbyte_agent_facebook_marketing/_vendored/connector_sdk/observability/models.py +19 -0
  31. airbyte_agent_facebook_marketing/_vendored/connector_sdk/observability/redactor.py +81 -0
  32. airbyte_agent_facebook_marketing/_vendored/connector_sdk/observability/session.py +103 -0
  33. airbyte_agent_facebook_marketing/_vendored/connector_sdk/performance/__init__.py +6 -0
  34. airbyte_agent_facebook_marketing/_vendored/connector_sdk/performance/instrumentation.py +57 -0
  35. airbyte_agent_facebook_marketing/_vendored/connector_sdk/performance/metrics.py +93 -0
  36. airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/__init__.py +75 -0
  37. airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/base.py +201 -0
  38. airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/components.py +244 -0
  39. airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/connector.py +120 -0
  40. airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/extensions.py +301 -0
  41. airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/operations.py +156 -0
  42. airbyte_agent_facebook_marketing/_vendored/connector_sdk/schema/security.py +236 -0
  43. airbyte_agent_facebook_marketing/_vendored/connector_sdk/secrets.py +182 -0
  44. airbyte_agent_facebook_marketing/_vendored/connector_sdk/telemetry/__init__.py +10 -0
  45. airbyte_agent_facebook_marketing/_vendored/connector_sdk/telemetry/config.py +32 -0
  46. airbyte_agent_facebook_marketing/_vendored/connector_sdk/telemetry/events.py +59 -0
  47. airbyte_agent_facebook_marketing/_vendored/connector_sdk/telemetry/tracker.py +155 -0
  48. airbyte_agent_facebook_marketing/_vendored/connector_sdk/types.py +270 -0
  49. airbyte_agent_facebook_marketing/_vendored/connector_sdk/utils.py +60 -0
  50. airbyte_agent_facebook_marketing/_vendored/connector_sdk/validation.py +848 -0
  51. airbyte_agent_facebook_marketing/connector.py +1553 -0
  52. airbyte_agent_facebook_marketing/connector_model.py +3120 -0
  53. airbyte_agent_facebook_marketing/models.py +814 -0
  54. airbyte_agent_facebook_marketing/types.py +1957 -0
  55. airbyte_agent_facebook_marketing-0.1.0.dist-info/METADATA +148 -0
  56. airbyte_agent_facebook_marketing-0.1.0.dist-info/RECORD +57 -0
  57. 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
+ )