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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ restructure.sh
.agents
.claude
.cursor
# Impala subgraph SDL used by scripts/validate_graphql.py and the offline
# schema test. Never committed: it carries internal feature-flag/permission
# names, tenant-specific policy, and staff email addresses from deprecation
# directives. Keep it local, or point TTD_GRAPHQL_SCHEMA_PATH elsewhere.
impala.graphql
16 changes: 8 additions & 8 deletions .speakeasy/gen.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ management:
docVersion: v0.1
speakeasyVersion: 1.794.0
generationVersion: 2.930.0
releaseVersion: 0.3.0
configChecksum: 8954e0a167cf29392916ea503e1e7f85
releaseVersion: 0.3.1
configChecksum: 412022fe57a148beb9a811f85df27ac1
repoURL: https://github.com/thetradedesk/ttd-data-python.git
installationURL: https://github.com/thetradedesk/ttd-data-python.git
published: true
Expand All @@ -18,7 +18,7 @@ features:
python:
additionalDependencies: 1.1.0
constsAndDefaults: 1.0.7
core: 6.0.36
core: 6.1.0
defaultEnabledRetries: 0.2.0
devContainers: 3.0.0
enumUnions: 0.1.1
Expand All @@ -33,7 +33,7 @@ features:
nameOverrides: 3.0.3
nullables: 1.0.2
responseFormat: 1.1.0
retries: 3.0.7
retries: 3.1.0
sdkHooks: 1.3.0
trackedFiles:
.devcontainer/devcontainer.json:
Expand Down Expand Up @@ -682,8 +682,8 @@ trackedFiles:
pristine_git_object: 5bc70856007fcb6807f1799f8a7900ae7a58aa1a
src/ttd_data/utils/retries.py:
id: 04accebbe68a
last_write_checksum: sha1:72b69874b569187013180f4ee1729e033016aa57
pristine_git_object: 5cef7c40b61d7110bb49931d20ba1a8da02769a8
last_write_checksum: sha1:ec14524be2802965a615b7a57d5d2bfe2149968f
pristine_git_object: f20abf23165385c311373cd59acafdbb26b82f01
src/ttd_data/utils/security.py:
id: e38af000ccc5
last_write_checksum: sha1:d7d901c2ae152ed07734fc7c7943723bcf38cffc
Expand All @@ -702,8 +702,8 @@ trackedFiles:
pristine_git_object: 178fae653a8b5c5e166f124ad4ce590f87085cb9
src/ttd_data/utils/values.py:
id: 45979a7770a1
last_write_checksum: sha1:3a4ec34b2221dd825f7432e2d913b21d8ff92506
pristine_git_object: 53c869685607f3756168d82e0cfaa581d333efbc
last_write_checksum: sha1:430ffa8f56b20db092f98952b2f4c909fbcee6f0
pristine_git_object: a75c6e1622dcdc3eeef51fc51acbcb8f5a190ac2
examples:
IngestAdvertiserData:
speakeasy-default-ingest-advertiser-data:
Expand Down
2 changes: 1 addition & 1 deletion .speakeasy/gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ generation:
generateNewTests: false
skipResponseBodyAssertions: false
python:
version: 0.3.0
version: 0.3.1
additionalDependencies:
dev: {}
main:
Expand Down
2 changes: 1 addition & 1 deletion .speakeasy/workflow.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
speakeasyVersion: 1.794.0
speakeasyVersion: 1.795.3
sources:
Data API:
sourceNamespace: data-api
Expand Down
51 changes: 51 additions & 0 deletions README-PYPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,57 @@ async def main():

asyncio.run(main())
```
### 9. Third-Party Data Taxonomy (GraphQL)

Taxonomy operations run as GraphQL requests against the Platform API supergraph. They live under `client.third_party_taxonomy`, and the platform token is passed per call as `ttd_auth`.

| SDK function | REST equivalent | What it does |
| --- | --- | --- |
| `query_segments(...)` | [`POST /v3/thirdpartydata/query`](https://open.thetradedesk.com/provider/docsApp/GuidesProvider/audience/ref/post-thirdpartydata-query) | Lists a provider's segments, optionally filtered by element ID. |
| `query_segment_taxonomy_status(...)` | [`GET /v3/thirdpartydata/status/{providerId}/{providerElementId}`](https://open.thetradedesk.com/provider/docsApp/GuidesProvider/audience/ref/get-thirdpartydata-status-providerid-providerelementid) | Returns one segment's taxonomy approval status. |
| `upsert_segments(...)` | [`POST`](https://open.thetradedesk.com/provider/docsApp/GuidesProvider/audience/ref/post-thirdpartydata) / [`PUT /v3/thirdpartydata`](https://open.thetradedesk.com/provider/docsApp/GuidesProvider/audience/ref/put-thirdpartydata) | Creates or updates up to 1000 segments in one call. |
| `client.graphql.execute(...)` | — | Sends any GraphQL document and returns the parsed response body. |

```python
from ttd_data import DataClient

