Skip to content

Integrate UI d2 python sdk into ttd data#24

Open
adithyasamavedhi-ttd wants to merge 3 commits into
mainfrom
Integrate-UID2-Python-SDK-into-ttd-data
Open

Integrate UI d2 python sdk into ttd data#24
adithyasamavedhi-ttd wants to merge 3 commits into
mainfrom
Integrate-UID2-Python-SDK-into-ttd-data

Conversation

@adithyasamavedhi-ttd
Copy link
Copy Markdown
Collaborator

@adithyasamavedhi-ttd adithyasamavedhi-ttd commented May 12, 2026

UID2 integration into ttd-data Python SDK

Adds ttd_data.DataClient — the recommended top-level client for the
ttd-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 UID2Config is supplied — resolves raw PII (email / phone /
hashed_email / hashed_phone) into UID2 (or EUID) tokens via the UID2
IdentityMapV3Client SDK before the request leaves the process, then
forwards 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

File Purpose (one line)
src/ttd_data/client.py DataClient wrapper around BaseDataClient that wires the resolver into each endpoint via sub-SDK proxies and merges UID2 failures back onto failed_lines.
src/ttd_data/uid2/config.py UID2Config dataclass + IdentityScope enum (UID2/EUID) holding base_url, api_key, client_secret.
src/ttd_data/uid2/models.py Subclassed data items (AdvertiserDataItem, ThirdPartyDataItem, OfflineConversionDataItem, PartnerDsrDataItem) that add raw-PII fields, plus response wrappers that attach identity_resolutions.
src/ttd_data/uid2/resolver.py The pre-request resolver: calls UID2 identity-map, mutates items in place (raw fields → UID2/EUID), and returns per-item resolutions + failed mappings.

ttd_data.DataClient, UID2Config, and IdentityScope are re-exported
from the package root. UID2-specific item / response classes live under
ttd_data.uid2.

Where to start reviewing

  1. src/ttd_data/client.pyDataClient class docstring + the
    sub-SDK proxies show the request lifecycle (resolve → convert →
    request → merge → wrap).
  2. src/ttd_data/uid2/resolver.py — module docstring covers the
    resolution rules, sentinel "*" behavior, and retry/abort policy.
  3. src/ttd_data/uid2/models.py — see how subclass items extend the
    Speakeasy base models with raw-PII fields and a model_validator
    that enforces "at most one UID2-family identifier per item".
  4. src/ttd_data/uid2/config.py — small, read this last.
  5. Smoke-test entrypoint: data-api-local/test_uid2_ingest.py exercises
    all six wrapped endpoints end-to-end.

