airbyte-agent-amazon-ads 0.1.6__py3-none-any.whl → 0.1.12__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_amazon_ads/__init__.py +20 -2
- airbyte_agent_amazon_ads/_vendored/connector_sdk/connector_model_loader.py +138 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/executor/local_executor.py +3 -1
- airbyte_agent_amazon_ads/_vendored/connector_sdk/introspection.py +222 -10
- airbyte_agent_amazon_ads/_vendored/connector_sdk/schema/base.py +3 -1
- airbyte_agent_amazon_ads/_vendored/connector_sdk/schema/components.py +5 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/schema/extensions.py +71 -0
- airbyte_agent_amazon_ads/_vendored/connector_sdk/types.py +1 -0
- airbyte_agent_amazon_ads/connector.py +166 -38
- airbyte_agent_amazon_ads/connector_model.py +1144 -2
- airbyte_agent_amazon_ads/models.py +55 -0
- airbyte_agent_amazon_ads/types.py +190 -0
- {airbyte_agent_amazon_ads-0.1.6.dist-info → airbyte_agent_amazon_ads-0.1.12.dist-info}/METADATA +8 -7
- {airbyte_agent_amazon_ads-0.1.6.dist-info → airbyte_agent_amazon_ads-0.1.12.dist-info}/RECORD +15 -15
- {airbyte_agent_amazon_ads-0.1.6.dist-info → airbyte_agent_amazon_ads-0.1.12.dist-info}/WHEEL +0 -0
|
@@ -4,8 +4,11 @@ Amazon-Ads connector.
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import inspect
|
|
8
|
+
import json
|
|
7
9
|
import logging
|
|
8
|
-
from
|
|
10
|
+
from functools import wraps
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, Mapping, TypeVar, overload
|
|
9
12
|
try:
|
|
10
13
|
from typing import Literal
|
|
11
14
|
except ImportError:
|
|
@@ -21,6 +24,9 @@ from .types import (
|
|
|
21
24
|
SponsoredProductCampaignsGetParams,
|
|
22
25
|
SponsoredProductCampaignsListParams,
|
|
23
26
|
SponsoredProductCampaignsListParamsStatefilter,
|
|
27
|
+
AirbyteSearchParams,
|
|
28
|
+
ProfilesSearchFilter,
|
|
29
|
+
ProfilesSearchQuery,
|
|
24
30
|
)
|
|
25
31
|
if TYPE_CHECKING:
|
|
26
32
|
from .models import AmazonAdsAuthConfig
|
|
@@ -34,11 +40,47 @@ from .models import (
|
|
|
34
40
|
Portfolio,
|
|
35
41
|
Profile,
|
|
36
42
|
SponsoredProductCampaign,
|
|
43
|
+
AirbyteSearchHit,
|
|
44
|
+
AirbyteSearchResult,
|
|
45
|
+
ProfilesSearchData,
|
|
46
|
+
ProfilesSearchResult,
|
|
37
47
|
)
|
|
38
48
|
|
|
39
49
|
# TypeVar for decorator type preservation
|
|
40
50
|
_F = TypeVar("_F", bound=Callable[..., Any])
|
|
41
51
|
|
|
52
|
+
DEFAULT_MAX_OUTPUT_CHARS = 50_000 # ~50KB default, configurable per-tool
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _raise_output_too_large(message: str) -> None:
|
|
56
|
+
try:
|
|
57
|
+
from pydantic_ai import ModelRetry # type: ignore[import-not-found]
|
|
58
|
+
except Exception as exc:
|
|
59
|
+
raise RuntimeError(message) from exc
|
|
60
|
+
raise ModelRetry(message)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _check_output_size(result: Any, max_chars: int | None, tool_name: str) -> Any:
|
|
64
|
+
if max_chars is None or max_chars <= 0:
|
|
65
|
+
return result
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
serialized = json.dumps(result, default=str)
|
|
69
|
+
except (TypeError, ValueError):
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
if len(serialized) > max_chars:
|
|
73
|
+
truncated_preview = serialized[:500] + "..." if len(serialized) > 500 else serialized
|
|
74
|
+
_raise_output_too_large(
|
|
75
|
+
f"Tool '{tool_name}' output too large ({len(serialized):,} chars, limit {max_chars:,}). "
|
|
76
|
+
"Please narrow your query by: using the 'fields' parameter to select only needed fields, "
|
|
77
|
+
"adding filters, or reducing the 'limit'. "
|
|
78
|
+
f"Preview: {truncated_preview}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return result
|
|
82
|
+
|
|
83
|
+
|
|
42
84
|
|
|
43
85
|
|
|
44
86
|
class AmazonAdsConnector:
|
|
@@ -49,7 +91,7 @@ class AmazonAdsConnector:
|
|
|
49
91
|
"""
|
|
50
92
|
|
|
51
93
|
connector_name = "amazon-ads"
|
|
52
|
-
connector_version = "1.0.
|
|
94
|
+
connector_version = "1.0.3"
|
|
53
95
|
vendored_sdk_version = "0.1.0" # Version of vendored connector-sdk
|
|
54
96
|
|
|
55
97
|
# Map of (entity, action) -> needs_envelope for envelope wrapping decision
|
|
@@ -80,7 +122,7 @@ class AmazonAdsConnector:
|
|
|
80
122
|
airbyte_client_id: str | None = None,
|
|
81
123
|
airbyte_client_secret: str | None = None,
|
|
82
124
|
on_token_refresh: Any | None = None,
|
|
83
|
-
|
|
125
|
+
region: str | None = None ):
|
|
84
126
|
"""
|
|
85
127
|
Initialize a new amazon-ads connector instance.
|
|
86
128
|
|
|
@@ -95,7 +137,7 @@ class AmazonAdsConnector:
|
|
|
95
137
|
airbyte_client_secret: Airbyte OAuth client secret (required for hosted mode)
|
|
96
138
|
on_token_refresh: Optional callback for OAuth2 token refresh persistence.
|
|
97
139
|
Called with new_tokens dict when tokens are refreshed. Can be sync or async.
|
|
98
|
-
Example: lambda tokens: save_to_database(tokens)
|
|
140
|
+
Example: lambda tokens: save_to_database(tokens) region: The Amazon Ads API endpoint URL based on region:
|
|
99
141
|
- NA (North America): https://advertising-api.amazon.com
|
|
100
142
|
- EU (Europe): https://advertising-api-eu.amazon.com
|
|
101
143
|
- FE (Far East): https://advertising-api-fe.amazon.com
|
|
@@ -142,8 +184,8 @@ class AmazonAdsConnector:
|
|
|
142
184
|
|
|
143
185
|
# Build config_values dict from server variables
|
|
144
186
|
config_values: dict[str, str] = {}
|
|
145
|
-
if
|
|
146
|
-
config_values["
|
|
187
|
+
if region:
|
|
188
|
+
config_values["region"] = region
|
|
147
189
|
|
|
148
190
|
self._executor = LocalExecutor(
|
|
149
191
|
model=AmazonAdsConnectorModel,
|
|
@@ -154,8 +196,8 @@ class AmazonAdsConnector:
|
|
|
154
196
|
|
|
155
197
|
# Update base_url with server variables if provided
|
|
156
198
|
base_url = self._executor.http_client.base_url
|
|
157
|
-
if
|
|
158
|
-
base_url = base_url.replace("{
|
|
199
|
+
if region:
|
|
200
|
+
base_url = base_url.replace("{region}", region)
|
|
159
201
|
self._executor.http_client.base_url = base_url
|
|
160
202
|
|
|
161
203
|
# Initialize entity query objects
|
|
@@ -218,15 +260,15 @@ class AmazonAdsConnector:
|
|
|
218
260
|
async def execute(
|
|
219
261
|
self,
|
|
220
262
|
entity: str,
|
|
221
|
-
action:
|
|
222
|
-
params:
|
|
263
|
+
action: Literal["list", "get", "search"],
|
|
264
|
+
params: Mapping[str, Any]
|
|
223
265
|
) -> AmazonAdsExecuteResult[Any] | AmazonAdsExecuteResultWithMeta[Any, Any] | Any: ...
|
|
224
266
|
|
|
225
267
|
async def execute(
|
|
226
268
|
self,
|
|
227
269
|
entity: str,
|
|
228
|
-
action:
|
|
229
|
-
params:
|
|
270
|
+
action: Literal["list", "get", "search"],
|
|
271
|
+
params: Mapping[str, Any] | None = None
|
|
230
272
|
) -> Any:
|
|
231
273
|
"""
|
|
232
274
|
Execute an entity operation with full type safety.
|
|
@@ -254,16 +296,17 @@ class AmazonAdsConnector:
|
|
|
254
296
|
from ._vendored.connector_sdk.executor import ExecutionConfig
|
|
255
297
|
|
|
256
298
|
# Remap parameter names from snake_case (TypedDict keys) to API parameter names
|
|
257
|
-
if params
|
|
299
|
+
resolved_params = dict(params) if params is not None else None
|
|
300
|
+
if resolved_params:
|
|
258
301
|
param_map = self._PARAM_MAP.get((entity, action), {})
|
|
259
302
|
if param_map:
|
|
260
|
-
|
|
303
|
+
resolved_params = {param_map.get(k, k): v for k, v in resolved_params.items()}
|
|
261
304
|
|
|
262
305
|
# Use ExecutionConfig for both local and hosted executors
|
|
263
306
|
config = ExecutionConfig(
|
|
264
307
|
entity=entity,
|
|
265
308
|
action=action,
|
|
266
|
-
params=
|
|
309
|
+
params=resolved_params
|
|
267
310
|
)
|
|
268
311
|
|
|
269
312
|
result = await self._executor.execute(config)
|
|
@@ -290,41 +333,67 @@ class AmazonAdsConnector:
|
|
|
290
333
|
# ===== INTROSPECTION METHODS =====
|
|
291
334
|
|
|
292
335
|
@classmethod
|
|
293
|
-
def
|
|
336
|
+
def tool_utils(
|
|
337
|
+
cls,
|
|
338
|
+
func: _F | None = None,
|
|
339
|
+
*,
|
|
340
|
+
update_docstring: bool = True,
|
|
341
|
+
max_output_chars: int | None = DEFAULT_MAX_OUTPUT_CHARS,
|
|
342
|
+
) -> _F | Callable[[_F], _F]:
|
|
294
343
|
"""
|
|
295
|
-
Decorator that
|
|
296
|
-
|
|
297
|
-
This class method can be used as a decorator to automatically generate
|
|
298
|
-
comprehensive documentation for AI tool functions.
|
|
344
|
+
Decorator that adds tool utilities like docstring augmentation and output limits.
|
|
299
345
|
|
|
300
346
|
Usage:
|
|
301
347
|
@mcp.tool()
|
|
302
|
-
@AmazonAdsConnector.
|
|
348
|
+
@AmazonAdsConnector.tool_utils
|
|
303
349
|
async def execute(entity: str, action: str, params: dict):
|
|
304
|
-
'''Execute operations.'''
|
|
305
350
|
...
|
|
306
351
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
- Example questions (if available in OpenAPI spec)
|
|
352
|
+
@mcp.tool()
|
|
353
|
+
@AmazonAdsConnector.tool_utils(update_docstring=False, max_output_chars=None)
|
|
354
|
+
async def execute(entity: str, action: str, params: dict):
|
|
355
|
+
...
|
|
312
356
|
|
|
313
357
|
Args:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
Returns:
|
|
317
|
-
The same function with updated __doc__
|
|
358
|
+
update_docstring: When True, append connector capabilities to __doc__.
|
|
359
|
+
max_output_chars: Max serialized output size before raising. Use None to disable.
|
|
318
360
|
"""
|
|
319
|
-
description = generate_tool_description(AmazonAdsConnectorModel)
|
|
320
361
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
362
|
+
def decorate(inner: _F) -> _F:
|
|
363
|
+
if update_docstring:
|
|
364
|
+
description = generate_tool_description(AmazonAdsConnectorModel)
|
|
365
|
+
original_doc = inner.__doc__ or ""
|
|
366
|
+
if original_doc.strip():
|
|
367
|
+
full_doc = f"{original_doc.strip()}\n{description}"
|
|
368
|
+
else:
|
|
369
|
+
full_doc = description
|
|
370
|
+
else:
|
|
371
|
+
full_doc = ""
|
|
326
372
|
|
|
327
|
-
|
|
373
|
+
if inspect.iscoroutinefunction(inner):
|
|
374
|
+
|
|
375
|
+
@wraps(inner)
|
|
376
|
+
async def aw(*args: Any, **kwargs: Any) -> Any:
|
|
377
|
+
result = await inner(*args, **kwargs)
|
|
378
|
+
return _check_output_size(result, max_output_chars, inner.__name__)
|
|
379
|
+
|
|
380
|
+
wrapped = aw
|
|
381
|
+
else:
|
|
382
|
+
|
|
383
|
+
@wraps(inner)
|
|
384
|
+
def sw(*args: Any, **kwargs: Any) -> Any:
|
|
385
|
+
result = inner(*args, **kwargs)
|
|
386
|
+
return _check_output_size(result, max_output_chars, inner.__name__)
|
|
387
|
+
|
|
388
|
+
wrapped = sw
|
|
389
|
+
|
|
390
|
+
if update_docstring:
|
|
391
|
+
wrapped.__doc__ = full_doc
|
|
392
|
+
return wrapped # type: ignore[return-value]
|
|
393
|
+
|
|
394
|
+
if func is not None:
|
|
395
|
+
return decorate(func)
|
|
396
|
+
return decorate
|
|
328
397
|
|
|
329
398
|
def list_entities(self) -> list[dict[str, Any]]:
|
|
330
399
|
"""
|
|
@@ -441,6 +510,65 @@ information about the advertiser's account in a specific marketplace.
|
|
|
441
510
|
|
|
442
511
|
|
|
443
512
|
|
|
513
|
+
async def search(
|
|
514
|
+
self,
|
|
515
|
+
query: ProfilesSearchQuery,
|
|
516
|
+
limit: int | None = None,
|
|
517
|
+
cursor: str | None = None,
|
|
518
|
+
fields: list[list[str]] | None = None,
|
|
519
|
+
) -> ProfilesSearchResult:
|
|
520
|
+
"""
|
|
521
|
+
Search profiles records from Airbyte cache.
|
|
522
|
+
|
|
523
|
+
This operation searches cached data from Airbyte syncs.
|
|
524
|
+
Only available in hosted execution mode.
|
|
525
|
+
|
|
526
|
+
Available filter fields (ProfilesSearchFilter):
|
|
527
|
+
- account_info:
|
|
528
|
+
- country_code:
|
|
529
|
+
- currency_code:
|
|
530
|
+
- daily_budget:
|
|
531
|
+
- profile_id:
|
|
532
|
+
- timezone:
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
query: Filter and sort conditions. Supports operators like eq, neq, gt, gte, lt, lte,
|
|
536
|
+
in, like, fuzzy, keyword, not, and, or. Example: {"filter": {"eq": {"status": "active"}}}
|
|
537
|
+
limit: Maximum results to return (default 1000)
|
|
538
|
+
cursor: Pagination cursor from previous response's next_cursor
|
|
539
|
+
fields: Field paths to include in results. Each path is a list of keys for nested access.
|
|
540
|
+
Example: [["id"], ["user", "name"]] returns id and user.name fields.
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
ProfilesSearchResult with hits (list of AirbyteSearchHit[ProfilesSearchData]) and pagination info
|
|
544
|
+
|
|
545
|
+
Raises:
|
|
546
|
+
NotImplementedError: If called in local execution mode
|
|
547
|
+
"""
|
|
548
|
+
params: dict[str, Any] = {"query": query}
|
|
549
|
+
if limit is not None:
|
|
550
|
+
params["limit"] = limit
|
|
551
|
+
if cursor is not None:
|
|
552
|
+
params["cursor"] = cursor
|
|
553
|
+
if fields is not None:
|
|
554
|
+
params["fields"] = fields
|
|
555
|
+
|
|
556
|
+
result = await self._connector.execute("profiles", "search", params)
|
|
557
|
+
|
|
558
|
+
# Parse response into typed result
|
|
559
|
+
return ProfilesSearchResult(
|
|
560
|
+
hits=[
|
|
561
|
+
AirbyteSearchHit[ProfilesSearchData](
|
|
562
|
+
id=hit.get("id"),
|
|
563
|
+
score=hit.get("score"),
|
|
564
|
+
data=ProfilesSearchData(**hit.get("data", {}))
|
|
565
|
+
)
|
|
566
|
+
for hit in result.get("hits", [])
|
|
567
|
+
],
|
|
568
|
+
next_cursor=result.get("next_cursor"),
|
|
569
|
+
took_ms=result.get("took_ms")
|
|
570
|
+
)
|
|
571
|
+
|
|
444
572
|
class PortfoliosQuery:
|
|
445
573
|
"""
|
|
446
574
|
Query class for Portfolios entity operations.
|