client = DataClient()

# List segments. Paginate with page.end_cursor while page.has_next_page.
page = client.third_party_taxonomy.query_segments(
ttd_auth=TTD_AUTH_TOKEN, provider_id=PROVIDER_ID, first=100
)
for node in page.nodes:
print(node["providerElementId"], node["taxonomyApprovalStatus"])

# One segment's approval status: APPROVED, DENIED, PENDING, NOT_IN_QUEUE, or None.
status = client.third_party_taxonomy.query_segment_taxonomy_status(
ttd_auth=TTD_AUTH_TOKEN, provider_id=PROVIDER_ID, provider_element_id="auto/in-market/ev"
)

# Create or update. The server decides per segment and reports it as `mode`.
result = client.third_party_taxonomy.upsert_segments(
ttd_auth=TTD_AUTH_TOKEN,
segments=[
{
"providerId": PROVIDER_ID,
"providerElementId": "auto/in-market/ev",
"displayName": "Interest > Auto > In-Market > EV",
"parentElementId": "ROOT",
"buyable": True,
}
],
)
for entry in result.succeeded:
print(entry["mode"], entry["segment"]["id"]) # CREATE or UPDATE
if result.failed:
print(result.failed) # a batch can partially succeed; always check this
```

Each segment is a `SegmentInput`. `providerId` and `providerElementId` are always required; `displayName`, `parentElementId` and `buyable` are additionally required when creating. Keys are camelCase to match the schema, and omitting a key leaves that field unchanged on update.

The supergraph reports authorization and policy failures as HTTP 200 with a top-level `errors` array, so those raise `GraphQLError` — which, like `APIError`, derives from `DataError`, so one `except DataError` covers the REST and GraphQL suites. Retries use the same configuration as the REST operations.

<!-- No SDK Example Usage [usage] -->

<!-- Start Available Resources and Operations [operations] -->
Expand Down
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,57 @@ async def main():

asyncio.run(main())
```
### 9. Third-Party Data Taxonomy (GraphQL)

Taxonomy operations run as GraphQL requests against the Platform API supergraph. They live under `client.third_party_taxonomy`, and the platform token is passed per call as `ttd_auth`.