Flow / assumptions

  • DataClient(uid2_config=None) is a thin pass-through over
    BaseDataClient — no UID2 work, same wire payload. Resolution only
    runs when a UID2Config is supplied.
  • UID2 resolution runs at the Python boundary, before Speakeasy
    validates or serializes, so subclass-only raw fields aren't stripped.
  • After resolution, raw PII fields are cleared on the item and only the
    resolved UID2/EUID (or TDID) is sent on the wire.
  • Per-item unmapped identifiers → sentinel "*" substituted so line
    numbers stay 1:1; the Trade Desk Data APIs reject "*", and the SDK
    merges those rejections back into failed_lines with
    ErrorCode = "Uid2Error".
  • Transient UID2 errors are retried (5x over 15s); catastrophic errors
    raise UID2ServiceError and abort the request entirely.
  • The UID2 SDK itself normalizes raw emails (lowercase, gmail-specific
    . / + 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; wraps
    BaseDataClient (the Speakeasy SDK) and, when uid2_config is
    supplied, adds UID2 resolution on the six PII-bearing endpoints.
  • BaseDataClient — the raw Speakeasy-generated client; internal
    building block, not exported from the package root.
  • UID2Config — credentials + scope (UID2 vs EUID) for the UID2 SDK.
  • Sub-SDK proxy (_AdvertiserProxy, _ThirdPartyProxy, etc.) —
    thin per-area proxy exposed as client.advertiser, client.third_party,
    ... that runs resolve → convert → request → merge → wrap and falls
    through to the underlying Speakeasy sub-SDK for any unwrapped method.
  • Subclassed data item (AdvertiserDataItem, ThirdPartyDataItem,
    OfflineConversionDataItem, PartnerDsrDataItem) — extends the
    Speakeasy base item with raw PII fields (email, phone,
    hashed_email, hashed_phone) that the resolver consumes; converted
    to the base item before the HTTP request is built.
  • Response wrapper (IngestAdvertiserDataResponse, etc.) — wraps the
    Speakeasy response and attaches identity_resolutions so callers can
    inspect each raw id's resolved UID2 / unmapped reason.

Endpoints wrapped

  • client.advertiser.ingest_advertiser_data
  • client.third_party.ingest_third_party_data
  • client.offline_conversion.ingest_offline_conversion_data
  • client.deletion_opt_out.data_subject_request_advertiser_data
  • client.deletion_opt_out.data_subject_request_merchant_data
  • client.deletion_opt_out.data_subject_request_third_party_data

Each has a sync and _async variant. Any other Speakeasy method falls
through unchanged via __getattr__.

Tests:

  1. advertiser
Screenshot 2026-05-14 at 1 24 41 PM
  1. thirdparty
Screenshot 2026-05-14 at 1 25 04 PM
  1. offline conversion
Screenshot 2026-05-14 at 1 38 14 PM
  1. deletion-optout advertiser
Screenshot 2026-05-14 at 1 24 07 PM
  1. deletion-optout thirdparty
Screenshot 2026-05-14 at 12 07 53 PM
  1. deletion-optout merchant
    Screenshot 2026-05-14 at 1 34 29 PM\

  2. Test UserIdType Enum for offline conversions

Screenshot 2026-05-15 at 5 18 41 PM

Check if its a breaking change for clients:

  1. 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

  2. I run the script on the current pypi version:

Screenshot 2026-05-15 at 3 02 49 PM Screenshot 2026-05-15 at 3 03 21 PM
  1. I run the script on this pr's sdk version:
Screenshot 2026-05-15 at 3 03 58 PM Screenshot 2026-05-15 at 3 04 30 PM

@adithyasamavedhi-ttd adithyasamavedhi-ttd requested a review from a team May 12, 2026 17:49
@adithyasamavedhi-ttd adithyasamavedhi-ttd force-pushed the Integrate-UID2-Python-SDK-into-ttd-data branch 11 times, most recently from 13dc2e0 to 7a51c53 Compare May 14, 2026 21:21
@adithyasamavedhi-ttd adithyasamavedhi-ttd force-pushed the Integrate-UID2-Python-SDK-into-ttd-data branch from 7a51c53 to c10c376 Compare May 14, 2026 21:40
Comment thread .speakeasy/gen.yaml Outdated
dev: {}
main: {}
main:
uid2-client: '>=2.9.0'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we specify max too in case 3.x is a breaking change?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, would be safe.

skipResponseBodyAssertions: false
python:
version: 0.0.1
version: 0.1.0
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is local delta quite different from the non local one?

  • UID2 client dependency
  • versioning pattern
  • persistentEdits

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread data-api-local/local_uid2_ingest_e2e.py Outdated
OfflineConversionDataItem(
tracking_tag_id=TRACKING_TAG_ID,
timestamp_utc=now,
user_id_array=[["-1", HASHED_EMAIL], ["-3", RAW_EMAIL]],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bit hacky.. let's chat about some better solutions

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have the UserIdType enum defined, I have added those values in tests, both work.

Comment thread src/ttd_data/client.py Outdated
@@ -0,0 +1,488 @@
from __future__ import annotations

# `BaseDataClient` wrapper. Pass `uid2_config` to enable UID2 identity-map
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed the comment.

Comment thread USAGE.md
<!-- Start SDK Example Usage [usage] -->
```python
# Synchronous Example
from ttd_data import DataClient
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think from ttd_data import BaseDataClient will fail due to not being exported at top level anymore, no?
Same in readme.md

Comment thread src/ttd_data/client.py
)


_UID2_ERROR_CODE = "Uid2Error"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread tests/unit/test_uid2.py


def test_data_client_without_uid2_config_skips_resolution(monkeypatch):
"""`DataClient()` with no `uid2_config` must not call the UID2 SDK.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I pass an email and no uid2 config?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread README.md
# ]
# ///

from ttd_data import DataClient
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to fix the imports and update readme and pypi readme examples with UID2 content (the SDK Example Usage section)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@adithyasamavedhi-ttd adithyasamavedhi-ttd force-pushed the Integrate-UID2-Python-SDK-into-ttd-data branch 4 times, most recently from 3e40441 to 81ac678 Compare May 16, 2026 00:19
@adithyasamavedhi-ttd adithyasamavedhi-ttd force-pushed the Integrate-UID2-Python-SDK-into-ttd-data branch from 81ac678 to 47cbe14 Compare May 16, 2026 00:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants