airbyte-agent-klaviyo 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_klaviyo/__init__.py +225 -0
  2. airbyte_agent_klaviyo/_vendored/__init__.py +1 -0
  3. airbyte_agent_klaviyo/_vendored/connector_sdk/__init__.py +82 -0
  4. airbyte_agent_klaviyo/_vendored/connector_sdk/auth_strategies.py +1171 -0
  5. airbyte_agent_klaviyo/_vendored/connector_sdk/auth_template.py +135 -0
  6. airbyte_agent_klaviyo/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
  7. airbyte_agent_klaviyo/_vendored/connector_sdk/cloud_utils/client.py +213 -0
  8. airbyte_agent_klaviyo/_vendored/connector_sdk/connector_model_loader.py +1120 -0
  9. airbyte_agent_klaviyo/_vendored/connector_sdk/constants.py +78 -0
  10. airbyte_agent_klaviyo/_vendored/connector_sdk/exceptions.py +23 -0
  11. airbyte_agent_klaviyo/_vendored/connector_sdk/executor/__init__.py +31 -0
  12. airbyte_agent_klaviyo/_vendored/connector_sdk/executor/hosted_executor.py +201 -0
  13. airbyte_agent_klaviyo/_vendored/connector_sdk/executor/local_executor.py +1854 -0
  14. airbyte_agent_klaviyo/_vendored/connector_sdk/executor/models.py +202 -0
  15. airbyte_agent_klaviyo/_vendored/connector_sdk/extensions.py +693 -0
  16. airbyte_agent_klaviyo/_vendored/connector_sdk/http/__init__.py +37 -0
  17. airbyte_agent_klaviyo/_vendored/connector_sdk/http/adapters/__init__.py +9 -0
  18. airbyte_agent_klaviyo/_vendored/connector_sdk/http/adapters/httpx_adapter.py +251 -0
  19. airbyte_agent_klaviyo/_vendored/connector_sdk/http/config.py +98 -0
  20. airbyte_agent_klaviyo/_vendored/connector_sdk/http/exceptions.py +119 -0
  21. airbyte_agent_klaviyo/_vendored/connector_sdk/http/protocols.py +114 -0
  22. airbyte_agent_klaviyo/_vendored/connector_sdk/http/response.py +104 -0
  23. airbyte_agent_klaviyo/_vendored/connector_sdk/http_client.py +693 -0
  24. airbyte_agent_klaviyo/_vendored/connector_sdk/introspection.py +481 -0
  25. airbyte_agent_klaviyo/_vendored/connector_sdk/logging/__init__.py +11 -0
  26. airbyte_agent_klaviyo/_vendored/connector_sdk/logging/logger.py +273 -0
  27. airbyte_agent_klaviyo/_vendored/connector_sdk/logging/types.py +93 -0
  28. airbyte_agent_klaviyo/_vendored/connector_sdk/observability/__init__.py +11 -0
  29. airbyte_agent_klaviyo/_vendored/connector_sdk/observability/config.py +179 -0
  30. airbyte_agent_klaviyo/_vendored/connector_sdk/observability/models.py +19 -0
  31. airbyte_agent_klaviyo/_vendored/connector_sdk/observability/redactor.py +81 -0
  32. airbyte_agent_klaviyo/_vendored/connector_sdk/observability/session.py +103 -0
  33. airbyte_agent_klaviyo/_vendored/connector_sdk/performance/__init__.py +6 -0
  34. airbyte_agent_klaviyo/_vendored/connector_sdk/performance/instrumentation.py +57 -0
  35. airbyte_agent_klaviyo/_vendored/connector_sdk/performance/metrics.py +93 -0
  36. airbyte_agent_klaviyo/_vendored/connector_sdk/schema/__init__.py +75 -0
  37. airbyte_agent_klaviyo/_vendored/connector_sdk/schema/base.py +201 -0
  38. airbyte_agent_klaviyo/_vendored/connector_sdk/schema/components.py +244 -0
  39. airbyte_agent_klaviyo/_vendored/connector_sdk/schema/connector.py +120 -0
  40. airbyte_agent_klaviyo/_vendored/connector_sdk/schema/extensions.py +301 -0
  41. airbyte_agent_klaviyo/_vendored/connector_sdk/schema/operations.py +156 -0
  42. airbyte_agent_klaviyo/_vendored/connector_sdk/schema/security.py +236 -0
  43. airbyte_agent_klaviyo/_vendored/connector_sdk/secrets.py +182 -0
  44. airbyte_agent_klaviyo/_vendored/connector_sdk/telemetry/__init__.py +10 -0
  45. airbyte_agent_klaviyo/_vendored/connector_sdk/telemetry/config.py +32 -0
  46. airbyte_agent_klaviyo/_vendored/connector_sdk/telemetry/events.py +59 -0
  47. airbyte_agent_klaviyo/_vendored/connector_sdk/telemetry/tracker.py +155 -0
  48. airbyte_agent_klaviyo/_vendored/connector_sdk/types.py +270 -0
  49. airbyte_agent_klaviyo/_vendored/connector_sdk/utils.py +60 -0
  50. airbyte_agent_klaviyo/_vendored/connector_sdk/validation.py +848 -0
  51. airbyte_agent_klaviyo/connector.py +1431 -0
  52. airbyte_agent_klaviyo/connector_model.py +2230 -0
  53. airbyte_agent_klaviyo/models.py +676 -0
  54. airbyte_agent_klaviyo/types.py +1319 -0
  55. airbyte_agent_klaviyo-0.1.0.dist-info/METADATA +151 -0
  56. airbyte_agent_klaviyo-0.1.0.dist-info/RECORD +57 -0
  57. airbyte_agent_klaviyo-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,1431 @@
