Skip to content

Commit 54275f2

Browse files
committed
fix(types): widen guards.CreateMediaBuyResponse to 3-branch union + re-export Submitted
The local CreateMediaBuyResponse alias in guards.py was a stale 2-branch union (Success | Error). PlatformHandler.create_media_buy now returns the full 3-branch union including the async Submitted envelope, so callers running is_create_media_buy_success on a handler result would silently mis-classify the submitted branch. Changes: - guards.py: widen CreateMediaBuyResponse to include CreateMediaBuySubmittedResponse; add is_create_media_buy_submitted TypeGuard; update success/error guards to short-circuit on the submitted branch (it's neither sync success nor terminal error). - adcp/__init__.py: re-export CreateMediaBuySubmittedResponse alongside the existing Success/Error aliases. - tests: cover the new submitted guard (positive + success/error negatives); update public_api snapshot for the top-level re-export.
1 parent d14080e commit 54275f2

4 files changed

Lines changed: 76 additions & 3 deletions

File tree

src/adcp/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@
294294
CreateContentStandardsErrorResponse,
295295
CreateContentStandardsSuccessResponse,
296296
CreateMediaBuyErrorResponse,
297+
CreateMediaBuySubmittedResponse,
297298
CreateMediaBuySuccessResponse,
298299
Deployment,
299300
Destination,
@@ -890,6 +891,7 @@ def get_adcp_version() -> str:
890891
"CreateContentStandardsErrorResponse",
891892
"CreateMediaBuySuccessResponse",
892893
"CreateMediaBuyErrorResponse",
894+
"CreateMediaBuySubmittedResponse",
893895
"Deployment",
894896
"Destination",
895897
"GetContentStandardsSuccessResponse",

src/adcp/types/guards.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def is_adcp_success(response: Any) -> bool:
7979
CalibrateContentErrorResponse,
8080
CalibrateContentSuccessResponse,
8181
CreateMediaBuyErrorResponse,
82+
CreateMediaBuySubmittedResponse,
8283
CreateMediaBuySuccessResponse,
8384
GetAccountFinancialsErrorResponse,
8485
GetAccountFinancialsSuccessResponse,
@@ -103,7 +104,9 @@ def is_adcp_success(response: Any) -> bool:
103104
)
104105

105106
# Type aliases for response unions
106-
CreateMediaBuyResponse = CreateMediaBuySuccessResponse | CreateMediaBuyErrorResponse
107+
CreateMediaBuyResponse = (
108+
CreateMediaBuySuccessResponse | CreateMediaBuyErrorResponse | CreateMediaBuySubmittedResponse
109+
)
107110
UpdateMediaBuyResponse = UpdateMediaBuySuccessResponse | UpdateMediaBuyErrorResponse
108111
ActivateSignalResponse = ActivateSignalSuccessResponse | ActivateSignalErrorResponse
109112
BuildCreativeResponse = BuildCreativeSuccessResponse | BuildCreativeErrorResponse
@@ -116,22 +119,49 @@ def is_adcp_success(response: Any) -> bool:
116119

117120
# --- Create Media Buy ---
118121

122+
123+
def is_create_media_buy_submitted(
124+
response: CreateMediaBuyResponse,
125+
) -> TypeGuard[CreateMediaBuySubmittedResponse]:
126+
"""Check if a CreateMediaBuyResponse is the async submitted envelope.
127+
128+
The submitted branch carries ``status == 'submitted'`` and a ``task_id``
129+
the buyer uses to poll ``tasks/get`` (or correlate with push-notification
130+
callbacks). It is neither a synchronous success nor a terminal error.
131+
"""
132+
return getattr(response, "status", None) == "submitted" and hasattr(response, "task_id")
133+
134+
119135
def is_create_media_buy_success(
120136
response: CreateMediaBuyResponse,
121137
) -> TypeGuard[CreateMediaBuySuccessResponse]:
122-
"""Check if a CreateMediaBuyResponse is a success."""
138+
"""Check if a CreateMediaBuyResponse is a synchronous success.
139+
140+
Returns False for the submitted (async) envelope — use
141+
``is_create_media_buy_submitted`` for that branch.
142+
"""
143+
if is_create_media_buy_submitted(response):
144+
return False
123145
return not is_adcp_error(response)
124146

125147

126148
def is_create_media_buy_error(
127149
response: CreateMediaBuyResponse,
128150
) -> TypeGuard[CreateMediaBuyErrorResponse]:
129-
"""Check if a CreateMediaBuyResponse is an error."""
151+
"""Check if a CreateMediaBuyResponse is an error.
152+
153+
Returns False for the submitted (async) envelope, even if it carries
154+
advisory (non-blocking) errors. Use ``is_create_media_buy_submitted``
155+
for that branch.
156+
"""
157+
if is_create_media_buy_submitted(response):
158+
return False
130159
return is_adcp_error(response)
131160

132161

133162
# --- Update Media Buy ---
134163

