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.
- airbyte_agent_klaviyo/__init__.py +225 -0
- airbyte_agent_klaviyo/_vendored/__init__.py +1 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/__init__.py +82 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/auth_strategies.py +1171 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/auth_template.py +135 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/cloud_utils/client.py +213 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/connector_model_loader.py +1120 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/constants.py +78 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/exceptions.py +23 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/executor/__init__.py +31 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/executor/hosted_executor.py +201 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/executor/local_executor.py +1854 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/executor/models.py +202 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/extensions.py +693 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/http/__init__.py +37 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/http/adapters/__init__.py +9 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/http/adapters/httpx_adapter.py +251 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/http/config.py +98 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/http/exceptions.py +119 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/http/protocols.py +114 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/http/response.py +104 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/http_client.py +693 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/introspection.py +481 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/logging/__init__.py +11 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/logging/logger.py +273 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/logging/types.py +93 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/observability/__init__.py +11 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/observability/config.py +179 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/observability/models.py +19 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/observability/redactor.py +81 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/observability/session.py +103 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/performance/__init__.py +6 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/performance/instrumentation.py +57 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/performance/metrics.py +93 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/schema/__init__.py +75 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/schema/base.py +201 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/schema/components.py +244 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/schema/connector.py +120 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/schema/extensions.py +301 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/schema/operations.py +156 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/schema/security.py +236 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/secrets.py +182 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/telemetry/__init__.py +10 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/telemetry/config.py +32 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/telemetry/events.py +59 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/telemetry/tracker.py +155 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/types.py +270 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/utils.py +60 -0
- airbyte_agent_klaviyo/_vendored/connector_sdk/validation.py +848 -0
- airbyte_agent_klaviyo/connector.py +1431 -0
- airbyte_agent_klaviyo/connector_model.py +2230 -0
- airbyte_agent_klaviyo/models.py +676 -0
- airbyte_agent_klaviyo/types.py +1319 -0
- airbyte_agent_klaviyo-0.1.0.dist-info/METADATA +151 -0
- airbyte_agent_klaviyo-0.1.0.dist-info/RECORD +57 -0
- 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
|
+
)
|