1
+ """
2
+ Klaviyo 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 KlaviyoConnectorModel
18
+ from ._vendored.connector_sdk.introspection import describe_entities, generate_tool_description
19
+ from .types import (
20
+ CampaignsGetParams,
21
+ CampaignsListParams,
22
+ EmailTemplatesGetParams,
23
+ EmailTemplatesListParams,
24
+ EventsListParams,
25
+ FlowsGetParams,
26
+ FlowsListParams,
27
+ ListsGetParams,
28
+ ListsListParams,
29
+ MetricsGetParams,
30
+ MetricsListParams,
31
+ ProfilesGetParams,
32
+ ProfilesListParams,
33
+ AirbyteSearchParams,
34
+ ProfilesSearchFilter,
35
+ ProfilesSearchQuery,
36
+ EventsSearchFilter,
37
+ EventsSearchQuery,
38
+ EmailTemplatesSearchFilter,
39
+ EmailTemplatesSearchQuery,
40
+ CampaignsSearchFilter,
41
+ CampaignsSearchQuery,
42
+ FlowsSearchFilter,
43
+ FlowsSearchQuery,
44
+ MetricsSearchFilter,
45
+ MetricsSearchQuery,
46
+ ListsSearchFilter,
47
+ ListsSearchQuery,
48
+ )
49
+ if TYPE_CHECKING:
50
+ from .models import KlaviyoAuthConfig
51
+ # Import response models and envelope models at runtime
52
+ from .models import (
53
+ KlaviyoCheckResult,
54
+ KlaviyoExecuteResult,
55
+ KlaviyoExecuteResultWithMeta,
56
+ ProfilesListResult,
57
+ ListsListResult,
58
+ CampaignsListResult,
59
+ EventsListResult,
60
+ MetricsListResult,
61
+ FlowsListResult,
62
+ EmailTemplatesListResult,
63
+ Campaign,
64
+ Event,
65
+ Flow,
66
+ List,
67
+ Metric,
68
+ Profile,
69
+ Template,
70
+ AirbyteSearchHit,
71
+ AirbyteSearchResult,
72
+ ProfilesSearchData,
73
+ ProfilesSearchResult,
74
+ EventsSearchData,
75
+ EventsSearchResult,
76
+ EmailTemplatesSearchData,
77
+ EmailTemplatesSearchResult,
78
+ CampaignsSearchData,
79
+ CampaignsSearchResult,
80
+ FlowsSearchData,
81
+ FlowsSearchResult,
82
+ MetricsSearchData,
83
+ MetricsSearchResult,
84
+ ListsSearchData,
85
+ ListsSearchResult,
86
+ )
87
+
88
+ # TypeVar for decorator type preservation
89
+ _F = TypeVar("_F", bound=Callable[..., Any])
90
+
91
+ DEFAULT_MAX_OUTPUT_CHARS = 50_000 # ~50KB default, configurable per-tool
92
+
93
+
94
+ def _raise_output_too_large(message: str) -> None:
95
+ try:
96
+ from pydantic_ai import ModelRetry # type: ignore[import-not-found]
97
+ except Exception as exc:
98
+ raise RuntimeError(message) from exc
99
+ raise ModelRetry(message)
100
+
101
+
102
+ def _check_output_size(result: Any, max_chars: int | None, tool_name: str) -> Any:
103
+ if max_chars is None or max_chars <= 0:
104
+ return result
105
+
106
+ try:
107
+ serialized = json.dumps(result, default=str)
108
+ except (TypeError, ValueError):
109
+ return result
110
+
111
+ if len(serialized) > max_chars:
112
+ truncated_preview = serialized[:500] + "..." if len(serialized) > 500 else serialized
113
+ _raise_output_too_large(
114
+ f"Tool '{tool_name}' output too large ({len(serialized):,} chars, limit {max_chars:,}). "
115
+ "Please narrow your query by: using the 'fields' parameter to select only needed fields, "
116
+ "adding filters, or reducing the 'limit'. "
117
+ f"Preview: {truncated_preview}"
118
+ )
119
+
120
+ return result
121
+
122
+
123
+
124
+
125
+ class KlaviyoConnector:
126
+ """
127
+ Type-safe Klaviyo API connector.
128
+
129
+ Auto-generated from OpenAPI specification with full type safety.
130
+ """
131
+
132
+ connector_name = "klaviyo"
133
+ connector_version = "1.0.0"
134
+ vendored_sdk_version = "0.1.0" # Version of vendored connector-sdk
135
+
136
+ # Map of (entity, action) -> needs_envelope for envelope wrapping decision
137
+ _ENVELOPE_MAP = {
138
+ ("profiles", "list"): True,
139
+ ("profiles", "get"): None,
140
+ ("lists", "list"): True,
141
+ ("lists", "get"): None,
142
+ ("campaigns", "list"): True,
143
+ ("campaigns", "get"): None,
144
+ ("events", "list"): True,
145
+ ("metrics", "list"): True,
146
+ ("metrics", "get"): None,
147
+ ("flows", "list"): True,
148
+ ("flows", "get"): None,
149
+ ("email_templates", "list"): True,
150
+ ("email_templates", "get"): None,
151
+ }
152
+
153
+ # Map of (entity, action) -> {python_param_name: api_param_name}
154
+ # Used to convert snake_case TypedDict keys to API parameter names in execute()
155
+ _PARAM_MAP = {
156
+ ('profiles', 'list'): {'page_size': 'page[size]', 'page_cursor': 'page[cursor]'},
157
+ ('profiles', 'get'): {'id': 'id'},
158
+ ('lists', 'list'): {'page_size': 'page[size]', 'page_cursor': 'page[cursor]'},
159
+ ('lists', 'get'): {'id': 'id'},
160
+ ('campaigns', 'list'): {'filter': 'filter', 'page_size': 'page[size]', 'page_cursor': 'page[cursor]'},
161
+ ('campaigns', 'get'): {'id': 'id'},
162
+ ('events', 'list'): {'page_size': 'page[size]', 'page_cursor': 'page[cursor]', 'sort': 'sort'},
163
+ ('metrics', 'list'): {'page_size': 'page[size]', 'page_cursor': 'page[cursor]'},
164
+ ('metrics', 'get'): {'id': 'id'},
165
+ ('flows', 'list'): {'page_size': 'page[size]', 'page_cursor': 'page[cursor]'},
166
+ ('flows', 'get'): {'id': 'id'},
167
+ ('email_templates', 'list'): {'page_size': 'page[size]', 'page_cursor': 'page[cursor]'},
168
+ ('email_templates', 'get'): {'id': 'id'},
169
+ }
170
+
171
+ def __init__(
172
+ self,
173
+ auth_config: KlaviyoAuthConfig | 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 klaviyo 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 = KlaviyoConnector(auth_config=KlaviyoAuthConfig(api_key="..."))
196
+ # Hosted mode (executed on Airbyte cloud)
197
+ connector = KlaviyoConnector(
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 = KlaviyoConnector(
210
+ auth_config=KlaviyoAuthConfig(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(KlaviyoConnectorModel.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=KlaviyoConnectorModel,
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.profiles = ProfilesQuery(self)
247
+ self.lists = ListsQuery(self)
248
+ self.campaigns = CampaignsQuery(self)
249
+ self.events = EventsQuery(self)
250
+ self.metrics = MetricsQuery(self)
251
+ self.flows = FlowsQuery(self)
252
+ self.email_templates = EmailTemplatesQuery(self)
253
+
254
+ # ===== TYPED EXECUTE METHOD (Recommended Interface) =====
255
+
256
+ @overload
257
+ async def execute(
258
+ self,
259
+ entity: Literal["profiles"],
260
+ action: Literal["list"],
261
+ params: "ProfilesListParams"
262
+ ) -> "ProfilesListResult": ...
263
+
264
+ @overload
265
+ async def execute(
266
+ self,
267
+ entity: Literal["profiles"],
268
+ action: Literal["get"],
269
+ params: "ProfilesGetParams"
270
+ ) -> "Profile": ...
271
+
272
+ @overload
273
+ async def execute(
274
+ self,
275
+ entity: Literal["lists"],
276
+ action: Literal["list"],
277
+ params: "ListsListParams"
278
+ ) -> "ListsListResult": ...
279
+
280
+ @overload
281
+ async def execute(
282
+ self,
283
+ entity: Literal["lists"],
284
+ action: Literal["get"],
285
+ params: "ListsGetParams"
286
+ ) -> "List": ...
287
+
288
+ @overload
289
+ async def execute(
290
+ self,
291
+ entity: Literal["campaigns"],
292
+ action: Literal["list"],
293
+ params: "CampaignsListParams"
294
+ ) -> "CampaignsListResult": ...
295
+
296
+ @overload
297
+ async def execute(
298
+ self,
299
+ entity: Literal["campaigns"],
300
+ action: Literal["get"],
301
+ params: "CampaignsGetParams"
302
+ ) -> "Campaign": ...
303
+
304
+ @overload
305
+ async def execute(
306
+ self,
307
+ entity: Literal["events"],
308
+ action: Literal["list"],
309
+ params: "EventsListParams"
310
+ ) -> "EventsListResult": ...
311
+
312
+ @overload
313
+ async def execute(
314
+ self,
315
+ entity: Literal["metrics"],
316
+ action: Literal["list"],
317
+ params: "MetricsListParams"
318
+ ) -> "MetricsListResult": ...
319
+
320
+ @overload
321
+ async def execute(
322
+ self,
323
+ entity: Literal["metrics"],
324
+ action: Literal["get"],
325
+ params: "MetricsGetParams"
326
+ ) -> "Metric": ...
327
+
328
+ @overload
329
+ async def execute(
330
+ self,
331
+ entity: Literal["flows"],
332
+ action: Literal["list"],
333
+ params: "FlowsListParams"
334
+ ) -> "FlowsListResult": ...
335
+
336
+ @overload
337
+ async def execute(
338
+ self,
339
+ entity: Literal["flows"],
340
+ action: Literal["get"],
341
+ params: "FlowsGetParams"
342
+ ) -> "Flow": ...
343
+
344
+ @overload
345
+ async def execute(
346
+ self,
347
+ entity: Literal["email_templates"],
348
+ action: Literal["list"],
349
+ params: "EmailTemplatesListParams"
350
+ ) -> "EmailTemplatesListResult": ...
351
+
352
+ @overload
353
+ async def execute(
354
+ self,
355
+ entity: Literal["email_templates"],
356
+ action: Literal["get"],
357
+ params: "EmailTemplatesGetParams"
358
+ ) -> "Template": ...
359
+
360
+
361
+ @overload
362
+ async def execute(
363
+ self,
364
+ entity: str,
365
+ action: Literal["list", "get", "search"],
366
+ params: Mapping[str, Any]
367
+ ) -> KlaviyoExecuteResult[Any] | KlaviyoExecuteResultWithMeta[Any, Any] | Any: ...
368
+
369
+ async def execute(
370
+ self,
371
+ entity: str,
372
+ action: Literal["list", "get", "search"],
373
+ params: Mapping[str, Any] | None = None
374
+ ) -> Any:
375
+ """
376
+ Execute an entity operation with full type safety.
377
+
378
+ This is the recommended interface for blessed connectors as it:
379
+ - Uses the same signature as non-blessed connectors
380
+ - Provides full IDE autocomplete for entity/action/params
381
+ - Makes migration from generic to blessed connectors seamless
382
+
383
+ Args:
384
+ entity: Entity name (e.g., "customers")
385
+ action: Operation action (e.g., "create", "get", "list")
386
+ params: Operation parameters (typed based on entity+action)
387
+
388
+ Returns:
389
+ Typed response based on the operation
390
+
391
+ Example:
392
+ customer = await connector.execute(
393
+ entity="customers",
394
+ action="get",
395
+ params={"id": "cus_123"}
396
+ )
397
+ """
398
+ from ._vendored.connector_sdk.executor import ExecutionConfig
399
+
400
+ # Remap parameter names from snake_case (TypedDict keys) to API parameter names
401
+ resolved_params = dict(params) if params is not None else None
402
+ if resolved_params:
403
+ param_map = self._PARAM_MAP.get((entity, action), {})
404
+ if param_map:
405
+ resolved_params = {param_map.get(k, k): v for k, v in resolved_params.items()}
406
+
407
+ # Use ExecutionConfig for both local and hosted executors
408
+ config = ExecutionConfig(
409
+ entity=entity,
410
+ action=action,
411
+ params=resolved_params
412
+ )
413
+
414
+ result = await self._executor.execute(config)
415
+
416
+ if not result.success:
417
+ raise RuntimeError(f"Execution failed: {result.error}")
418
+
419
+ # Check if this operation has extractors configured
420
+ has_extractors = self._ENVELOPE_MAP.get((entity, action), False)
421
+
422
+ if has_extractors:
423
+ # With extractors - return Pydantic envelope with data and meta
424
+ if result.meta is not None:
425
+ return KlaviyoExecuteResultWithMeta[Any, Any](
426
+ data=result.data,
427
+ meta=result.meta
428
+ )
429
+ else:
430
+ return KlaviyoExecuteResult[Any](data=result.data)
431
+ else:
432
+ # No extractors - return raw response data
433
+ return result.data
434
+
435
+ # ===== HEALTH CHECK METHOD =====
436
+
437
+ async def check(self) -> KlaviyoCheckResult:
438
+ """
439
+ Perform a health check to verify connectivity and credentials.
440
+
441
+ Executes a lightweight list operation (limit=1) to validate that
442
+ the connector can communicate with the API and credentials are valid.
443
+
444
+ Returns:
445
+ KlaviyoCheckResult with status ("healthy" or "unhealthy") and optional error message
446
+
447
+ Example:
448
+ result = await connector.check()
449
+ if result.status == "healthy":
450
+ print("Connection verified!")
451
+ else:
452
+ print(f"Check failed: {result.error}")
453
+ """
454
+ result = await self._executor.check()
455
+
456
+ if result.success and isinstance(result.data, dict):
457
+ return KlaviyoCheckResult(
458
+ status=result.data.get("status", "unhealthy"),
459
+ error=result.data.get("error"),
460
+ checked_entity=result.data.get("checked_entity"),
461
+ checked_action=result.data.get("checked_action"),
462
+ )
463
+ else:
464
+ return KlaviyoCheckResult(
465
+ status="unhealthy",
466
+ error=result.error or "Unknown error during health check",
467
+ )
468
+
469
+ # ===== INTROSPECTION METHODS =====
470
+
471
+ @classmethod
472
+ def tool_utils(
473
+ cls,
474
+ func: _F | None = None,
475
+ *,
476
+ update_docstring: bool = True,
477
+ enable_hosted_mode_features: bool = True,
478
+ max_output_chars: int | None = DEFAULT_MAX_OUTPUT_CHARS,
479
+ ) -> _F | Callable[[_F], _F]:
480
+ """
481
+ Decorator that adds tool utilities like docstring augmentation and output limits.
482
+
483
+ Usage:
484
+ @mcp.tool()
485
+ @KlaviyoConnector.tool_utils
486
+ async def execute(entity: str, action: str, params: dict):
487
+ ...
488
+
489
+ @mcp.tool()
490
+ @KlaviyoConnector.tool_utils(update_docstring=False, max_output_chars=None)
491
+ async def execute(entity: str, action: str, params: dict):
492
+ ...
493
+
494
+ Args:
495
+ update_docstring: When True, append connector capabilities to __doc__.
496
+ enable_hosted_mode_features: When False, omit hosted-mode search sections from docstrings.
497
+ max_output_chars: Max serialized output size before raising. Use None to disable.
498
+ """
499
+
500
+ def decorate(inner: _F) -> _F:
501
+ if update_docstring:
502
+ description = generate_tool_description(
503
+ KlaviyoConnectorModel,
504
+ enable_hosted_mode_features=enable_hosted_mode_features,
505
+ )
506
+ original_doc = inner.__doc__ or ""
507
+ if original_doc.strip():
508
+ full_doc = f"{original_doc.strip()}\n{description}"
509
+ else:
510
+ full_doc = description
511
+ else:
512
+ full_doc = ""
513
+
514
+ if inspect.iscoroutinefunction(inner):
515
+
516
+ @wraps(inner)
517
+ async def aw(*args: Any, **kwargs: Any) -> Any:
518
+ result = await inner(*args, **kwargs)
519
+ return _check_output_size(result, max_output_chars, inner.__name__)
520
+
521
+ wrapped = aw
522
+ else:
523
+
524
+ @wraps(inner)
525
+ def sw(*args: Any, **kwargs: Any) -> Any:
526
+ result = inner(*args, **kwargs)
527
+ return _check_output_size(result, max_output_chars, inner.__name__)
528
+
529
+ wrapped = sw
530
+
531
+ if update_docstring:
532
+ wrapped.__doc__ = full_doc
533
+ return wrapped # type: ignore[return-value]
534
+
535
+ if func is not None:
536
+ return decorate(func)
537
+ return decorate
538
+
539
+ def list_entities(self) -> list[dict[str, Any]]:
540
+ """
541
+ Get structured data about available entities, actions, and parameters.
542
+
543
+ Returns a list of entity descriptions with:
544
+ - entity_name: Name of the entity (e.g., "contacts", "deals")
545
+ - description: Entity description from the first endpoint
546
+ - available_actions: List of actions (e.g., ["list", "get", "create"])
547
+ - parameters: Dict mapping action -> list of parameter dicts
548
+
549
+ Example:
550
+ entities = connector.list_entities()
551
+ for entity in entities:
552
+ print(f"{entity['entity_name']}: {entity['available_actions']}")
553
+ """
554
+ return describe_entities(KlaviyoConnectorModel)
555
+
556
+ def entity_schema(self, entity: str) -> dict[str, Any] | None:
557
+ """
558
+ Get the JSON schema for an entity.
559
+
560
+ Args:
561
+ entity: Entity name (e.g., "contacts", "companies")
562
+
563
+ Returns:
564
+ JSON schema dict describing the entity structure, or None if not found.
565
+
566
+ Example:
567
+ schema = connector.entity_schema("contacts")
568
+ if schema:
569
+ print(f"Contact properties: {list(schema.get('properties', {}).keys())}")
570
+ """
571
+ entity_def = next(
572
+ (e for e in KlaviyoConnectorModel.entities if e.name == entity),
573
+ None
574
+ )
575
+ if entity_def is None:
576
+ logging.getLogger(__name__).warning(
577
+ f"Entity '{entity}' not found. Available entities: "
578
+ f"{[e.name for e in KlaviyoConnectorModel.entities]}"
579
+ )
580
+ return entity_def.entity_schema if entity_def else None
581
+
582
+
583
+
584
+ class ProfilesQuery:
585
+ """
586
+ Query class for Profiles entity operations.
587
+ """
588
+
589
+ def __init__(self, connector: KlaviyoConnector):
590
+ """Initialize query with connector reference."""
591
+ self._connector = connector
592
+
593
+ async def list(
594
+ self,
595
+ page_size: int | None = None,
596
+ page_cursor: str | None = None,
597
+ **kwargs
598
+ ) -> ProfilesListResult:
599
+ """
600
+ Returns a paginated list of profiles (contacts) in your Klaviyo account
601
+
602
+ Args:
603
+ page_size: Number of results per page (max 100)
604
+ page_cursor: Cursor for pagination
605
+ **kwargs: Additional parameters
606
+
607
+ Returns:
608
+ ProfilesListResult
609
+ """
610
+ params = {k: v for k, v in {
611
+ "page[size]": page_size,
612
+ "page[cursor]": page_cursor,
613
+ **kwargs
614
+ }.items() if v is not None}
615
+
616
+ result = await self._connector.execute("profiles", "list", params)
617
+ # Cast generic envelope to concrete typed result
618
+ return ProfilesListResult(
619
+ data=result.data
620
+ )
621
+
622
+
623
+
624
+ async def get(
625
+ self,
626
+ id: str | None = None,
627
+ **kwargs
628
+ ) -> Profile:
629
+ """
630
+ Get a single profile by ID
631
+
632
+ Args:
633
+ id: Profile ID
634
+ **kwargs: Additional parameters
635
+
636
+ Returns:
637
+ Profile
638
+ """
639
+ params = {k: v for k, v in {
640
+ "id": id,
641
+ **kwargs
642
+ }.items() if v is not None}
643
+
644
+ result = await self._connector.execute("profiles", "get", params)
645
+ return result
646
+
647
+
648
+
649
+ async def search(
650
+ self,
651
+ query: ProfilesSearchQuery,
652
+ limit: int | None = None,
653
+ cursor: str | None = None,
654
+ fields: list[list[str]] | None = None,
655
+ ) -> ProfilesSearchResult:
656
+ """
657
+ Search profiles records from Airbyte cache.
658
+
659
+ This operation searches cached data from Airbyte syncs.
660
+ Only available in hosted execution mode.
661
+
662
+ Available filter fields (ProfilesSearchFilter):
663
+ - attributes:
664
+ - id:
665
+ - links:
666
+ - relationships:
667
+ - segments:
668
+ - type:
669
+ - updated:
670
+
671
+ Args:
672
+ query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
673
+ in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
674
+ limit: Maximum results to return (default 1000)
675
+ cursor: Pagination cursor from previous response's next_cursor
676
+ fields: Field paths to include in results. Each path is a list of keys for nested access.
677
+ Example: [["id"], ["user", "name"]] returns id and user.name fields.
678
+
679
+ Returns:
680
+ ProfilesSearchResult with hits (list of AirbyteSearchHit[ProfilesSearchData]) and pagination info
681
+
682
+ Raises:
683
+ NotImplementedError: If called in local execution mode
684
+ """
685
+ params: dict[str, Any] = {"query": query}
686
+ if limit is not None:
687
+ params["limit"] = limit
688
+ if cursor is not None:
689
+ params["cursor"] = cursor
690
+ if fields is not None:
691
+ params["fields"] = fields
692
+
693
+ result = await self._connector.execute("profiles", "search", params)
694
+
695
+ # Parse response into typed result
696
+ return ProfilesSearchResult(
697
+ hits=[
698
+ AirbyteSearchHit[ProfilesSearchData](
699
+ id=hit.get("id"),
700
+ score=hit.get("score"),
701
+ data=ProfilesSearchData(**hit.get("data", {}))
702
+ )
703
+ for hit in result.get("hits", [])
704
+ ],
705
+ next_cursor=result.get("next_cursor"),
706
+ took_ms=result.get("took_ms")
707
+ )
708
+
709
+ class ListsQuery:
710
+ """
711
+ Query class for Lists entity operations.
712
+ """
713
+
714
+ def __init__(self, connector: KlaviyoConnector):
715
+ """Initialize query with connector reference."""
716
+ self._connector = connector
717
+
718
+ async def list(
719
+ self,
720
+ page_size: int | None = None,
721
+ page_cursor: str | None = None,
722
+ **kwargs
723
+ ) -> ListsListResult:
724
+ """
725
+ Returns a paginated list of all lists in your Klaviyo account
726
+
727
+ Args:
728
+ page_size: Number of results per page (max 100)
729
+ page_cursor: Cursor for pagination
730
+ **kwargs: Additional parameters
731
+
732
+ Returns:
733
+ ListsListResult
734
+ """
735
+ params = {k: v for k, v in {
736
+ "page[size]": page_size,
737
+ "page[cursor]": page_cursor,
738
+ **kwargs
739
+ }.items() if v is not None}
740
+
741
+ result = await self._connector.execute("lists", "list", params)
742
+ # Cast generic envelope to concrete typed result
743
+ return ListsListResult(
744
+ data=result.data
745
+ )
746
+
747
+
748
+
749
+ async def get(
750
+ self,
751
+ id: str | None = None,
752
+ **kwargs
753
+ ) -> List:
754
+ """
755
+ Get a single list by ID
756
+
757
+ Args:
758
+ id: List ID
759
+ **kwargs: Additional parameters
760
+
761
+ Returns:
762
+ List
763
+ """
764
+ params = {k: v for k, v in {
765
+ "id": id,
766
+ **kwargs
767
+ }.items() if v is not None}
768
+
769
+ result = await self._connector.execute("lists", "get", params)
770
+ return result
771
+
772
+
773
+
774
+ async def search(
775
+ self,
776
+ query: ListsSearchQuery,
777
+ limit: int | None = None,
778
+ cursor: str | None = None,
779
+ fields: list[list[str]] | None = None,
780
+ ) -> ListsSearchResult:
781
+ """
782
+ Search lists records from Airbyte cache.
783
+
784
+ This operation searches cached data from Airbyte syncs.
785
+ Only available in hosted execution mode.
786
+
787
+ Available filter fields (ListsSearchFilter):
788
+ - attributes:
789
+ - id:
790
+ - links:
791
+ - relationships:
792
+ - type:
793
+ - updated:
794
+
795
+ Args:
796
+ query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
797
+ in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
798
+ limit: Maximum results to return (default 1000)
799
+ cursor: Pagination cursor from previous response's next_cursor
800
+ fields: Field paths to include in results. Each path is a list of keys for nested access.
801
+ Example: [["id"], ["user", "name"]] returns id and user.name fields.
802
+
803
+ Returns:
804
+ ListsSearchResult with hits (list of AirbyteSearchHit[ListsSearchData]) and pagination info
805
+
806
+ Raises:
807
+ NotImplementedError: If called in local execution mode
808
+ """
809
+ params: dict[str, Any] = {"query": query}
810
+ if limit is not None:
811
+ params["limit"] = limit
812
+ if cursor is not None:
813
+ params["cursor"] = cursor
814
+ if fields is not None:
815
+ params["fields"] = fields
816
+
817
+ result = await self._connector.execute("lists", "search", params)
818
+
819
+ # Parse response into typed result
820
+ return ListsSearchResult(
821
+ hits=[
822
+ AirbyteSearchHit[ListsSearchData](
823
+ id=hit.get("id"),
824
+ score=hit.get("score"),
825
+ data=ListsSearchData(**hit.get("data", {}))
826
+ )
827
+ for hit in result.get("hits", [])
828
+ ],
829
+ next_cursor=result.get("next_cursor"),
830
+ took_ms=result.get("took_ms")
831
+ )
832
+
833
+ class CampaignsQuery:
834
+ """
835
+ Query class for Campaigns entity operations.
836
+ """
837
+
838
+ def __init__(self, connector: KlaviyoConnector):
839
+ """Initialize query with connector reference."""
840
+ self._connector = connector
841
+
842
+ async def list(
843
+ self,
844
+ filter: str,
845
+ page_size: int | None = None,
846
+ page_cursor: str | None = None,
847
+ **kwargs
848
+ ) -> CampaignsListResult:
849
+ """
850
+ Returns a paginated list of campaigns. A channel filter is required.
851
+
852
+ Args:
853
+ filter: Filter by channel (email or sms)
854
+ page_size: Number of results per page (max 100)
855
+ page_cursor: Cursor for pagination
856
+ **kwargs: Additional parameters
857
+
858
+ Returns:
859
+ CampaignsListResult
860
+ """
861
+ params = {k: v for k, v in {
862
+ "filter": filter,
863
+ "page[size]": page_size,
864
+ "page[cursor]": page_cursor,
865
+ **kwargs
866
+ }.items() if v is not None}
867
+
868
+ result = await self._connector.execute("campaigns", "list", params)
869
+ # Cast generic envelope to concrete typed result
870
+ return CampaignsListResult(
871
+ data=result.data
872
+ )
873
+
874
+
875
+
876
+ async def get(
877
+ self,
878
+ id: str | None = None,
879
+ **kwargs
880
+ ) -> Campaign:
881
+ """
882
+ Get a single campaign by ID
883
+
884
+ Args:
885
+ id: Campaign ID
886
+ **kwargs: Additional parameters
887
+
888
+ Returns:
889
+ Campaign
890
+ """
891
+ params = {k: v for k, v in {
892
+ "id": id,
893
+ **kwargs
894
+ }.items() if v is not None}
895
+
896
+ result = await self._connector.execute("campaigns", "get", params)
897
+ return result
898
+
899
+
900
+
901
+ async def search(
902
+ self,
903
+ query: CampaignsSearchQuery,
904
+ limit: int | None = None,
905
+ cursor: str | None = None,
906
+ fields: list[list[str]] | None = None,
907
+ ) -> CampaignsSearchResult:
908
+ """
909
+ Search campaigns records from Airbyte cache.
910
+
911
+ This operation searches cached data from Airbyte syncs.
912
+ Only available in hosted execution mode.
913
+
914
+ Available filter fields (CampaignsSearchFilter):
915
+ - attributes:
916
+ - id:
917
+ - links:
918
+ - relationships:
919
+ - type:
920
+ - updated_at:
921
+
922
+ Args:
923
+ query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
924
+ in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
925
+ limit: Maximum results to return (default 1000)
926
+ cursor: Pagination cursor from previous response's next_cursor
927
+ fields: Field paths to include in results. Each path is a list of keys for nested access.
928
+ Example: [["id"], ["user", "name"]] returns id and user.name fields.
929
+
930
+ Returns:
931
+ CampaignsSearchResult with hits (list of AirbyteSearchHit[CampaignsSearchData]) and pagination info
932
+
933
+ Raises:
934
+ NotImplementedError: If called in local execution mode
935
+ """
936
+ params: dict[str, Any] = {"query": query}
937
+ if limit is not None:
938
+ params["limit"] = limit
939
+ if cursor is not None:
940
+ params["cursor"] = cursor
941
+ if fields is not None:
942
+ params["fields"] = fields
943
+
944
+ result = await self._connector.execute("campaigns", "search", params)
945
+
946
+ # Parse response into typed result
947
+ return CampaignsSearchResult(
948
+ hits=[
949
+ AirbyteSearchHit[CampaignsSearchData](
950
+ id=hit.get("id"),
951
+ score=hit.get("score"),
952
+ data=CampaignsSearchData(**hit.get("data", {}))
953
+ )
954
+ for hit in result.get("hits", [])
955
+ ],
956
+ next_cursor=result.get("next_cursor"),
957
+ took_ms=result.get("took_ms")
958
+ )
959
+
960
+ class EventsQuery:
961
+ """
962
+ Query class for Events entity operations.
963
+ """
964
+
965
+ def __init__(self, connector: KlaviyoConnector):
966
+ """Initialize query with connector reference."""
967
+ self._connector = connector
968
+
969
+ async def list(
970
+ self,
971
+ page_size: int | None = None,
972
+ page_cursor: str | None = None,
973
+ sort: str | None = None,
974
+ **kwargs
975
+ ) -> EventsListResult:
976
+ """
977
+ Returns a paginated list of events (actions taken by profiles)
978
+
979
+ Args:
980
+ page_size: Number of results per page (max 100)
981
+ page_cursor: Cursor for pagination
982
+ sort: Sort order for events
983
+ **kwargs: Additional parameters
984
+
985
+ Returns:
986
+ EventsListResult
987
+ """
988
+ params = {k: v for k, v in {
989
+ "page[size]": page_size,
990
+ "page[cursor]": page_cursor,
991
+ "sort": sort,
992
+ **kwargs
993
+ }.items() if v is not None}
994
+
995
+ result = await self._connector.execute("events", "list", params)
996
+ # Cast generic envelope to concrete typed result
997
+ return EventsListResult(
998
+ data=result.data
999
+ )
1000
+
1001
+
1002
+
1003
+ async def search(
1004
+ self,
1005
+ query: EventsSearchQuery,
1006
+ limit: int | None = None,
1007
+ cursor: str | None = None,
1008
+ fields: list[list[str]] | None = None,
1009
+ ) -> EventsSearchResult:
1010
+ """
1011
+ Search events records from Airbyte cache.
1012
+
1013
+ This operation searches cached data from Airbyte syncs.
1014
+ Only available in hosted execution mode.
1015
+
1016
+ Available filter fields (EventsSearchFilter):
1017
+ - attributes:
1018
+ - datetime:
1019
+ - id:
1020
+ - links:
1021
+ - relationships:
1022
+ - type:
1023
+
1024
+ Args:
1025
+ query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
1026
+ in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
1027
+ limit: Maximum results to return (default 1000)
1028
+ cursor: Pagination cursor from previous response's next_cursor
1029
+ fields: Field paths to include in results. Each path is a list of keys for nested access.
1030
+ Example: [["id"], ["user", "name"]] returns id and user.name fields.
1031
+
1032
+ Returns:
1033
+ EventsSearchResult with hits (list of AirbyteSearchHit[EventsSearchData]) and pagination info
1034
+
1035
+ Raises:
1036
+ NotImplementedError: If called in local execution mode
1037
+ """
1038
+ params: dict[str, Any] = {"query": query}
1039
+ if limit is not None:
1040
+ params["limit"] = limit
1041
+ if cursor is not None:
1042
+ params["cursor"] = cursor
1043
+ if fields is not None:
1044
+ params["fields"] = fields
1045
+
1046
+ result = await self._connector.execute("events", "search", params)
1047
+
1048
+ # Parse response into typed result
1049
+ return EventsSearchResult(
1050
+ hits=[
1051
+ AirbyteSearchHit[EventsSearchData](
1052
+ id=hit.get("id"),
1053
+ score=hit.get("score"),
1054
+ data=EventsSearchData(**hit.get("data", {}))
1055
+ )
1056
+ for hit in result.get("hits", [])
1057
+ ],
1058
+ next_cursor=result.get("next_cursor"),
1059
+ took_ms=result.get("took_ms")
1060
+ )
1061
+
1062
+ class MetricsQuery:
1063
+ """
1064
+ Query class for Metrics entity operations.
1065
+ """
1066
+
1067
+ def __init__(self, connector: KlaviyoConnector):
1068
+ """Initialize query with connector reference."""
1069
+ self._connector = connector
1070
+
1071
+ async def list(
1072
+ self,
1073
+ page_size: int | None = None,
1074
+ page_cursor: str | None = None,
1075
+ **kwargs
1076
+ ) -> MetricsListResult:
1077
+ """
1078
+ Returns a paginated list of metrics (event types)
1079
+
1080
+ Args:
1081
+ page_size: Number of results per page (max 100)
1082
+ page_cursor: Cursor for pagination
1083
+ **kwargs: Additional parameters
1084
+
1085
+ Returns:
1086
+ MetricsListResult
1087
+ """
1088
+ params = {k: v for k, v in {
1089
+ "page[size]": page_size,
1090
+ "page[cursor]": page_cursor,
1091
+ **kwargs
1092
+ }.items() if v is not None}
1093
+
1094
+ result = await self._connector.execute("metrics", "list", params)
1095
+ # Cast generic envelope to concrete typed result
1096
+ return MetricsListResult(
1097
+ data=result.data
1098
+ )
1099
+
1100
+
1101
+
1102
+ async def get(
1103
+ self,
1104
+ id: str | None = None,
1105
+ **kwargs
1106
+ ) -> Metric:
1107
+ """
1108
+ Get a single metric by ID
1109
+
1110
+ Args:
1111
+ id: Metric ID
1112
+ **kwargs: Additional parameters
1113
+
1114
+ Returns:
1115
+ Metric
1116
+ """
1117
+ params = {k: v for k, v in {
1118
+ "id": id,
1119
+ **kwargs
1120
+ }.items() if v is not None}
1121
+
1122
+ result = await self._connector.execute("metrics", "get", params)
1123
+ return result
1124
+
1125
+
1126
+
1127
+ async def search(
1128
+ self,
1129
+ query: MetricsSearchQuery,
1130
+ limit: int | None = None,
1131
+ cursor: str | None = None,
1132
+ fields: list[list[str]] | None = None,
1133
+ ) -> MetricsSearchResult:
1134
+ """
1135
+ Search metrics records from Airbyte cache.
1136
+
1137
+ This operation searches cached data from Airbyte syncs.
1138
+ Only available in hosted execution mode.
1139
+
1140
+ Available filter fields (MetricsSearchFilter):
1141
+ - attributes:
1142
+ - id:
1143
+ - links:
1144
+ - relationships:
1145
+ - type:
1146
+ - updated:
1147
+
1148
+ Args:
1149
+ query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
1150
+ in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
1151
+ limit: Maximum results to return (default 1000)
1152
+ cursor: Pagination cursor from previous response's next_cursor
1153
+ fields: Field paths to include in results. Each path is a list of keys for nested access.
1154
+ Example: [["id"], ["user", "name"]] returns id and user.name fields.
1155
+
1156
+ Returns:
1157
+ MetricsSearchResult with hits (list of AirbyteSearchHit[MetricsSearchData]) and pagination info
1158
+
1159
+ Raises:
1160
+ NotImplementedError: If called in local execution mode
1161
+ """
1162
+ params: dict[str, Any] = {"query": query}
1163
+ if limit is not None:
1164
+ params["limit"] = limit
1165
+ if cursor is not None:
1166
+ params["cursor"] = cursor
1167
+ if fields is not None:
1168
+ params["fields"] = fields
1169
+
1170
+ result = await self._connector.execute("metrics", "search", params)
1171
+
1172
+ # Parse response into typed result
1173
+ return MetricsSearchResult(
1174
+ hits=[
1175
+ AirbyteSearchHit[MetricsSearchData](
1176
+ id=hit.get("id"),
1177
+ score=hit.get("score"),
1178
+ data=MetricsSearchData(**hit.get("data", {}))
1179
+ )
1180
+ for hit in result.get("hits", [])
1181
+ ],
1182
+ next_cursor=result.get("next_cursor"),
1183
+ took_ms=result.get("took_ms")
1184
+ )
1185
+
1186
+ class FlowsQuery:
1187
+ """
1188
+ Query class for Flows entity operations.
1189
+ """
1190
+
1191
+ def __init__(self, connector: KlaviyoConnector):
1192
+ """Initialize query with connector reference."""
1193
+ self._connector = connector
1194
+
1195
+ async def list(
1196
+ self,
1197
+ page_size: int | None = None,
1198
+ page_cursor: str | None = None,
1199
+ **kwargs
1200
+ ) -> FlowsListResult:
1201
+ """
1202
+ Returns a paginated list of flows (automated sequences)
1203
+
1204
+ Args:
1205
+ page_size: Number of results per page (max 100)
1206
+ page_cursor: Cursor for pagination
1207
+ **kwargs: Additional parameters
1208
+
1209
+ Returns:
1210
+ FlowsListResult
1211
+ """
1212
+ params = {k: v for k, v in {
1213
+ "page[size]": page_size,
1214
+ "page[cursor]": page_cursor,
1215
+ **kwargs
1216
+ }.items() if v is not None}
1217
+
1218
+ result = await self._connector.execute("flows", "list", params)
1219
+ # Cast generic envelope to concrete typed result
1220
+ return FlowsListResult(
1221
+ data=result.data
1222
+ )
1223
+
1224
+
1225
+
1226
+ async def get(
1227
+ self,
1228
+ id: str | None = None,
1229
+ **kwargs
1230
+ ) -> Flow:
1231
+ """
1232
+ Get a single flow by ID
1233
+
1234
+ Args:
1235
+ id: Flow ID
1236
+ **kwargs: Additional parameters
1237
+
1238
+ Returns:
1239
+ Flow
1240
+ """
1241
+ params = {k: v for k, v in {
1242
+ "id": id,
1243
+ **kwargs
1244
+ }.items() if v is not None}
1245
+
1246
+ result = await self._connector.execute("flows", "get", params)
1247
+ return result
1248
+
1249
+
1250
+
1251
+ async def search(
1252
+ self,
1253
+ query: FlowsSearchQuery,
1254
+ limit: int | None = None,
1255
+ cursor: str | None = None,
1256
+ fields: list[list[str]] | None = None,
1257
+ ) -> FlowsSearchResult:
1258
+ """
1259
+ Search flows records from Airbyte cache.
1260
+
1261
+ This operation searches cached data from Airbyte syncs.
1262
+ Only available in hosted execution mode.
1263
+
1264
+ Available filter fields (FlowsSearchFilter):
1265
+ - attributes:
1266
+ - id:
1267
+ - links:
1268
+ - relationships:
1269
+ - type:
1270
+ - updated:
1271
+
1272
+ Args:
1273
+ query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
1274
+ in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
1275
+ limit: Maximum results to return (default 1000)
1276
+ cursor: Pagination cursor from previous response's next_cursor
1277
+ fields: Field paths to include in results. Each path is a list of keys for nested access.
1278
+ Example: [["id"], ["user", "name"]] returns id and user.name fields.
1279
+
1280
+ Returns:
1281
+ FlowsSearchResult with hits (list of AirbyteSearchHit[FlowsSearchData]) and pagination info
1282
+
1283
+ Raises:
1284
+ NotImplementedError: If called in local execution mode
1285
+ """
1286
+ params: dict[str, Any] = {"query": query}
1287
+ if limit is not None:
1288
+ params["limit"] = limit
1289
+ if cursor is not None:
1290
+ params["cursor"] = cursor
1291
+ if fields is not None:
1292
+ params["fields"] = fields
1293
+
1294
+ result = await self._connector.execute("flows", "search", params)
1295
+
1296
+ # Parse response into typed result
1297
+ return FlowsSearchResult(
1298
+ hits=[
1299
+ AirbyteSearchHit[FlowsSearchData](
1300
+ id=hit.get("id"),
1301
+ score=hit.get("score"),
1302
+ data=FlowsSearchData(**hit.get("data", {}))
1303
+ )
1304
+ for hit in result.get("hits", [])
1305
+ ],
1306
+ next_cursor=result.get("next_cursor"),
1307
+ took_ms=result.get("took_ms")
1308
+ )
1309
+
1310
+ class EmailTemplatesQuery:
1311
+ """
1312
+ Query class for EmailTemplates entity operations.
1313
+ """
1314
+
1315
+ def __init__(self, connector: KlaviyoConnector):
1316
+ """Initialize query with connector reference."""
1317
+ self._connector = connector
1318
+
1319
+ async def list(
1320
+ self,
1321
+ page_size: int | None = None,
1322
+ page_cursor: str | None = None,
1323
+ **kwargs
1324
+ ) -> EmailTemplatesListResult:
1325
+ """
1326
+ Returns a paginated list of email templates
1327
+
1328
+ Args:
1329
+ page_size: Number of results per page (max 100)
1330
+ page_cursor: Cursor for pagination
1331
+ **kwargs: Additional parameters
1332
+
1333
+ Returns:
1334
+ EmailTemplatesListResult
1335
+ """
1336
+ params = {k: v for k, v in {
1337
+ "page[size]": page_size,
1338
+ "page[cursor]": page_cursor,
1339
+ **kwargs
1340
+ }.items() if v is not None}
1341
+
1342
+ result = await self._connector.execute("email_templates", "list", params)
1343
+ # Cast generic envelope to concrete typed result
1344
+ return EmailTemplatesListResult(
1345
+ data=result.data
1346
+ )
1347
+
1348
+
1349
+
1350
+ async def get(
1351
+ self,
1352
+ id: str | None = None,
1353
+ **kwargs
1354
+ ) -> Template:
1355
+ """
1356
+ Get a single email template by ID
1357
+
1358
+ Args:
1359
+ id: Template ID
1360
+ **kwargs: Additional parameters
1361
+
1362
+ Returns:
1363
+ Template
1364
+ """
1365
+ params = {k: v for k, v in {
1366
+ "id": id,
1367
+ **kwargs
1368
+ }.items() if v is not None}
1369
+
1370
+ result = await self._connector.execute("email_templates", "get", params)
1371
+ return result
1372
+
1373
+
1374
+
1375
+ async def search(
1376
+ self,
1377
+ query: EmailTemplatesSearchQuery,
1378
+ limit: int | None = None,
1379
+ cursor: str | None = None,
1380
+ fields: list[list[str]] | None = None,
1381
+ ) -> EmailTemplatesSearchResult:
1382
+ """
1383
+ Search email_templates records from Airbyte cache.
1384
+
1385
+ This operation searches cached data from Airbyte syncs.
1386
+ Only available in hosted execution mode.
1387
+
1388
+ Available filter fields (EmailTemplatesSearchFilter):
1389
+ - attributes:
1390
+ - id:
1391
+ - links:
1392
+ - type:
1393
+ - updated:
1394
+
1395
+ Args:
1396
+ query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
1397
+ in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
1398
+ limit: Maximum results to return (default 1000)
1399
+ cursor: Pagination cursor from previous response's next_cursor
1400
+ fields: Field paths to include in results. Each path is a list of keys for nested access.
1401
+ Example: [["id"], ["user", "name"]] returns id and user.name fields.
1402
+
1403
+ Returns:
1404
+ EmailTemplatesSearchResult with hits (list of AirbyteSearchHit[EmailTemplatesSearchData]) and pagination info
1405
+
1406
+ Raises:
1407
+ NotImplementedError: If called in local execution mode
1408
+ """
1409
+ params: dict[str, Any] = {"query": query}
1410
+ if limit is not None:
1411
+ params["limit"] = limit
1412
+ if cursor is not None:
1413
+ params["cursor"] = cursor
1414
+ if fields is not None:
1415
+ params["fields"] = fields
1416
+
1417
+ result = await self._connector.execute("email_templates", "search", params)
1418
+
1419
+ # Parse response into typed result
1420
+ return EmailTemplatesSearchResult(
1421
+ hits=[
1422
+ AirbyteSearchHit[EmailTemplatesSearchData](
1423
+ id=hit.get("id"),
1424
+ score=hit.get("score"),
1425
+ data=EmailTemplatesSearchData(**hit.get("data", {}))
1426
+ )
1427
+ for hit in result.get("hits", [])
1428
+ ],
1429
+ next_cursor=result.get("next_cursor"),
1430
+ took_ms=result.get("took_ms")
1431
+ )