Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ extend-exclude = [
"src/adcp/types/_generated.py",
"src/adcp/types/tasks.py",
"src/adcp/types/generated_poc/",
"examples/",
"scripts/",
]

[tool.ruff.lint]
Expand Down
9 changes: 7 additions & 2 deletions src/adcp/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
GetMediaBuyDeliveryResponse,
GetProductsRequest,
GetProductsResponse,
GetSignalsRequest,
GetSignalsDiscoveryRequest,
GetSignalsLookupRequest,
GetSignalsResponse,
ListAccountsRequest,
ListAccountsResponse,
Expand Down Expand Up @@ -275,7 +276,11 @@ async def get_signals(
Raises:
Exception: If the request fails
"""
request = GetSignalsRequest(**kwargs)
request: GetSignalsDiscoveryRequest | GetSignalsLookupRequest
if "signal_ids" in kwargs:
request = GetSignalsLookupRequest.model_validate(kwargs)
else:
request = GetSignalsDiscoveryRequest.model_validate(kwargs)
result = await self._client.get_signals(request)
if not result.success or not result.data:
raise ADCPSimpleAPIError(
Expand Down
2 changes: 1 addition & 1 deletion src/adcp/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@
GetProductsResponse,
GetPropertyListRequest,
GetPropertyListResponse,
GetSignalsRequest,
GetSignalsResponse,
Gtin,
HtmlAsset,
Expand Down Expand Up @@ -399,6 +398,7 @@
GetProductsWholesaleRequest,
GetSignalsDiscoveryRequest,
GetSignalsLookupRequest,
GetSignalsRequest,
HtmlPreviewRender,
InlineDaastAsset,
InlineVastAsset,
Expand Down
3 changes: 3 additions & 0 deletions src/adcp/types/aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,9 @@ def process_result(result: SyncCatalogResult) -> None:
"""Get products in wholesale mode - buying_mode='wholesale', raw inventory."""

# Get Signals Request Variants
GetSignalsRequest = GetSignalsRequest1 | GetSignalsRequest2
"""Union of GetSignalsRequest variants. Use instead of the RootModel wrapper."""

GetSignalsDiscoveryRequest = GetSignalsRequest1
"""Discover signals by natural language spec - signal_spec required."""

Expand Down
45 changes: 45 additions & 0 deletions tests/test_type_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,3 +808,48 @@ def test_destination_union_contains_all_variants():
}

assert union_args == expected_variants


def test_get_signals_request_is_union_not_root_model():
"""GetSignalsRequest is a plain union alias, not a RootModel.

This allows consumers to subclass GetSignalsRequest1 or GetSignalsRequest2
with custom model_config (e.g. extra='forbid' in CI, extra='ignore' in prod).
See: https://github.com/adcontextprotocol/adcp-client-python/issues/138
"""
import types

from pydantic import RootModel

from adcp import GetSignalsRequest

# Must be a union, not a RootModel subclass
assert isinstance(GetSignalsRequest, types.UnionType)
assert not (isinstance(GetSignalsRequest, type) and issubclass(GetSignalsRequest, RootModel))


def test_get_signals_request_union_contains_variants():
"""GetSignalsRequest union contains the two concrete variants."""
from typing import get_args

from adcp import GetSignalsDiscoveryRequest, GetSignalsLookupRequest, GetSignalsRequest

union_args = set(get_args(GetSignalsRequest))
assert union_args == {GetSignalsDiscoveryRequest, GetSignalsLookupRequest}


def test_get_signals_request_variants_are_subclassable():
"""Consumers can subclass GetSignalsRequest variants with custom model_config."""
from pydantic import ConfigDict

from adcp import GetSignalsDiscoveryRequest, GetSignalsLookupRequest

class MyDiscoveryRequest(GetSignalsDiscoveryRequest):
model_config = ConfigDict(extra="forbid")

class MyLookupRequest(GetSignalsLookupRequest):
model_config = ConfigDict(extra="forbid")

# Should construct without error
req = MyDiscoveryRequest(signal_spec="targeting signals for automotive in-market buyers")
assert req.signal_spec == "targeting signals for automotive in-market buyers"