Skip to content

Commit f8ce501

Browse files
normalize-id-types-to-match-public-documentation
1 parent 03e052a commit f8ce501

15 files changed

+82
-42
lines changed

tests/unit/test_handlers_build_items.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626

2727
class TestAdvertiserBuildItems:
28-
_MINIMAL = {"id_type": "tdid", "id_value": "test-tdid-value", "segment_name": "test-segment-name"}
28+
_MINIMAL = {"id_type": "TDID", "id_value": "test-tdid-value", "segment_name": "test-segment-name"}
2929

3030
def test_builds_advertiser_data_item_with_correct_fields(self):
3131
# Handler maps id_type → AdvertiserDataItem field dynamically: {d["id_type"]: d["id_value"]}
@@ -48,9 +48,10 @@ def test_optional_fields_are_passed_through_when_provided(self):
4848
assert item.cookie_mapping_partner_id == "test-partner-id"
4949

5050
def test_non_tdid_id_types_map_correctly(self):
51-
for id_type in ["daid", "uid2", "ramp_id"]:
51+
cases = [("DAID", "daid"), ("UID2", "uid2"), ("RampID", "ramp_id")]
52+
for id_type, field_name in cases:
5253
row = {**self._MINIMAL, "id_type": id_type, "id_value": f"test-{id_type}-value"}
53-
assert getattr(adv_handler.build_items([row])[0], id_type) == f"test-{id_type}-value"
54+
assert getattr(adv_handler.build_items([row])[0], field_name) == f"test-{id_type}-value"
5455

5556

5657
# --------------------------------------------------------------------------- #
@@ -59,7 +60,7 @@ def test_non_tdid_id_types_map_correctly(self):
5960

6061

6162
class TestThirdPartyBuildItems:
62-
_MINIMAL = {"id_type": "tdid", "id_value": "test-tdid-value", "segment_name": "test-segment-name"}
63+
_MINIMAL = {"id_type": "TDID", "id_value": "test-tdid-value", "segment_name": "test-segment-name"}
6364

6465
def test_builds_third_party_data_item_with_correct_fields(self):
6566
item = tp_handler.build_items([self._MINIMAL])[0]
@@ -85,19 +86,19 @@ def test_optional_fields_are_passed_through_when_provided(self):
8586

8687

8788
def test_deletion_optout_advertiser_returns_partner_dsr_item_with_correct_id():
88-
item = del_adv_handler.build_items([{"id_type": "tdid", "id_value": "test-advertiser-tdid"}])[0]
89+
item = del_adv_handler.build_items([{"id_type": "TDID", "id_value": "test-advertiser-tdid"}])[0]
8990
assert isinstance(item, PartnerDsrDataItem)
9091
assert getattr(item, "tdid") == "test-advertiser-tdid"
9192

9293

9394
def test_deletion_optout_thirdparty_returns_partner_dsr_item_with_correct_id():
94-
item = del_tp_handler.build_items([{"id_type": "uid2", "id_value": "test-thirdparty-uid2"}])[0]
95+
item = del_tp_handler.build_items([{"id_type": "UID2", "id_value": "test-thirdparty-uid2"}])[0]
9596
assert isinstance(item, PartnerDsrDataItem)
9697
assert getattr(item, "uid2") == "test-thirdparty-uid2"
9798

9899

99100
def test_deletion_optout_merchant_returns_partner_dsr_item_with_correct_id():
100-
item = del_merch_handler.build_items([{"id_type": "tdid", "id_value": "test-merchant-tdid"}])[0]
101+
item = del_merch_handler.build_items([{"id_type": "TDID", "id_value": "test-merchant-tdid"}])[0]
101102
assert isinstance(item, PartnerDsrDataItem)
102103
assert getattr(item, "tdid") == "test-merchant-tdid"
103104

