Skip to content
Open
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
21 changes: 21 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,24 @@ jobs:

- name: pylint
run: uv run python -m pylint -j=0 src/ttd_data

tests:
name: Unit tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up uv
uses: astral-sh/setup-uv@v5

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install dependencies
run: uv sync --group dev

- name: pytest
run: uv run python -m pytest tests/unit -v
463 changes: 232 additions & 231 deletions .speakeasy/gen.lock

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions .speakeasy/gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ generation:
devContainers:
enabled: true
schemaPath: .speakeasy/out.openapi.yaml
sdkClassName: DataClient
sdkClassName: BaseDataClient
maintainOpenAPIOrder: true
usageSnippets:
optionalPropertyRendering: withExample
Expand All @@ -17,7 +17,7 @@ generation:
securityFeb2025: true
sharedErrorComponentsApr2025: true
sharedNestedComponentsJan2026: true
nameOverrideFeb2026: false
nameOverrideFeb2026: true
auth:
oAuth2ClientCredentialsEnabled: true
oAuth2PasswordEnabled: true
Expand All @@ -28,16 +28,18 @@ generation:
allOfMergeStrategy: shallowMerge
requestBodyFieldName: body
versioningStrategy: automatic
persistentEdits: {}
persistentEdits:
enabled: "true"
tests:
generateTests: true
generateNewTests: false
skipResponseBodyAssertions: false
python:
version: 0.1.7
version: 0.2.10
additionalDependencies:
dev: {}
main: {}
main:
uid2-client: '>=2.9.0,<3.0.0'
allowedRedefinedBuiltins:
- id
- object
Expand Down
4 changes: 4 additions & 0 deletions .speakeasy/out.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ components:
type: "string"
nullable: true
additionalProperties: false
x-speakeasy-name-override: BaseAdvertiserDataItem
AdvertiserDataRequest:
required:
- "AdvertiserId"
Expand Down Expand Up @@ -718,6 +719,7 @@ components:
type: "string"
nullable: true
additionalProperties: false
x-speakeasy-name-override: BaseOfflineConversionDataItem
OfflineConversionDataRequest:
required:
- "DataProviderId"
Expand Down Expand Up @@ -836,6 +838,7 @@ components:
type: "string"
nullable: true
additionalProperties: false
x-speakeasy-name-override: BasePartnerDsrDataItem
PartnerDsrRequestType:
enum:
- "OptOut"
Expand Down Expand Up @@ -954,6 +957,7 @@ components:
type: "string"
nullable: true
additionalProperties: false
x-speakeasy-name-override: BaseThirdPartyDataItem
ThirdPartyDataRequest:
required:
- "DataProviderId"
Expand Down
18 changes: 17 additions & 1 deletion .speakeasy/overlay.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,20 @@ actions:
type: array
items:
$ref: "#/components/schemas/DataOrigin"
nullable: true
nullable: true