| SDK function | REST equivalent | What it does |
| --- | --- | --- |
| `query_segments(...)` | [`POST /v3/thirdpartydata/query`](https://open.thetradedesk.com/provider/docsApp/GuidesProvider/audience/ref/post-thirdpartydata-query) | Lists a provider's segments, optionally filtered by element ID. |
| `query_segment_taxonomy_status(...)` | [`GET /v3/thirdpartydata/status/{providerId}/{providerElementId}`](https://open.thetradedesk.com/provider/docsApp/GuidesProvider/audience/ref/get-thirdpartydata-status-providerid-providerelementid) | Returns one segment's taxonomy approval status. |
| `upsert_segments(...)` | [`POST`](https://open.thetradedesk.com/provider/docsApp/GuidesProvider/audience/ref/post-thirdpartydata) / [`PUT /v3/thirdpartydata`](https://open.thetradedesk.com/provider/docsApp/GuidesProvider/audience/ref/put-thirdpartydata) | Creates or updates up to 1000 segments in one call. |
| `client.graphql.execute(...)` | — | Sends any GraphQL document and returns the parsed response body. |

```python
from ttd_data import DataClient

client = DataClient()

# List segments. Paginate with page.end_cursor while page.has_next_page.
page = client.third_party_taxonomy.query_segments(
ttd_auth=TTD_AUTH_TOKEN, provider_id=PROVIDER_ID, first=100
)
for node in page.nodes:
print(node["providerElementId"], node["taxonomyApprovalStatus"])

# One segment's approval status: APPROVED, DENIED, PENDING, NOT_IN_QUEUE, or None.
status = client.third_party_taxonomy.query_segment_taxonomy_status(
ttd_auth=TTD_AUTH_TOKEN, provider_id=PROVIDER_ID, provider_element_id="auto/in-market/ev"
)

# Create or update. The server decides per segment and reports it as `mode`.
result = client.third_party_taxonomy.upsert_segments(
ttd_auth=TTD_AUTH_TOKEN,
segments=[
{
"providerId": PROVIDER_ID,
"providerElementId": "auto/in-market/ev",
"displayName": "Interest > Auto > In-Market > EV",
"parentElementId": "ROOT",
"buyable": True,
}
],
)
for entry in result.succeeded:
print(entry["mode"], entry["segment"]["id"]) # CREATE or UPDATE
if result.failed:
print(result.failed) # a batch can partially succeed; always check this
```

Each segment is a `SegmentInput`. `providerId` and `providerElementId` are always required; `displayName`, `parentElementId` and `buyable` are additionally required when creating. Keys are camelCase to match the schema, and omitting a key leaves that field unchanged on update.

The supergraph reports authorization and policy failures as HTTP 200 with a top-level `errors` array, so those raise `GraphQLError` — which, like `APIError`, derives from `DataError`, so one `except DataError` covers the REST and GraphQL suites. Retries use the same configuration as the REST operations.

<!-- No SDK Example Usage [usage] -->

<!-- Start Available Resources and Operations [operations] -->
Expand Down
119 changes: 119 additions & 0 deletions examples/graphql_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""Example: third-party data taxonomy GraphQL operations via
ttd-data-python's DataClient.

TTD_AUTH_TOKEN=... required. Platform token, sent as `TTD-Auth`.
GRAPHQL_EXAMPLE_PROVIDER_ID=... required. Provider to operate on.
GRAPHQL_EXAMPLE_ELEMENT_ID=... optional. Segment to operate on.

Nothing is guessed: every operation acts on exactly what you name. Without
GRAPHQL_EXAMPLE_ELEMENT_ID the file lists segments and runs the escape-hatch
query only. With it, it also reads that segment's approval status, filters to
it, and upserts it — which creates or updates it in the provider's taxonomy.

TTD_AUTH_TOKEN=... GRAPHQL_EXAMPLE_PROVIDER_ID=... \
python examples/graphql_example.py
"""

import json
import os

from ttd_data import DataClient
from ttd_data.graphql import Page


def required(name: str, description: str) -> str:
value = os.getenv(name, "").strip()
if not value:
raise SystemExit(f"Set {name} to {description}.")
return value


token = required("TTD_AUTH_TOKEN", "a platform token")
PROVIDER_ID = required("GRAPHQL_EXAMPLE_PROVIDER_ID", "the provider to operate on")

# Optional: the upsert is skipped entirely when this is unset.
ELEMENT_ID = os.getenv("GRAPHQL_EXAMPLE_ELEMENT_ID", "").strip()

client = DataClient(ttd_auth=token)


def show(label: str, page: Page) -> None:
print(f"\n{'=' * 60}\n {label} ({page.total_count} total)\n{'=' * 60}")
print(json.dumps(page.nodes, indent=2))


# ---------------------------------------------------------------------------
# Taxonomy
# ---------------------------------------------------------------------------

segments = client.third_party_taxonomy.query_segments(
provider_id=PROVIDER_ID, first=5
)
show("Segments for provider", segments)

if segments.has_next_page:
show(
"Segments (next page)",
client.third_party_taxonomy.query_segments(
provider_id=PROVIDER_ID,
first=5,
after=segments.end_cursor,
),
)

if not ELEMENT_ID:
print("\nSkipping status and filter (set GRAPHQL_EXAMPLE_ELEMENT_ID to run them).")
else:
status = client.third_party_taxonomy.query_segment_taxonomy_status(
provider_id=PROVIDER_ID, provider_element_id=ELEMENT_ID
)
print(f"\nTaxonomy approval status for {ELEMENT_ID}: {status}")
show(
f"Segments filtered to {ELEMENT_ID}",
client.third_party_taxonomy.query_segments(
provider_id=PROVIDER_ID, provider_element_ids=[ELEMENT_ID]
),
)

# ---------------------------------------------------------------------------
# Escape hatch: anything the typed methods do not cover
# ---------------------------------------------------------------------------

# `execute` returns the raw body — the typed methods are what wrap it.
raw = client.graphql.execute(
query="""
query ThirdPartyDataProvider($id: ID!) {
thirdPartyDataProvider(id: $id) {
id
name
}
}
""",
variables={"id": PROVIDER_ID},
)
print(f"\n{'=' * 60}\n Arbitrary query via execute()\n{'=' * 60}")
print(json.dumps(raw.get("data"), indent=2))

# ---------------------------------------------------------------------------
# Mutation — this writes to the provider's taxonomy
# ---------------------------------------------------------------------------

if not ELEMENT_ID:
print("\nSkipping upsert (set GRAPHQL_EXAMPLE_ELEMENT_ID to run it).")
else:
result = client.third_party_taxonomy.upsert_segments(
segments=[
{
"providerId": PROVIDER_ID,
"providerElementId": ELEMENT_ID,
"displayName": "Example > SDK Test Segment",
"parentElementId": "ROOT",
"buyable": True,
"description": "Created by examples/graphql_example.py",
}
],
)
print(f"\n{'=' * 60}\n Upsert segment\n{'=' * 60}")
print(f"accepted: {json.dumps(result.succeeded, indent=2)}")
if result.failed:
print(f"rejected: {json.dumps(result.failed, indent=2)}")
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ttd-data"
version = "0.3.0"
version = "0.3.1"
description = "Python Client SDK for TTD Data API."
authors = [{ name = "Speakeasy" },]
readme = "README-PYPI.md"
Expand All @@ -16,6 +16,7 @@ license = { text = "map[name:Apache License 2.0 shortName:Apache-2.0 url:https:/

[dependency-groups]
dev = [
"graphql-core >=3.2.0,<4.0.0",
"mypy ==1.15.0",
"pylint ==3.2.3",
"pyright ==1.1.398",
Expand Down
Loading
Loading