@@ -121,15 +122,15 @@ def test_builds_offline_conversion_data_item_with_correct_fields(self):
121122
def test_user_ids_converted_to_user_id_array_with_type_codes(self):
122123
row = {
123124
**self._MINIMAL,
124-
"user_ids": [{"type": "tdid", "id": "test-tdid-value"}, {"type": "daid", "id": "test-daid-value"}],
125+
"user_ids": [{"type": "TDID", "id": "test-tdid-value"}, {"type": "DAID", "id": "test-daid-value"}],
125126
}
126127
item = oc_handler.build_items([row])[0]
127128
assert item.user_id_array == [["0", "test-tdid-value"], ["1", "test-daid-value"]]
128129

129130
def test_all_user_id_types_map_to_correct_codes(self):
130131
type_map = {
131-
"tdid": "0", "daid": "1", "uid2": "2", "uid2token": "3",
132-
"euid": "4", "euidtoken": "5", "rampid": "6",
132+
"TDID": "0", "DAID": "1", "UID2": "2", "UID2Token": "3",
133+
"EUID": "4", "EUIDToken": "5", "RampID": "6",
133134
}
134135
for id_type, expected_code in type_map.items():
135136
row = {**self._MINIMAL, "user_ids": [{"type": id_type, "id": f"test-{id_type}-value"}]}

tests/unit/test_push_data.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def _make_handler(failed_lines: list | None = None) -> MagicMock:
4848

4949

5050
def test_all_rows_succeed_output_has_success_true(spark: SparkSession) -> None:
51-
data = [("tdid", "abc123", "seg1"), ("uid2", "def456", "seg2")]
51+
data = [("TDID", "abc123", "seg1"), ("UID2", "def456", "seg2")]
5252
df = spark.createDataFrame(data, _REQUIRED_SCHEMA)
5353

5454
with patch("importlib.import_module", return_value=_make_handler()):
@@ -67,7 +67,7 @@ def test_all_rows_succeed_output_has_success_true(spark: SparkSession) -> None:
6767

6868

6969
def test_output_dataframe_has_status_columns(spark: SparkSession) -> None:
70-
df = spark.createDataFrame([("tdid", "abc123", "seg1")], _REQUIRED_SCHEMA)
70+
df = spark.createDataFrame([("TDID", "abc123", "seg1")], _REQUIRED_SCHEMA)
7171

7272
with patch("importlib.import_module", return_value=_make_handler()):
7373
result = _make_client(spark).push_data(df, _CONTEXT)
@@ -95,7 +95,7 @@ def test_extra_columns_preserved_in_output(spark: SparkSession) -> None:
9595
StructField("custom_col", StringType(), True),
9696
]
9797
)
98-
df = spark.createDataFrame([("tdid", "abc123", "seg1", "my_value")], schema)
98+
df = spark.createDataFrame([("TDID", "abc123", "seg1", "my_value")], schema)
9999

100100
with patch("importlib.import_module", return_value=_make_handler()):
101101
result = _make_client(spark).push_data(df, _CONTEXT)
@@ -111,7 +111,7 @@ def test_extra_columns_preserved_in_output(spark: SparkSession) -> None:
111111

112112
def test_partial_failure_maps_error_to_correct_row(spark: SparkSession) -> None:
113113
# Two rows in one batch; item #1 fails, item #2 succeeds
114-
data = [("tdid", "abc123", "seg1"), ("tdid", "def456", "seg2")]
114+
data = [("TDID", "abc123", "seg1"), ("TDID", "def456", "seg2")]
115115
df = spark.createDataFrame(data, _REQUIRED_SCHEMA)
116116

117117
failed_line = MagicMock()
@@ -135,7 +135,7 @@ def test_missing_required_column_raises_schema_validation_error(spark: SparkSess
135135
# missing: segment_name
136136
]
137137
)
138-
df = spark.createDataFrame([("tdid", "abc123")], schema)
138+
df = spark.createDataFrame([("TDID", "abc123")], schema)
139139

140140
with pytest.raises(TTDSchemaValidationError) as exc_info:
141141
_make_client(spark).push_data(df, _CONTEXT)