164+
135165
def is_update_media_buy_success(
136166
response: UpdateMediaBuyResponse,
137167
) -> TypeGuard[UpdateMediaBuySuccessResponse]:
@@ -148,6 +178,7 @@ def is_update_media_buy_error(
148178

149179
# --- Activate Signal ---
150180

181+
151182
def is_activate_signal_success(
152183
response: ActivateSignalResponse,
153184
) -> TypeGuard[ActivateSignalSuccessResponse]:
@@ -164,6 +195,7 @@ def is_activate_signal_error(
164195

165196
# --- Build Creative ---
166197

198+
167199
def is_build_creative_success(
168200
response: BuildCreativeResponse,
169201
) -> TypeGuard[BuildCreativeSuccessResponse]:
@@ -180,6 +212,7 @@ def is_build_creative_error(
180212

181213
# --- Sync Creatives ---
182214

215+
183216
def is_sync_creatives_success(
184217
response: SyncCreativesResponse,
185218
) -> TypeGuard[SyncCreativesSuccessResponse]:
@@ -196,6 +229,7 @@ def is_sync_creatives_error(
196229

197230
# --- Performance Feedback ---
198231

232+
199233
def is_performance_feedback_success(
200234
response: ProvidePerformanceFeedbackSuccessResponse | ProvidePerformanceFeedbackErrorResponse,
201235
) -> TypeGuard[ProvidePerformanceFeedbackSuccessResponse]:
@@ -212,6 +246,7 @@ def is_performance_feedback_error(
212246

213247
# --- Sync Accounts ---
214248

249+
215250
def is_sync_accounts_success(
216251
response: SyncAccountsResponse,
217252
) -> TypeGuard[SyncAccountsSuccessResponse]:
@@ -228,6 +263,7 @@ def is_sync_accounts_error(
228263

229264
# --- Log Event ---
230265

266+
231267
def is_log_event_success(
232268
response: LogEventResponse,
233269
) -> TypeGuard[LogEventSuccessResponse]:
@@ -244,6 +280,7 @@ def is_log_event_error(
244280

245281
# --- Sync Catalogs ---
246282

283+
247284
def is_sync_catalogs_success(
248285
response: SyncCatalogsResponse,
249286
) -> TypeGuard[SyncCatalogsSuccessResponse]:
@@ -260,6 +297,7 @@ def is_sync_catalogs_error(
260297

261298
# --- Get Account Financials ---
262299

300+
263301
def is_get_account_financials_success(
264302
response: GetAccountFinancialsSuccessResponse | GetAccountFinancialsErrorResponse,
265303
) -> TypeGuard[GetAccountFinancialsSuccessResponse]:
@@ -276,6 +314,7 @@ def is_get_account_financials_error(
276314

277315
# --- Content Standards ---
278316

317+
279318
def is_calibrate_content_success(
280319
response: CalibrateContentSuccessResponse | CalibrateContentErrorResponse,
281320
) -> TypeGuard[CalibrateContentSuccessResponse]:
@@ -292,6 +331,7 @@ def is_validate_content_delivery_success(
292331

293332
# --- Creative Features ---
294333

334+
295335
def is_get_creative_features_success(
296336
response: GetCreativeFeaturesSuccessResponse | GetCreativeFeaturesErrorResponse,
297337
) -> TypeGuard[GetCreativeFeaturesSuccessResponse]:
@@ -310,6 +350,7 @@ def is_get_creative_features_success(
310350
# Media buy guards
311351
"is_create_media_buy_success",
312352
"is_create_media_buy_error",
353+
"is_create_media_buy_submitted",
313354
"is_update_media_buy_success",
314355
"is_update_media_buy_error",
315356
# Signal guards

tests/fixtures/public_api_snapshot.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
"CreateMediaBuyErrorResponse",
9595
"CreateMediaBuyRequest",
9696
"CreateMediaBuyResponse",
97+
"CreateMediaBuySubmittedResponse",
9798
"CreateMediaBuySuccessResponse",
9899
"Creative",
99100
"CreativeApproval",

tests/test_type_guards.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
is_adcp_error,
77
is_adcp_success,
88
is_create_media_buy_error,
9+
is_create_media_buy_submitted,
910
is_create_media_buy_success,
1011
is_update_media_buy_error,
1112
is_update_media_buy_success,
@@ -93,6 +94,34 @@ def test_create_media_buy_error_guard(self) -> None:
9394
)
9495
assert is_create_media_buy_success(resp) is False
9596
assert is_create_media_buy_error(resp) is True
97+
assert is_create_media_buy_submitted(resp) is False
98+
99+
def test_create_media_buy_submitted_guard(self) -> None:
100+
"""Submitted (async) envelope is neither success nor error."""
101+
from adcp.types.aliases import (
102+
CreateMediaBuyErrorResponse,
103+
CreateMediaBuySubmittedResponse,
104+
CreateMediaBuySuccessResponse,
105+
)
106+
107+
submitted = CreateMediaBuySubmittedResponse.model_validate(
108+
{"status": "submitted", "task_id": "task_abc"}
109+
)
110+
assert is_create_media_buy_submitted(submitted) is True
111+
assert is_create_media_buy_success(submitted) is False
112+
assert is_create_media_buy_error(submitted) is False
113+
114+
# Negative cases: success and error payloads must not be classified
115+
# as submitted.
116+
success = CreateMediaBuySuccessResponse.model_validate(
117+
{"media_buy_id": "mb_123", "packages": []}
118+
)
119+
assert is_create_media_buy_submitted(success) is False
120+
121+
error = CreateMediaBuyErrorResponse.model_validate(
122+
{"errors": [{"message": "fail", "code": "INVALID_REQUEST"}]}
123+
)
124+
assert is_create_media_buy_submitted(error) is False
96125

97126
def test_update_media_buy_guards(self) -> None:
98127
from adcp.types.aliases import (

0 commit comments

Comments
 (0)