# Rename the generated schema classes so the public names are free for the
# hand-written wrappers in `ttd_data.uid2_ext` that add raw email / phone /
# hashed_email / hashed_phone fields.
- target: "$.components.schemas.AdvertiserDataItem"
update:
x-speakeasy-name-override: BaseAdvertiserDataItem
- target: "$.components.schemas.ThirdPartyDataItem"
update:
x-speakeasy-name-override: BaseThirdPartyDataItem
- target: "$.components.schemas.PartnerDsrDataItem"
update:
x-speakeasy-name-override: BasePartnerDsrDataItem
- target: "$.components.schemas.OfflineConversionDataItem"
update:
x-speakeasy-name-override: BaseOfflineConversionDataItem
29 changes: 20 additions & 9 deletions .speakeasy/workflow.lock
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
speakeasyVersion: 1.761.5
speakeasyVersion: 1.763.1
sources:
Data API:
sourceNamespace: data-api
sourceRevisionDigest: sha256:ddc22a3c5d0bbf4810aa9555cd193bfdf6bee2c5d0843e01e7d0006fc505730f
sourceBlobDigest: sha256:c86d70812b7b8fb35ad731b6755a72a8c5d8a1bd62af3f1e64883c81264cd1fe
sourceRevisionDigest: sha256:0daf241a54a03955ac31c7434f554ddab15bd58de0eba952e0b9ae6a34f2eac7
sourceBlobDigest: sha256:c7806fbc0c6ce5c9db56204d8ad142e32949242388ae9b9f6cae3df05590ed30
tags:
- latest
- v0.1
Data API Local:
sourceNamespace: data-api-local
sourceRevisionDigest: sha256:4d928ad86686f71fb18f025908926675e9a4db92003d6db4b9261715295fe5bf
sourceBlobDigest: sha256:f937fae3db68ebe534207c942a10f8d6c2ba00b4da1097f0455f2dd3dabb3140
tags:
- latest
- v0.1
targets:
data-api:
source: Data API
sourceNamespace: data-api
sourceRevisionDigest: sha256:ddc22a3c5d0bbf4810aa9555cd193bfdf6bee2c5d0843e01e7d0006fc505730f
sourceBlobDigest: sha256:c86d70812b7b8fb35ad731b6755a72a8c5d8a1bd62af3f1e64883c81264cd1fe
sourceRevisionDigest: sha256:0daf241a54a03955ac31c7434f554ddab15bd58de0eba952e0b9ae6a34f2eac7
sourceBlobDigest: sha256:c7806fbc0c6ce5c9db56204d8ad142e32949242388ae9b9f6cae3df05590ed30
codeSamplesNamespace: data-api-python-code-samples
codeSamplesRevisionDigest: sha256:17559c1f3685fb8ada33a85032c16f1204816196020b860301f8852965c12233
codeSamplesRevisionDigest: sha256:399ba1e4b8d171fa5c1b27e26548dba753b770623eff87e51181cd84eba837a8
data-api-local:
source: Data API Local
sourceNamespace: data-api-local
sourceRevisionDigest: sha256:4d928ad86686f71fb18f025908926675e9a4db92003d6db4b9261715295fe5bf
sourceBlobDigest: sha256:f937fae3db68ebe534207c942a10f8d6c2ba00b4da1097f0455f2dd3dabb3140
codeSamplesNamespace: data-api-local-python-code-samples
codeSamplesRevisionDigest: sha256:0698d00298b9de56934c0d07e1b3893b38a4b2a2a2525efd5f5df34fe264f765
workflow:
workflowVersion: 1.0.0
speakeasyVersion: latest
Expand All @@ -31,9 +45,6 @@ workflow:
data-api:
target: python
source: Data API
publish:
pypi:
token: $pypi_token
codeSamples:
registry:
location: registry.speakeasyapi.dev/thetradedesk/data-api/data-api-python-code-samples
Expand Down
3 changes: 0 additions & 3 deletions .speakeasy/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ targets:
data-api:
target: python
source: Data API
publish:
pypi:
token: $pypi_token
codeSamples:
registry:
location: registry.speakeasyapi.dev/thetradedesk/data-api/data-api-python-code-samples
Expand Down
67 changes: 64 additions & 3 deletions README-PYPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,18 +166,29 @@ with DataClient() as client:
### 3. Offline Conversions Data (CAPI)

```python
from ttd_data import DataClient, models
from datetime import datetime, timezone
from ttd_data import DataClient, UserIdType, models

with DataClient() as client:
response = client.offline_conversion.ingest_offline_conversion_data(
ttd_auth=TTD_AUTH_TOKEN,
data_provider_id=DATA_PROVIDER_ID,
items=[
# Pre-resolved TDID
models.OfflineConversionDataItem(
tracking_tag_id=TRACKING_TAG_ID,
timestamp_utc=datetime.now(timezone.utc),
tdid="<TDID>",
)
),
# Multiple identifiers via UserIdArray
models.OfflineConversionDataItem(
tracking_tag_id=TRACKING_TAG_ID,
timestamp_utc=datetime.now(timezone.utc),
user_id_array=[
[UserIdType.TDID, "<TDID>"],
[UserIdType.UID2, "<UID2>"],
],
),
],
)
```
Expand Down Expand Up @@ -234,7 +245,57 @@ with DataClient() as client:
```


### 7. Async usage
### 7. UID2 Identity Mapping

Supply a `UID2Config` to resolve raw PII (email / phone) to UID2 before ingest. Per-item mapping failures (opted-out or unmapped identifiers) appear in `failed_lines` with `ErrorCode = "Uid2Error"`. A complete UID2 service failure raises `UID2ServiceError`.

```python
from ttd_data import DataClient, IdentityScope, UID2Config, UID2ServiceError
from ttd_data.models import AdvertiserData, AdvertiserDataItem

uid2_config = UID2Config(
base_url="<UID2_BASE_URL>",
api_key="<UID2_API_KEY>",
client_secret="<UID2_CLIENT_SECRET>",
identity_scope=IdentityScope.UID2,
)