ttd_databricks_python/ttd_databricks/handlers/advertiser.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from ttd_databricks_python.ttd_databricks.constants import TTD_DATABRICKS_SDK_ORIGIN_ID
88
from ttd_databricks_python.ttd_databricks.contexts import AdvertiserContext
9+
from ttd_databricks_python.ttd_databricks.id_types import normalize_id_type
910

1011
if TYPE_CHECKING:
1112
from ttd_data import DataClient
@@ -26,7 +27,7 @@ def build_items(items_data: list[dict[str, Any]]) -> list[AdvertiserDataItem]:
2627
adv_data_kwargs[field] = d[field]
2728

2829
adv_item_kwargs = {
29-
d["id_type"]: d["id_value"],
30+
normalize_id_type(d["id_type"]): d["id_value"],
3031
"data": [AdvertiserData(**adv_data_kwargs)],
3132
}
3233
for field in ITEM_OPTIONAL_FIELDS:

ttd_databricks_python/ttd_databricks/handlers/deletion_optout_advertiser.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import TYPE_CHECKING, Any, Optional, cast
66

77
from ttd_databricks_python.ttd_databricks.contexts import DeletionOptOutAdvertiserContext
8+
from ttd_databricks_python.ttd_databricks.id_types import normalize_id_type
89

910
if TYPE_CHECKING:
1011
from ttd_data import DataClient
@@ -17,7 +18,7 @@ def build_items(items_data: list[dict[str, Any]]) -> list[PartnerDsrDataItem]:
1718

1819
items = []
1920
for d in items_data:
20-
items.append(PartnerDsrDataItem(**{d["id_type"]: d["id_value"]}))
21+
items.append(PartnerDsrDataItem(**{normalize_id_type(d["id_type"]): d["id_value"]}))
2122
return items
2223

2324

ttd_databricks_python/ttd_databricks/handlers/deletion_optout_merchant.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import TYPE_CHECKING, Any, Optional, cast
66

77
from ttd_databricks_python.ttd_databricks.contexts import DeletionOptOutMerchantContext
8+
from ttd_databricks_python.ttd_databricks.id_types import normalize_id_type
89

910
if TYPE_CHECKING:
1011
from ttd_data import DataClient
@@ -17,7 +18,7 @@ def build_items(items_data: list[dict[str, Any]]) -> list[PartnerDsrDataItem]:
1718

1819
items = []
1920
for d in items_data:
20-
items.append(PartnerDsrDataItem(**{d["id_type"]: d["id_value"]}))
21+
items.append(PartnerDsrDataItem(**{normalize_id_type(d["id_type"]): d["id_value"]}))
2122
return items
2223

2324

ttd_databricks_python/ttd_databricks/handlers/deletion_optout_thirdparty.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import TYPE_CHECKING, Any, Optional, cast
66

77
from ttd_databricks_python.ttd_databricks.contexts import DeletionOptOutThirdPartyContext
8+
from ttd_databricks_python.ttd_databricks.id_types import normalize_id_type
89

910
if TYPE_CHECKING:
1011
from ttd_data import DataClient
@@ -17,7 +18,7 @@ def build_items(items_data: list[dict[str, Any]]) -> list[PartnerDsrDataItem]:
1718

1819
items = []
1920
for d in items_data:
20-
items.append(PartnerDsrDataItem(**{d["id_type"]: d["id_value"]}))
21+
items.append(PartnerDsrDataItem(**{normalize_id_type(d["id_type"]): d["id_value"]}))
2122
return items
2223

2324

ttd_databricks_python/ttd_databricks/handlers/offline_conversion.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
from ttd_data import DataClient
1212
from ttd_data.models import OfflineConversionDataItem
1313

14-
# Maps user_ids[].type (lowercase) → string type code used in UserIdArray
14+
# Maps user_ids[].type (user-facing name) → string type code used in UserIdArray
1515
_USER_ID_TYPE_CODE: dict[str, str] = {
16-
"tdid": "0",
17-
"daid": "1",
18-
"uid2": "2",
19-
"uid2token": "3",
20-
"euid": "4",
21-
"euidtoken": "5",
22-
"rampid": "6",
16+
"TDID": "0",
17+
"DAID": "1",
18+
"UID2": "2",
19+
"UID2Token": "3",
20+
"EUID": "4",
21+
"EUIDToken": "5",
22+
"RampID": "6",
2323
}
2424

