adcp 1.3.0__tar.gz → 1.4.0__tar.gz
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.
- {adcp-1.3.0/src/adcp.egg-info → adcp-1.4.0}/PKG-INFO +46 -18
- {adcp-1.3.0 → adcp-1.4.0}/README.md +45 -17
- {adcp-1.3.0 → adcp-1.4.0}/pyproject.toml +1 -1
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/__init__.py +9 -1
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/client.py +5 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/exceptions.py +34 -0
- adcp-1.4.0/src/adcp/simple.py +347 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/testing/__init__.py +15 -0
- {adcp-1.3.0 → adcp-1.4.0/src/adcp.egg-info}/PKG-INFO +46 -18
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp.egg-info/SOURCES.txt +3 -1
- adcp-1.4.0/tests/test_simple_api.py +183 -0
- {adcp-1.3.0 → adcp-1.4.0}/LICENSE +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/setup.cfg +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/__main__.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/config.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/protocols/__init__.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/protocols/a2a.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/protocols/base.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/protocols/mcp.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/testing/test_helpers.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/types/__init__.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/types/core.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/types/generated.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/types/tasks.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/utils/__init__.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/utils/operation_id.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/utils/preview_cache.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp/utils/response_parser.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp.egg-info/dependency_links.txt +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp.egg-info/entry_points.txt +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp.egg-info/requires.txt +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/src/adcp.egg-info/top_level.txt +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/tests/test_cli.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/tests/test_client.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/tests/test_code_generation.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/tests/test_discriminated_unions.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/tests/test_format_id_validation.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/tests/test_helpers.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/tests/test_preview_html.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/tests/test_protocols.py +0 -0
- {adcp-1.3.0 → adcp-1.4.0}/tests/test_response_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: adcp
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Official Python client for the Ad Context Protocol (AdCP)
|
|
5
5
|
Author-email: AdCP Community <maintainers@adcontextprotocol.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -63,36 +63,64 @@ pip install adcp
|
|
|
63
63
|
|
|
64
64
|
## Quick Start: Test Helpers
|
|
65
65
|
|
|
66
|
-
The fastest way to get started is using
|
|
66
|
+
The fastest way to get started is using pre-configured test agents with the **`.simple` API**:
|
|
67
67
|
|
|
68
68
|
```python
|
|
69
69
|
from adcp.testing import test_agent
|
|
70
|
-
from adcp.types.generated import GetProductsRequest
|
|
71
70
|
|
|
72
|
-
# Zero configuration - just import and
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
brief="Coffee subscription service",
|
|
76
|
-
promoted_offering="Premium coffee deliveries"
|
|
77
|
-
)
|
|
71
|
+
# Zero configuration - just import and call with kwargs!
|
|
72
|
+
products = await test_agent.simple.get_products(
|
|
73
|
+
brief='Coffee subscription service for busy professionals'
|
|
78
74
|
)
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
print(f"Found {len(products.products)} products")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Simple vs. Standard API
|
|
80
|
+
|
|
81
|
+
**Every ADCPClient** includes both API styles via the `.simple` accessor:
|
|
82
|
+
|
|
83
|
+
**Simple API** (`client.simple.*`) - Recommended for examples/prototyping:
|
|
84
|
+
```python
|
|
85
|
+
from adcp.testing import test_agent
|
|
86
|
+
|
|
87
|
+
# Kwargs and direct return - raises on error
|
|
88
|
+
products = await test_agent.simple.get_products(brief='Coffee brands')
|
|
89
|
+
print(products.products[0].name)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Standard API** (`client.*`) - Recommended for production:
|
|
93
|
+
```python
|
|
94
|
+
from adcp.testing import test_agent
|
|
95
|
+
from adcp.types.generated import GetProductsRequest
|
|
96
|
+
|
|
97
|
+
# Explicit request objects and TaskResult wrapper
|
|
98
|
+
request = GetProductsRequest(brief='Coffee brands')
|
|
99
|
+
result = await test_agent.get_products(request)
|
|
100
|
+
|
|
101
|
+
if result.success and result.data:
|
|
102
|
+
print(result.data.products[0].name)
|
|
103
|
+
else:
|
|
104
|
+
print(f"Error: {result.error}")
|
|
82
105
|
```
|
|
83
106
|
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
**When to use which:**
|
|
108
|
+
- **Simple API** (`.simple`): Quick testing, documentation, examples, notebooks
|
|
109
|
+
- **Standard API**: Production code, complex error handling, webhook workflows
|
|
110
|
+
|
|
111
|
+
### Available Test Helpers
|
|
112
|
+
|
|
113
|
+
Pre-configured agents (all include `.simple` accessor):
|
|
114
|
+
- **`test_agent`**: MCP test agent with authentication
|
|
115
|
+
- **`test_agent_a2a`**: A2A test agent with authentication
|
|
116
|
+
- **`test_agent_no_auth`**: MCP test agent without authentication
|
|
117
|
+
- **`test_agent_a2a_no_auth`**: A2A test agent without authentication
|
|
89
118
|
- **`creative_agent`**: Reference creative agent for preview functionality
|
|
90
119
|
- **`test_agent_client`**: Multi-agent client with both protocols
|
|
91
|
-
- **`create_test_agent()`**: Factory for custom test configurations
|
|
92
120
|
|
|
93
121
|
> **Note**: Test agents are rate-limited and for testing/examples only. DO NOT use in production.
|
|
94
122
|
|
|
95
|
-
See [examples/
|
|
123
|
+
See [examples/simple_api_demo.py](examples/simple_api_demo.py) for a complete comparison.
|
|
96
124
|
|
|
97
125
|
## Quick Start: Distributed Operations
|
|
98
126
|
|
|
@@ -26,36 +26,64 @@ pip install adcp
|
|
|
26
26
|
|
|
27
27
|
## Quick Start: Test Helpers
|
|
28
28
|
|
|
29
|
-
The fastest way to get started is using
|
|
29
|
+
The fastest way to get started is using pre-configured test agents with the **`.simple` API**:
|
|
30
30
|
|
|
31
31
|
```python
|
|
32
32
|
from adcp.testing import test_agent
|
|
33
|
-
from adcp.types.generated import GetProductsRequest
|
|
34
33
|
|
|
35
|
-
# Zero configuration - just import and
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
brief="Coffee subscription service",
|
|
39
|
-
promoted_offering="Premium coffee deliveries"
|
|
40
|
-
)
|
|
34
|
+
# Zero configuration - just import and call with kwargs!
|
|
35
|
+
products = await test_agent.simple.get_products(
|
|
36
|
+
brief='Coffee subscription service for busy professionals'
|
|
41
37
|
)
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
print(f"Found {len(products.products)} products")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Simple vs. Standard API
|
|
43
|
+
|
|
44
|
+
**Every ADCPClient** includes both API styles via the `.simple` accessor:
|
|
45
|
+
|
|
46
|
+
**Simple API** (`client.simple.*`) - Recommended for examples/prototyping:
|
|
47
|
+
```python
|
|
48
|
+
from adcp.testing import test_agent
|
|
49
|
+
|
|
50
|
+
# Kwargs and direct return - raises on error
|
|
51
|
+
products = await test_agent.simple.get_products(brief='Coffee brands')
|
|
52
|
+
print(products.products[0].name)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Standard API** (`client.*`) - Recommended for production:
|
|
56
|
+
```python
|
|
57
|
+
from adcp.testing import test_agent
|
|
58
|
+
from adcp.types.generated import GetProductsRequest
|
|
59
|
+
|
|
60
|
+
# Explicit request objects and TaskResult wrapper
|
|
61
|
+
request = GetProductsRequest(brief='Coffee brands')
|
|
62
|
+
result = await test_agent.get_products(request)
|
|
63
|
+
|
|
64
|
+
if result.success and result.data:
|
|
65
|
+
print(result.data.products[0].name)
|
|
66
|
+
else:
|
|
67
|
+
print(f"Error: {result.error}")
|
|
45
68
|
```
|
|
46
69
|
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
**When to use which:**
|
|
71
|
+
- **Simple API** (`.simple`): Quick testing, documentation, examples, notebooks
|
|
72
|
+
- **Standard API**: Production code, complex error handling, webhook workflows
|
|
73
|
+
|
|
74
|
+
### Available Test Helpers
|
|
75
|
+
|
|
76
|
+
Pre-configured agents (all include `.simple` accessor):
|
|
77
|
+
- **`test_agent`**: MCP test agent with authentication
|
|
78
|
+
- **`test_agent_a2a`**: A2A test agent with authentication
|
|
79
|
+
- **`test_agent_no_auth`**: MCP test agent without authentication
|
|
80
|
+
- **`test_agent_a2a_no_auth`**: A2A test agent without authentication
|
|
52
81
|
- **`creative_agent`**: Reference creative agent for preview functionality
|
|
53
82
|
- **`test_agent_client`**: Multi-agent client with both protocols
|
|
54
|
-
- **`create_test_agent()`**: Factory for custom test configurations
|
|
55
83
|
|
|
56
84
|
> **Note**: Test agents are rate-limited and for testing/examples only. DO NOT use in production.
|
|
57
85
|
|
|
58
|
-
See [examples/
|
|
86
|
+
See [examples/simple_api_demo.py](examples/simple_api_demo.py) for a complete comparison.
|
|
59
87
|
|
|
60
88
|
## Quick Start: Distributed Operations
|
|
61
89
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "adcp"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.4.0"
|
|
8
8
|
description = "Official Python client for the Ad Context Protocol (AdCP)"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "AdCP Community", email = "maintainers@adcontextprotocol.org"}
|
|
@@ -23,13 +23,17 @@ from adcp.exceptions import (
|
|
|
23
23
|
from adcp.testing import (
|
|
24
24
|
CREATIVE_AGENT_CONFIG,
|
|
25
25
|
TEST_AGENT_A2A_CONFIG,
|
|
26
|
+
TEST_AGENT_A2A_NO_AUTH_CONFIG,
|
|
26
27
|
TEST_AGENT_MCP_CONFIG,
|
|
28
|
+
TEST_AGENT_MCP_NO_AUTH_CONFIG,
|
|
27
29
|
TEST_AGENT_TOKEN,
|
|
28
30
|
create_test_agent,
|
|
29
31
|
creative_agent,
|
|
30
32
|
test_agent,
|
|
31
33
|
test_agent_a2a,
|
|
34
|
+
test_agent_a2a_no_auth,
|
|
32
35
|
test_agent_client,
|
|
36
|
+
test_agent_no_auth,
|
|
33
37
|
)
|
|
34
38
|
from adcp.types.core import AgentConfig, Protocol, TaskResult, TaskStatus, WebhookMetadata
|
|
35
39
|
from adcp.types.generated import (
|
|
@@ -146,7 +150,7 @@ from adcp.types.generated import (
|
|
|
146
150
|
TaskStatus as GeneratedTaskStatus,
|
|
147
151
|
)
|
|
148
152
|
|
|
149
|
-
__version__ = "1.
|
|
153
|
+
__version__ = "1.4.0"
|
|
150
154
|
|
|
151
155
|
__all__ = [
|
|
152
156
|
# Client classes
|
|
@@ -161,12 +165,16 @@ __all__ = [
|
|
|
161
165
|
# Test helpers
|
|
162
166
|
"test_agent",
|
|
163
167
|
"test_agent_a2a",
|
|
168
|
+
"test_agent_no_auth",
|
|
169
|
+
"test_agent_a2a_no_auth",
|
|
164
170
|
"creative_agent",
|
|
165
171
|
"test_agent_client",
|
|
166
172
|
"create_test_agent",
|
|
167
173
|
"TEST_AGENT_TOKEN",
|
|
168
174
|
"TEST_AGENT_MCP_CONFIG",
|
|
169
175
|
"TEST_AGENT_A2A_CONFIG",
|
|
176
|
+
"TEST_AGENT_MCP_NO_AUTH_CONFIG",
|
|
177
|
+
"TEST_AGENT_A2A_NO_AUTH_CONFIG",
|
|
170
178
|
"CREATIVE_AGENT_CONFIG",
|
|
171
179
|
# Exceptions
|
|
172
180
|
"ADCPError",
|
|
@@ -86,6 +86,11 @@ class ADCPClient:
|
|
|
86
86
|
else:
|
|
87
87
|
raise ValueError(f"Unsupported protocol: {agent_config.protocol}")
|
|
88
88
|
|
|
89
|
+
# Initialize simple API accessor (lazy import to avoid circular dependency)
|
|
90
|
+
from adcp.simple import SimpleAPI
|
|
91
|
+
|
|
92
|
+
self.simple = SimpleAPI(self)
|
|
93
|
+
|
|
89
94
|
def get_webhook_url(self, task_type: str, operation_id: str) -> str:
|
|
90
95
|
"""Generate webhook URL for a task."""
|
|
91
96
|
if not self.webhook_url_template:
|
|
@@ -119,3 +119,37 @@ class ADCPWebhookSignatureError(ADCPWebhookError):
|
|
|
119
119
|
" Webhook signatures use HMAC-SHA256 for security."
|
|
120
120
|
)
|
|
121
121
|
super().__init__(message, agent_id, None, suggestion)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ADCPSimpleAPIError(ADCPError):
|
|
125
|
+
"""Error from simplified API (.simple accessor).
|
|
126
|
+
|
|
127
|
+
Raised when a simple API method fails. The underlying error details
|
|
128
|
+
are available in the message. For more control over error handling,
|
|
129
|
+
use the standard API (client.method()) instead of client.simple.method().
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def __init__(
|
|
133
|
+
self,
|
|
134
|
+
operation: str,
|
|
135
|
+
error_message: str | None = None,
|
|
136
|
+
agent_id: str | None = None,
|
|
137
|
+
):
|
|
138
|
+
"""Initialize simple API error.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
operation: The operation that failed (e.g., "get_products")
|
|
142
|
+
error_message: The underlying error message from TaskResult
|
|
143
|
+
agent_id: Optional agent ID for context
|
|
144
|
+
"""
|
|
145
|
+
message = f"{operation} failed"
|
|
146
|
+
if error_message:
|
|
147
|
+
message = f"{message}: {error_message}"
|
|
148
|
+
|
|
149
|
+
suggestion = (
|
|
150
|
+
f"For more control over error handling, use the standard API:\n"
|
|
151
|
+
f" result = await client.{operation}(request)\n"
|
|
152
|
+
f" if not result.success:\n"
|
|
153
|
+
f" # Handle error with full TaskResult context"
|
|
154
|
+
)
|
|
155
|
+
super().__init__(message, agent_id, None, suggestion)
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""Simplified API accessor for ADCPClient.
|
|
2
|
+
|
|
3
|
+
Provides an ergonomic API with:
|
|
4
|
+
- Kwargs instead of request objects
|
|
5
|
+
- Direct return values (no TaskResult unwrapping)
|
|
6
|
+
- Raises exceptions on errors
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
client = ADCPClient(config)
|
|
10
|
+
|
|
11
|
+
# Standard API: full control
|
|
12
|
+
result = await client.get_products(GetProductsRequest(brief="Coffee"))
|
|
13
|
+
if result.success:
|
|
14
|
+
print(result.data.products)
|
|
15
|
+
|
|
16
|
+
# Simple API: ergonomic
|
|
17
|
+
products = await client.simple.get_products(brief="Coffee")
|
|
18
|
+
print(products.products)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import TYPE_CHECKING, Any
|
|
24
|
+
|
|
25
|
+
from adcp.exceptions import ADCPSimpleAPIError
|
|
26
|
+
from adcp.types.generated import (
|
|
27
|
+
ActivateSignalRequest,
|
|
28
|
+
ActivateSignalResponse,
|
|
29
|
+
GetMediaBuyDeliveryRequest,
|
|
30
|
+
GetMediaBuyDeliveryResponse,
|
|
31
|
+
GetProductsRequest,
|
|
32
|
+
GetProductsResponse,
|
|
33
|
+
GetSignalsRequest,
|
|
34
|
+
GetSignalsResponse,
|
|
35
|
+
ListAuthorizedPropertiesRequest,
|
|
36
|
+
ListAuthorizedPropertiesResponse,
|
|
37
|
+
ListCreativeFormatsRequest,
|
|
38
|
+
ListCreativeFormatsResponse,
|
|
39
|
+
ListCreativesRequest,
|
|
40
|
+
ListCreativesResponse,
|
|
41
|
+
PreviewCreativeRequest,
|
|
42
|
+
PreviewCreativeResponse,
|
|
43
|
+
ProvidePerformanceFeedbackRequest,
|
|
44
|
+
ProvidePerformanceFeedbackResponse,
|
|
45
|
+
SyncCreativesRequest,
|
|
46
|
+
SyncCreativesResponse,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from adcp.client import ADCPClient
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SimpleAPI:
|
|
54
|
+
"""Simplified API accessor for ergonomic usage.
|
|
55
|
+
|
|
56
|
+
Provides kwargs-based methods that return unwrapped response data
|
|
57
|
+
and raise exceptions on errors.
|
|
58
|
+
|
|
59
|
+
This is intended for:
|
|
60
|
+
- Quick prototyping and testing
|
|
61
|
+
- Documentation and examples
|
|
62
|
+
- Simple scripts and notebooks
|
|
63
|
+
|
|
64
|
+
For production code with complex error handling, use the standard
|
|
65
|
+
client API which returns TaskResult wrappers.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, client: ADCPClient):
|
|
69
|
+
"""Initialize simple API accessor.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
client: The ADCPClient instance to wrap
|
|
73
|
+
"""
|
|
74
|
+
self._client = client
|
|
75
|
+
|
|
76
|
+
async def get_products(
|
|
77
|
+
self,
|
|
78
|
+
**kwargs: Any,
|
|
79
|
+
) -> GetProductsResponse:
|
|
80
|
+
"""Get advertising products (simplified).
|
|
81
|
+
|
|
82
|
+
This is a convenience wrapper around client.get_products() that:
|
|
83
|
+
- Accepts kwargs instead of GetProductsRequest
|
|
84
|
+
- Returns unwrapped GetProductsResponse
|
|
85
|
+
- Raises ADCPSimpleAPIError on failures
|
|
86
|
+
|
|
87
|
+
For full control over error handling, use client.get_products() instead.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
**kwargs: Arguments for GetProductsRequest (brief, brand_manifest, etc.)
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
GetProductsResponse directly (no TaskResult wrapper)
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ADCPSimpleAPIError: If request fails. Use standard API for detailed error handling.
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
products = await client.simple.get_products(
|
|
100
|
+
brief='Coffee subscription service'
|
|
101
|
+
)
|
|
102
|
+
print(f"Found {len(products.products)} products")
|
|
103
|
+
"""
|
|
104
|
+
request = GetProductsRequest(**kwargs)
|
|
105
|
+
result = await self._client.get_products(request)
|
|
106
|
+
if not result.success or not result.data:
|
|
107
|
+
raise ADCPSimpleAPIError(
|
|
108
|
+
operation="get_products",
|
|
109
|
+
error_message=result.error,
|
|
110
|
+
agent_id=self._client.agent_config.id,
|
|
111
|
+
)
|
|
112
|
+
return result.data
|
|
113
|
+
|
|
114
|
+
async def list_creative_formats(
|
|
115
|
+
self,
|
|
116
|
+
**kwargs: Any,
|
|
117
|
+
) -> ListCreativeFormatsResponse:
|
|
118
|
+
"""List supported creative formats.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
**kwargs: Arguments passed to ListCreativeFormatsRequest
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
ListCreativeFormatsResponse with formats list
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
Exception: If the request fails
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
formats = await client.simple.list_creative_formats()
|
|
131
|
+
print(f"Found {len(formats.formats)} formats")
|
|
132
|
+
"""
|
|
133
|
+
request = ListCreativeFormatsRequest(**kwargs)
|
|
134
|
+
result = await self._client.list_creative_formats(request)
|
|
135
|
+
if not result.success or not result.data:
|
|
136
|
+
raise ADCPSimpleAPIError(
|
|
137
|
+
operation="list_creative_formats",
|
|
138
|
+
error_message=result.error,
|
|
139
|
+
agent_id=self._client.agent_config.id,
|
|
140
|
+
)
|
|
141
|
+
return result.data
|
|
142
|
+
|
|
143
|
+
async def preview_creative(
|
|
144
|
+
self,
|
|
145
|
+
**kwargs: Any,
|
|
146
|
+
) -> PreviewCreativeResponse:
|
|
147
|
+
"""Preview creative manifest.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
**kwargs: Arguments passed to PreviewCreativeRequest
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
PreviewCreativeResponse with preview data
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
Exception: If the request fails
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
preview = await client.simple.preview_creative(
|
|
160
|
+
manifest={'format_id': 'banner_300x250', 'assets': {...}}
|
|
161
|
+
)
|
|
162
|
+
print(f"Preview: {preview.previews[0]}")
|
|
163
|
+
"""
|
|
164
|
+
request = PreviewCreativeRequest(**kwargs)
|
|
165
|
+
result = await self._client.preview_creative(request)
|
|
166
|
+
if not result.success or not result.data:
|
|
167
|
+
raise ADCPSimpleAPIError(
|
|
168
|
+
operation="preview_creative",
|
|
169
|
+
error_message=result.error,
|
|
170
|
+
agent_id=self._client.agent_config.id,
|
|
171
|
+
)
|
|
172
|
+
return result.data
|
|
173
|
+
|
|
174
|
+
async def sync_creatives(
|
|
175
|
+
self,
|
|
176
|
+
**kwargs: Any,
|
|
177
|
+
) -> SyncCreativesResponse:
|
|
178
|
+
"""Sync creatives.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
**kwargs: Arguments passed to SyncCreativesRequest
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
SyncCreativesResponse
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
Exception: If the request fails
|
|
188
|
+
"""
|
|
189
|
+
request = SyncCreativesRequest(**kwargs)
|
|
190
|
+
result = await self._client.sync_creatives(request)
|
|
191
|
+
if not result.success or not result.data:
|
|
192
|
+
raise ADCPSimpleAPIError(
|
|
193
|
+
operation="sync_creatives",
|
|
194
|
+
error_message=result.error,
|
|
195
|
+
agent_id=self._client.agent_config.id,
|
|
196
|
+
)
|
|
197
|
+
return result.data
|
|
198
|
+
|
|
199
|
+
async def list_creatives(
|
|
200
|
+
self,
|
|
201
|
+
**kwargs: Any,
|
|
202
|
+
) -> ListCreativesResponse:
|
|
203
|
+
"""List creatives.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
**kwargs: Arguments passed to ListCreativesRequest
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
ListCreativesResponse
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
Exception: If the request fails
|
|
213
|
+
"""
|
|
214
|
+
request = ListCreativesRequest(**kwargs)
|
|
215
|
+
result = await self._client.list_creatives(request)
|
|
216
|
+
if not result.success or not result.data:
|
|
217
|
+
raise ADCPSimpleAPIError(
|
|
218
|
+
operation="list_creatives",
|
|
219
|
+
error_message=result.error,
|
|
220
|
+
agent_id=self._client.agent_config.id,
|
|
221
|
+
)
|
|
222
|
+
return result.data
|
|
223
|
+
|
|
224
|
+
async def get_media_buy_delivery(
|
|
225
|
+
self,
|
|
226
|
+
**kwargs: Any,
|
|
227
|
+
) -> GetMediaBuyDeliveryResponse:
|
|
228
|
+
"""Get media buy delivery.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
**kwargs: Arguments passed to GetMediaBuyDeliveryRequest
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
GetMediaBuyDeliveryResponse
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
Exception: If the request fails
|
|
238
|
+
"""
|
|
239
|
+
request = GetMediaBuyDeliveryRequest(**kwargs)
|
|
240
|
+
result = await self._client.get_media_buy_delivery(request)
|
|
241
|
+
if not result.success or not result.data:
|
|
242
|
+
raise ADCPSimpleAPIError(
|
|
243
|
+
operation="get_media_buy_delivery",
|
|
244
|
+
error_message=result.error,
|
|
245
|
+
agent_id=self._client.agent_config.id,
|
|
246
|
+
)
|
|
247
|
+
return result.data
|
|
248
|
+
|
|
249
|
+
async def list_authorized_properties(
|
|
250
|
+
self,
|
|
251
|
+
**kwargs: Any,
|
|
252
|
+
) -> ListAuthorizedPropertiesResponse:
|
|
253
|
+
"""List authorized properties.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
**kwargs: Arguments passed to ListAuthorizedPropertiesRequest
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
ListAuthorizedPropertiesResponse
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
Exception: If the request fails
|
|
263
|
+
"""
|
|
264
|
+
request = ListAuthorizedPropertiesRequest(**kwargs)
|
|
265
|
+
result = await self._client.list_authorized_properties(request)
|
|
266
|
+
if not result.success or not result.data:
|
|
267
|
+
raise ADCPSimpleAPIError(
|
|
268
|
+
operation="list_authorized_properties",
|
|
269
|
+
error_message=result.error,
|
|
270
|
+
agent_id=self._client.agent_config.id,
|
|
271
|
+
)
|
|
272
|
+
return result.data
|
|
273
|
+
|
|
274
|
+
async def get_signals(
|
|
275
|
+
self,
|
|
276
|
+
**kwargs: Any,
|
|
277
|
+
) -> GetSignalsResponse:
|
|
278
|
+
"""Get signals.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
**kwargs: Arguments passed to GetSignalsRequest
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
GetSignalsResponse
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
Exception: If the request fails
|
|
288
|
+
"""
|
|
289
|
+
request = GetSignalsRequest(**kwargs)
|
|
290
|
+
result = await self._client.get_signals(request)
|
|
291
|
+
if not result.success or not result.data:
|
|
292
|
+
raise ADCPSimpleAPIError(
|
|
293
|
+
operation="get_signals",
|
|
294
|
+
error_message=result.error,
|
|
295
|
+
agent_id=self._client.agent_config.id,
|
|
296
|
+
)
|
|
297
|
+
return result.data
|
|
298
|
+
|
|
299
|
+
async def activate_signal(
|
|
300
|
+
self,
|
|
301
|
+
**kwargs: Any,
|
|
302
|
+
) -> ActivateSignalResponse:
|
|
303
|
+
"""Activate signal.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
**kwargs: Arguments passed to ActivateSignalRequest
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
ActivateSignalResponse
|
|
310
|
+
|
|
311
|
+
Raises:
|
|
312
|
+
Exception: If the request fails
|
|
313
|
+
"""
|
|
314
|
+
request = ActivateSignalRequest(**kwargs)
|
|
315
|
+
result = await self._client.activate_signal(request)
|
|
316
|
+
if not result.success or not result.data:
|
|
317
|
+
raise ADCPSimpleAPIError(
|
|
318
|
+
operation="activate_signal",
|
|
319
|
+
error_message=result.error,
|
|
320
|
+
agent_id=self._client.agent_config.id,
|
|
321
|
+
)
|
|
322
|
+
return result.data
|
|
323
|
+
|
|
324
|
+
async def provide_performance_feedback(
|
|
325
|
+
self,
|
|
326
|
+
**kwargs: Any,
|
|
327
|
+
) -> ProvidePerformanceFeedbackResponse:
|
|
328
|
+
"""Provide performance feedback.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
**kwargs: Arguments passed to ProvidePerformanceFeedbackRequest
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
ProvidePerformanceFeedbackResponse
|
|
335
|
+
|
|
336
|
+
Raises:
|
|
337
|
+
Exception: If the request fails
|
|
338
|
+
"""
|
|
339
|
+
request = ProvidePerformanceFeedbackRequest(**kwargs)
|
|
340
|
+
result = await self._client.provide_performance_feedback(request)
|
|
341
|
+
if not result.success or not result.data:
|
|
342
|
+
raise ADCPSimpleAPIError(
|
|
343
|
+
operation="provide_performance_feedback",
|
|
344
|
+
error_message=result.error,
|
|
345
|
+
agent_id=self._client.agent_config.id,
|
|
346
|
+
)
|
|
347
|
+
return result.data
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
"""Test helpers for AdCP client library.
|
|
2
2
|
|
|
3
3
|
Provides pre-configured test agents for examples and quick testing.
|
|
4
|
+
|
|
5
|
+
All test agents include a `.simple` accessor for ergonomic usage:
|
|
6
|
+
|
|
7
|
+
- **Standard API** (client methods): Full TaskResult with error handling
|
|
8
|
+
- **Simple API** (client.simple methods): Direct returns, raises on error
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
# Standard API - explicit control
|
|
12
|
+
result = await test_agent.get_products(GetProductsRequest(brief='Coffee'))
|
|
13
|
+
if result.success:
|
|
14
|
+
print(result.data.products)
|
|
15
|
+
|
|
16
|
+
# Simple API - ergonomic
|
|
17
|
+
products = await test_agent.simple.get_products(brief='Coffee')
|
|
18
|
+
print(products.products)
|
|
4
19
|
"""
|
|
5
20
|
|
|
6
21
|
from __future__ import annotations
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: adcp
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Official Python client for the Ad Context Protocol (AdCP)
|
|
5
5
|
Author-email: AdCP Community <maintainers@adcontextprotocol.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -63,36 +63,64 @@ pip install adcp
|
|
|
63
63
|
|
|
64
64
|
## Quick Start: Test Helpers
|
|
65
65
|
|
|
66
|
-
The fastest way to get started is using
|
|
66
|
+
The fastest way to get started is using pre-configured test agents with the **`.simple` API**:
|
|
67
67
|
|
|
68
68
|
```python
|
|
69
69
|
from adcp.testing import test_agent
|
|
70
|
-
from adcp.types.generated import GetProductsRequest
|
|
71
70
|
|
|
72
|
-
# Zero configuration - just import and
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
brief="Coffee subscription service",
|
|
76
|
-
promoted_offering="Premium coffee deliveries"
|
|
77
|
-
)
|
|
71
|
+
# Zero configuration - just import and call with kwargs!
|
|
72
|
+
products = await test_agent.simple.get_products(
|
|
73
|
+
brief='Coffee subscription service for busy professionals'
|
|
78
74
|
)
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
print(f"Found {len(products.products)} products")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Simple vs. Standard API
|
|
80
|
+
|
|
81
|
+
**Every ADCPClient** includes both API styles via the `.simple` accessor:
|
|
82
|
+
|
|
83
|
+
**Simple API** (`client.simple.*`) - Recommended for examples/prototyping:
|
|
84
|
+
```python
|
|
85
|
+
from adcp.testing import test_agent
|
|
86
|
+
|
|
87
|
+
# Kwargs and direct return - raises on error
|
|
88
|
+
products = await test_agent.simple.get_products(brief='Coffee brands')
|
|
89
|
+
print(products.products[0].name)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Standard API** (`client.*`) - Recommended for production:
|
|
93
|
+
```python
|
|
94
|
+
from adcp.testing import test_agent
|
|
95
|
+
from adcp.types.generated import GetProductsRequest
|
|
96
|
+
|
|
97
|
+
# Explicit request objects and TaskResult wrapper
|
|
98
|
+
request = GetProductsRequest(brief='Coffee brands')
|
|
99
|
+
result = await test_agent.get_products(request)
|
|
100
|
+
|
|
101
|
+
if result.success and result.data:
|
|
102
|
+
print(result.data.products[0].name)
|
|
103
|
+
else:
|
|
104
|
+
print(f"Error: {result.error}")
|
|
82
105
|
```
|
|
83
106
|
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
**When to use which:**
|
|
108
|
+
- **Simple API** (`.simple`): Quick testing, documentation, examples, notebooks
|
|
109
|
+
- **Standard API**: Production code, complex error handling, webhook workflows
|
|
110
|
+
|
|
111
|
+
### Available Test Helpers
|
|
112
|
+
|
|
113
|
+
Pre-configured agents (all include `.simple` accessor):
|
|
114
|
+
- **`test_agent`**: MCP test agent with authentication
|
|
115
|
+
- **`test_agent_a2a`**: A2A test agent with authentication
|
|
116
|
+
- **`test_agent_no_auth`**: MCP test agent without authentication
|
|
117
|
+
- **`test_agent_a2a_no_auth`**: A2A test agent without authentication
|
|
89
118
|
- **`creative_agent`**: Reference creative agent for preview functionality
|
|
90
119
|
- **`test_agent_client`**: Multi-agent client with both protocols
|
|
91
|
-
- **`create_test_agent()`**: Factory for custom test configurations
|
|
92
120
|
|
|
93
121
|
> **Note**: Test agents are rate-limited and for testing/examples only. DO NOT use in production.
|
|
94
122
|
|
|
95
|
-
See [examples/
|
|
123
|
+
See [examples/simple_api_demo.py](examples/simple_api_demo.py) for a complete comparison.
|
|
96
124
|
|
|
97
125
|
## Quick Start: Distributed Operations
|
|
98
126
|
|
|
@@ -6,6 +6,7 @@ src/adcp/__main__.py
|
|
|
6
6
|
src/adcp/client.py
|
|
7
7
|
src/adcp/config.py
|
|
8
8
|
src/adcp/exceptions.py
|
|
9
|
+
src/adcp/simple.py
|
|
9
10
|
src/adcp.egg-info/PKG-INFO
|
|
10
11
|
src/adcp.egg-info/SOURCES.txt
|
|
11
12
|
src/adcp.egg-info/dependency_links.txt
|
|
@@ -34,4 +35,5 @@ tests/test_format_id_validation.py
|
|
|
34
35
|
tests/test_helpers.py
|
|
35
36
|
tests/test_preview_html.py
|
|
36
37
|
tests/test_protocols.py
|
|
37
|
-
tests/test_response_parser.py
|
|
38
|
+
tests/test_response_parser.py
|
|
39
|
+
tests/test_simple_api.py
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Tests for the simplified API accessor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import AsyncMock, patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from adcp.testing import test_agent
|
|
10
|
+
from adcp.types.core import TaskResult, TaskStatus
|
|
11
|
+
from adcp.types.generated import (
|
|
12
|
+
GetProductsResponse,
|
|
13
|
+
ListCreativeFormatsResponse,
|
|
14
|
+
PreviewCreativeResponse,
|
|
15
|
+
Product,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.asyncio
|
|
20
|
+
async def test_get_products_simple_api():
|
|
21
|
+
"""Test client.simple.get_products with kwargs."""
|
|
22
|
+
# Create mock response (using model_construct to bypass validation for test data)
|
|
23
|
+
mock_product = Product.model_construct(
|
|
24
|
+
product_id="prod_1",
|
|
25
|
+
name="Test Product",
|
|
26
|
+
description="A test product",
|
|
27
|
+
)
|
|
28
|
+
mock_response = GetProductsResponse.model_construct(products=[mock_product])
|
|
29
|
+
mock_result = TaskResult[GetProductsResponse](
|
|
30
|
+
status=TaskStatus.COMPLETED, data=mock_response, success=True
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Mock the client's get_products method
|
|
34
|
+
with patch.object(test_agent, "get_products", new=AsyncMock(return_value=mock_result)):
|
|
35
|
+
# Call simplified API with kwargs
|
|
36
|
+
result = await test_agent.simple.get_products(brief="Coffee subscription service")
|
|
37
|
+
|
|
38
|
+
# Verify it returns unwrapped data
|
|
39
|
+
assert isinstance(result, GetProductsResponse)
|
|
40
|
+
assert len(result.products) == 1
|
|
41
|
+
assert result.products[0].product_id == "prod_1"
|
|
42
|
+
|
|
43
|
+
# Verify the underlying call was made correctly
|
|
44
|
+
test_agent.get_products.assert_called_once()
|
|
45
|
+
call_args = test_agent.get_products.call_args[0][0]
|
|
46
|
+
assert call_args.brief == "Coffee subscription service"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.asyncio
|
|
50
|
+
async def test_get_products_simple_api_failure():
|
|
51
|
+
"""Test client.simple.get_products raises exception on failure."""
|
|
52
|
+
from adcp.exceptions import ADCPSimpleAPIError
|
|
53
|
+
|
|
54
|
+
# Create mock failure response
|
|
55
|
+
mock_result = TaskResult[GetProductsResponse](
|
|
56
|
+
status=TaskStatus.FAILED, data=None, success=False, error="Test error"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
with patch.object(test_agent, "get_products", new=AsyncMock(return_value=mock_result)):
|
|
60
|
+
# Should raise ADCPSimpleAPIError on failure
|
|
61
|
+
with pytest.raises(ADCPSimpleAPIError, match="get_products failed"):
|
|
62
|
+
await test_agent.simple.get_products(brief="Test")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_simple_api_has_no_sync_methods():
|
|
66
|
+
"""Test that simple API only provides async methods.
|
|
67
|
+
|
|
68
|
+
Users can wrap with asyncio.run() if they need sync behavior.
|
|
69
|
+
"""
|
|
70
|
+
# Verify simple API doesn't have sync methods
|
|
71
|
+
assert not hasattr(test_agent.simple, "get_products_sync")
|
|
72
|
+
assert hasattr(test_agent.simple, "get_products")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.asyncio
|
|
76
|
+
async def test_list_creative_formats_simple_api():
|
|
77
|
+
"""Test client.simple.list_creative_formats with kwargs."""
|
|
78
|
+
from adcp.types.generated import Format
|
|
79
|
+
|
|
80
|
+
# Create mock response (using model_construct to bypass validation for test data)
|
|
81
|
+
mock_format = Format.model_construct(
|
|
82
|
+
format_id={"id": "banner_300x250"},
|
|
83
|
+
name="Banner 300x250",
|
|
84
|
+
description="Standard banner",
|
|
85
|
+
)
|
|
86
|
+
mock_response = ListCreativeFormatsResponse.model_construct(formats=[mock_format])
|
|
87
|
+
mock_result = TaskResult[ListCreativeFormatsResponse](
|
|
88
|
+
status=TaskStatus.COMPLETED, data=mock_response, success=True
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
with patch.object(test_agent, "list_creative_formats", new=AsyncMock(return_value=mock_result)):
|
|
92
|
+
# Call simplified API
|
|
93
|
+
result = await test_agent.simple.list_creative_formats()
|
|
94
|
+
|
|
95
|
+
# Verify it returns unwrapped data
|
|
96
|
+
assert isinstance(result, ListCreativeFormatsResponse)
|
|
97
|
+
assert len(result.formats) == 1
|
|
98
|
+
assert result.formats[0].format_id["id"] == "banner_300x250"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_simple_api_exists_on_client():
|
|
102
|
+
"""Test that all clients have a .simple accessor."""
|
|
103
|
+
from adcp.testing import creative_agent, test_agent_a2a
|
|
104
|
+
|
|
105
|
+
# All clients should have .simple
|
|
106
|
+
assert hasattr(test_agent, "simple")
|
|
107
|
+
assert hasattr(test_agent_a2a, "simple")
|
|
108
|
+
assert hasattr(creative_agent, "simple")
|
|
109
|
+
|
|
110
|
+
# Should be SimpleAPI instance
|
|
111
|
+
from adcp.simple import SimpleAPI
|
|
112
|
+
|
|
113
|
+
assert isinstance(test_agent.simple, SimpleAPI)
|
|
114
|
+
assert isinstance(test_agent_a2a.simple, SimpleAPI)
|
|
115
|
+
assert isinstance(creative_agent.simple, SimpleAPI)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_simple_api_on_freshly_constructed_client():
|
|
119
|
+
"""Test that .simple accessor works on freshly constructed ADCPClient."""
|
|
120
|
+
from adcp import ADCPClient, AgentConfig, Protocol
|
|
121
|
+
from adcp.simple import SimpleAPI
|
|
122
|
+
|
|
123
|
+
# Create a new client from scratch
|
|
124
|
+
client = ADCPClient(
|
|
125
|
+
AgentConfig(
|
|
126
|
+
id="test-agent",
|
|
127
|
+
agent_uri="https://test.example.com/mcp/",
|
|
128
|
+
protocol=Protocol.MCP,
|
|
129
|
+
auth_token="test-token",
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Should have .simple accessor
|
|
134
|
+
assert hasattr(client, "simple")
|
|
135
|
+
assert isinstance(client.simple, SimpleAPI)
|
|
136
|
+
|
|
137
|
+
# Should reference the same client
|
|
138
|
+
assert client.simple._client is client
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@pytest.mark.asyncio
|
|
142
|
+
async def test_preview_creative_simple_api():
|
|
143
|
+
"""Test client.simple.preview_creative."""
|
|
144
|
+
from adcp.testing import creative_agent
|
|
145
|
+
|
|
146
|
+
mock_response = PreviewCreativeResponse(
|
|
147
|
+
previews=[{"url": "https://preview.example.com/123", "html": "<html>...</html>"}]
|
|
148
|
+
)
|
|
149
|
+
mock_result = TaskResult[PreviewCreativeResponse](
|
|
150
|
+
status=TaskStatus.COMPLETED, data=mock_response, success=True
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
with patch.object(creative_agent, "preview_creative", new=AsyncMock(return_value=mock_result)):
|
|
154
|
+
# Call simplified API
|
|
155
|
+
result = await creative_agent.simple.preview_creative(
|
|
156
|
+
manifest={"format_id": "banner_300x250", "assets": {}}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Verify it returns unwrapped data
|
|
160
|
+
assert isinstance(result, PreviewCreativeResponse)
|
|
161
|
+
assert result.previews is not None
|
|
162
|
+
assert len(result.previews) == 1
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_simple_api_methods():
|
|
166
|
+
"""Test that SimpleAPI has all expected methods."""
|
|
167
|
+
# Check all methods exist
|
|
168
|
+
assert hasattr(test_agent.simple, "get_products")
|
|
169
|
+
assert hasattr(test_agent.simple, "list_creative_formats")
|
|
170
|
+
assert hasattr(test_agent.simple, "preview_creative")
|
|
171
|
+
assert hasattr(test_agent.simple, "sync_creatives")
|
|
172
|
+
assert hasattr(test_agent.simple, "list_creatives")
|
|
173
|
+
assert hasattr(test_agent.simple, "get_media_buy_delivery")
|
|
174
|
+
assert hasattr(test_agent.simple, "list_authorized_properties")
|
|
175
|
+
assert hasattr(test_agent.simple, "get_signals")
|
|
176
|
+
assert hasattr(test_agent.simple, "activate_signal")
|
|
177
|
+
assert hasattr(test_agent.simple, "provide_performance_feedback")
|
|
178
|
+
|
|
179
|
+
# Verify they're all async methods (not sync)
|
|
180
|
+
import inspect
|
|
181
|
+
|
|
182
|
+
assert inspect.iscoroutinefunction(test_agent.simple.get_products)
|
|
183
|
+
assert inspect.iscoroutinefunction(test_agent.simple.list_creative_formats)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|