try:
with DataClient(uid2_config=uid2_config, server_url="<TTD_DATA_SERVER_URL>") as client:
response = client.advertiser.ingest_advertiser_data(
ttd_auth=TTD_AUTH_TOKEN,
advertiser_id=ADVERTISER_ID,
items=[
# Raw email — resolved to UID2 before ingest
AdvertiserDataItem(
data=[AdvertiserData(name="loyalty_members")],
email="user@example.com",
),
# Pre-hashed email
AdvertiserDataItem(
data=[AdvertiserData(name="loyalty_members")],
hashed_email="<SHA256_BASE64>",
),
# Pre-resolved TDID — no UID2 work needed
AdvertiserDataItem(
data=[AdvertiserData(name="loyalty_members")],
tdid="<TDID>",
),
],
)

# Check for per-item mapping failures
server_response = response.advertiser_data_server_response
if server_response and server_response.failed_lines:
for line in server_response.failed_lines:
print(f"Item {line.item_number} failed: {line.error_code} — {line.message}")

except UID2ServiceError as e:
# The UID2 identity-map service itself failed — no items were sent
print(f"UID2 service error: {e}")
```

### 8. Async usage

The same SDK client can also be used to make asynchronous requests by importing asyncio.

Expand Down
67 changes: 64 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,18 +166,29 @@ with DataClient() as client:
### 3. Offline Conversions Data (CAPI)

```python
from ttd_data import DataClient, models
from datetime import datetime, timezone
from ttd_data import DataClient, UserIdType, models

with DataClient() as client:
response = client.offline_conversion.ingest_offline_conversion_data(
ttd_auth=TTD_AUTH_TOKEN,
data_provider_id=DATA_PROVIDER_ID,
items=[
# Pre-resolved TDID
models.OfflineConversionDataItem(
tracking_tag_id=TRACKING_TAG_ID,
timestamp_utc=datetime.now(timezone.utc),
tdid="<TDID>",
)
),
# Multiple identifiers via UserIdArray
models.OfflineConversionDataItem(
tracking_tag_id=TRACKING_TAG_ID,
timestamp_utc=datetime.now(timezone.utc),
user_id_array=[
[UserIdType.TDID, "<TDID>"],
[UserIdType.UID2, "<UID2>"],
],
),
],
)
```
Expand Down Expand Up @@ -234,7 +245,57 @@ with DataClient() as client:
```


### 7. Async usage
### 7. UID2 Identity Mapping

Supply a `UID2Config` to resolve raw PII (email / phone) to UID2 before ingest. Per-item mapping failures (opted-out or unmapped identifiers) appear in `failed_lines` with `ErrorCode = "Uid2Error"`. A complete UID2 service failure raises `UID2ServiceError`.

```python
from ttd_data import DataClient, IdentityScope, UID2Config, UID2ServiceError
from ttd_data.models import AdvertiserData, AdvertiserDataItem

uid2_config = UID2Config(
base_url="<UID2_BASE_URL>",
api_key="<UID2_API_KEY>",
client_secret="<UID2_CLIENT_SECRET>",
identity_scope=IdentityScope.UID2,
)

try:
with DataClient(uid2_config=uid2_config, server_url="<TTD_DATA_SERVER_URL>") as client:
response = client.advertiser.ingest_advertiser_data(
ttd_auth=TTD_AUTH_TOKEN,
advertiser_id=ADVERTISER_ID,
items=[
# Raw email — resolved to UID2 before ingest
AdvertiserDataItem(
data=[AdvertiserData(name="loyalty_members")],
email="user@example.com",
),
# Pre-hashed email
AdvertiserDataItem(
data=[AdvertiserData(name="loyalty_members")],
hashed_email="<SHA256_BASE64>",
),
# Pre-resolved TDID — no UID2 work needed
AdvertiserDataItem(
data=[AdvertiserData(name="loyalty_members")],
tdid="<TDID>",
),
],
)

# Check for per-item mapping failures
server_response = response.advertiser_data_server_response
if server_response and server_response.failed_lines:
for line in server_response.failed_lines:
print(f"Item {line.item_number} failed: {line.error_code} — {line.message}")

except UID2ServiceError as e:
# The UID2 identity-map service itself failed — no items were sent
print(f"UID2 service error: {e}")
```

### 8. Async usage

The same SDK client can also be used to make asynchronous requests by importing asyncio.

Expand Down
Loading
Loading