2525

@@ -43,7 +43,7 @@ def build_items(items_data: list[dict[str, Any]]) -> list[OfflineConversionDataI
4343
raw_user_ids = row.get("user_ids")
4444
if raw_user_ids:
4545
kwargs["user_id_array"] = [
46-
[_USER_ID_TYPE_CODE[user_id["type"].lower()], user_id["id"]] for user_id in raw_user_ids
46+
[_USER_ID_TYPE_CODE[user_id["type"]], user_id["id"]] for user_id in raw_user_ids
4747
]
4848

4949
for field in ITEM_OPTIONAL_FIELDS:

ttd_databricks_python/ttd_databricks/handlers/third_party.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from ttd_databricks_python.ttd_databricks.constants import TTD_DATABRICKS_SDK_ORIGIN_ID
88
from ttd_databricks_python.ttd_databricks.contexts import ThirdPartyContext
9+
from ttd_databricks_python.ttd_databricks.id_types import normalize_id_type
910

1011
if TYPE_CHECKING:
1112
from ttd_data import DataClient
@@ -26,7 +27,7 @@ def build_items(items_data: list[dict[str, Any]]) -> list[ThirdPartyDataItem]:
2627
tp_data_kwargs[field] = d[field]
2728

2829
tp_item_kwargs = {
29-
d["id_type"]: d["id_value"],
30+
normalize_id_type(d["id_type"]): d["id_value"],
3031
"data": [ThirdPartyData(**tp_data_kwargs)],
3132
}
3233
for field in ITEM_OPTIONAL_FIELDS:
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""User-facing id_type names and normalization to ttd-data Pydantic field names."""
2+
3+
from __future__ import annotations
4+
5+
# TTD public documentation specifies id_type values in PascalCase/ALLCAPS.
6+
# This dict maps those user-facing id_type values to the corresponding Python
7+
# field names on the ttd-data Pydantic models, which use snake_case field names
8+
# with PascalCase/ALLCAPS aliases for the wire format.
9+
_NORMALIZATION: dict[str, str] = {
10+
"TDID": "tdid",
11+
"DAID": "daid",
12+
"UID2": "uid2",
13+
"UID2Token": "uid2_token",
14+
"EUID": "euid",
15+
"EUIDToken": "euid_token",
16+
"RampID": "ramp_id",
17+
"ID5": "id5",
18+
"netID": "net_id",
19+
"FirstId": "first_id"
20+
}
21+
22+
VALID_ID_TYPES: frozenset[str] = frozenset(_NORMALIZATION)
23+
24+
25+
def normalize_id_type(id_type: str) -> str:
26+
"""Map a user-facing id_type to the Python field name expected by ttd-data models.
27+
28+
Raises ValueError for unrecognized values.
29+
"""
30+
try:
31+
return _NORMALIZATION[id_type]
32+
except KeyError:
33+
valid = ", ".join(sorted(_NORMALIZATION))
34+
raise ValueError(f"Unknown id_type {id_type!r}. Must be one of: {valid}.")

ttd_databricks_python/ttd_databricks/schemas/advertiser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ def input_schema() -> StructType:
3434
3535
Mandatory columns (not nullable):
3636
id_type → which AdvertiserDataItem identity field this row uses.
37-
Must be one of: tdid, daid, uid2, uid2_token, ramp_id, core_id,
38-
euid, euid_token, id5, net_id, first_id, merkury_id, iqvia_ppid.
37+
Must be one of: TDID, DAID, UID2, UID2Token, EUID, EUIDToken,
38+
RampID, ID5, netID, FirstId, CoreID, MerkuryID, IqviaPPID.
3939
id_value → the identifier value for the given id_type.
4040
segment_name → AdvertiserData.name (audience segment / data element name).
4141

0 commit comments

Comments
 (0)