Integrate UI d2 python sdk into ttd data#24
Conversation
13dc2e0 to
7a51c53
Compare
7a51c53 to
c10c376
Compare
| dev: {} | ||
| main: {} | ||
| main: | ||
| uid2-client: '>=2.9.0' |
There was a problem hiding this comment.
Should we specify max too in case 3.x is a breaking change?
There was a problem hiding this comment.
Yes, would be safe.
| skipResponseBodyAssertions: false | ||
| python: | ||
| version: 0.0.1 | ||
| version: 0.1.0 |
There was a problem hiding this comment.
Why is local delta quite different from the non local one?
- UID2 client dependency
- versioning pattern
- persistentEdits
There was a problem hiding this comment.
copied over the main overlay and gen.yaml files into the data-api-local, versioning is off since the local one is not generated or updated everytime we male changes.
| OfflineConversionDataItem( | ||
| tracking_tag_id=TRACKING_TAG_ID, | ||
| timestamp_utc=now, | ||
| user_id_array=[["-1", HASHED_EMAIL], ["-3", RAW_EMAIL]], |
There was a problem hiding this comment.
this is a bit hacky.. let's chat about some better solutions
There was a problem hiding this comment.
We already have the UserIdType enum defined, I have added those values in tests, both work.
| @@ -0,0 +1,488 @@ | |||
| from __future__ import annotations | |||
|
|
|||
| # `BaseDataClient` wrapper. Pass `uid2_config` to enable UID2 identity-map | |||
There was a problem hiding this comment.
Current comment is a bit confusing imo. Consider something along the lines of:
Extends BaseDataClient to support PII to UID2 identity-mapping. To enable UID2 identity-mapping pass uid2_config.
There was a problem hiding this comment.
changed the comment.
| <!-- Start SDK Example Usage [usage] --> | ||
| ```python | ||
| # Synchronous Example | ||
| from ttd_data import DataClient |
There was a problem hiding this comment.
I think from ttd_data import BaseDataClient will fail due to not being exported at top level anymore, no?
Same in readme.md
| ) | ||
|
|
||
|
|
||
| _UID2_ERROR_CODE = "Uid2Error" |
There was a problem hiding this comment.
This is a bit of a magic string, can we add it to overlay to get this error code generated by speakeasy alongside other error codes?
There was a problem hiding this comment.
This is a Item level error and the remaining error files we have are exceptions raised to the client. I did move the existing exception UID2ServiceError to be imported alongside other errors.
|
|
||
|
|
||
| def test_data_client_without_uid2_config_skips_resolution(monkeypatch): | ||
| """`DataClient()` with no `uid2_config` must not call the UID2 SDK. |
There was a problem hiding this comment.
What if I pass an email and no uid2 config?
There was a problem hiding this comment.
converted: List[Any] = [ base_cls.model_validate(it.model_dump(by_alias=True)) if isinstance(it, wrapper_cls) else it for it in items ]
this snippet on the _prepare_items_for_request converts the objects to base class, it drops anything which base class does not support.
| # ] | ||
| # /// | ||
|
|
||
| from ttd_data import DataClient |
There was a problem hiding this comment.
We need to fix the imports and update readme and pypi readme examples with UID2 content (the SDK Example Usage section)
There was a problem hiding this comment.
Had a look at both README and USAGE files, README has mixed ownership and has sections which can survive speakeasy regenration, however if speakeasy pipeline on github runs again, it will rewrite the BaseDataClient. Usage is entirely owned by speakeasy and will be wiped out
3e40441 to
81ac678
Compare
81ac678 to
47cbe14
Compare
UID2 integration into
ttd-dataPython SDKAdds
ttd_data.DataClient— the recommended top-level client for thettd-data SDK. It enables ingesting advertiser, third-party, offline-conversion,
and deletion-opt-out data to the Trade Desk Data API endpoints, and — when
a
UID2Configis supplied — resolves raw PII (email/phone/hashed_email/hashed_phone) into UID2 (or EUID) tokens via the UID2IdentityMapV3ClientSDK before the request leaves the process, thenforwards a UID2-only payload to the Trade Desk Data APIs.
The Speakeasy-generated client is treated as an internal building block.
It is not re-exported from the package root and callers should not use
it directly.
Files
src/ttd_data/client.pyDataClientwrapper aroundBaseDataClientthat wires the resolver into each endpoint via sub-SDK proxies and merges UID2 failures back ontofailed_lines.src/ttd_data/uid2/config.pyUID2Configdataclass +IdentityScopeenum (UID2/EUID) holding base_url, api_key, client_secret.src/ttd_data/uid2/models.pyAdvertiserDataItem,ThirdPartyDataItem,OfflineConversionDataItem,PartnerDsrDataItem) that add raw-PII fields, plus response wrappers that attachidentity_resolutions.src/ttd_data/uid2/resolver.pyttd_data.DataClient,UID2Config, andIdentityScopeare re-exportedfrom the package root. UID2-specific item / response classes live under
ttd_data.uid2.Where to start reviewing
src/ttd_data/client.py—DataClientclass docstring + thesub-SDK proxies show the request lifecycle (resolve → convert →
request → merge → wrap).
src/ttd_data/uid2/resolver.py— module docstring covers theresolution rules, sentinel
"*"behavior, and retry/abort policy.src/ttd_data/uid2/models.py— see how subclass items extend theSpeakeasy base models with raw-PII fields and a
model_validatorthat enforces "at most one UID2-family identifier per item".
src/ttd_data/uid2/config.py— small, read this last.data-api-local/test_uid2_ingest.pyexercisesall six wrapped endpoints end-to-end.
Flow / assumptions
DataClient(uid2_config=None)is a thin pass-through overBaseDataClient— no UID2 work, same wire payload. Resolution onlyruns when a
UID2Configis supplied.validates or serializes, so subclass-only raw fields aren't stripped.
resolved UID2/EUID (or TDID) is sent on the wire.
"*"substituted so linenumbers stay 1:1; the Trade Desk Data APIs reject
"*", and the SDKmerges those rejections back into
failed_lineswithErrorCode = "Uid2Error".raise
UID2ServiceErrorand abort the request entirely../+handling) and validates raw phones (E.164) before hashing —what travels to the operator is always SHA-256+base64 hashes, never
plaintext.
Glossary of the key classes
DataClient— top-level client for the ttd-data SDK; wrapsBaseDataClient(the Speakeasy SDK) and, whenuid2_configissupplied, adds UID2 resolution on the six PII-bearing endpoints.
BaseDataClient— the raw Speakeasy-generated client; internalbuilding block, not exported from the package root.
UID2Config— credentials + scope (UID2 vs EUID) for the UID2 SDK._AdvertiserProxy,_ThirdPartyProxy, etc.) —thin per-area proxy exposed as
client.advertiser,client.third_party,... that runs
resolve → convert → request → merge → wrapand fallsthrough to the underlying Speakeasy sub-SDK for any unwrapped method.
AdvertiserDataItem,ThirdPartyDataItem,OfflineConversionDataItem,PartnerDsrDataItem) — extends theSpeakeasy base item with raw PII fields (
email,phone,hashed_email,hashed_phone) that the resolver consumes; convertedto the base item before the HTTP request is built.
IngestAdvertiserDataResponse, etc.) — wraps theSpeakeasy response and attaches
identity_resolutionsso callers caninspect each raw id's resolved UID2 / unmapped reason.
Endpoints wrapped
client.advertiser.ingest_advertiser_dataclient.third_party.ingest_third_party_dataclient.offline_conversion.ingest_offline_conversion_dataclient.deletion_opt_out.data_subject_request_advertiser_dataclient.deletion_opt_out.data_subject_request_merchant_dataclient.deletion_opt_out.data_subject_request_third_party_dataEach has a sync and
_asyncvariant. Any other Speakeasy method fallsthrough unchanged via
__getattr__.Tests:
advertiserthirdpartyoffline conversiondeletion-optout advertiserdeletion-optout thirdpartydeletion-optout merchantTest UserIdType Enum for
offline conversionsCheck if its a breaking change for clients:
I created this script with some test entried of how clients can send data to various TTD Data APIs:
local_data_api_ingest_e2e.py
I run the script on the current pypi version: