Skip to content

Commit 210058e

Browse files
karlwaldmanclaude
andauthored
fix(telemetry): send X-SDK-Language and X-Client-Type headers (#4) (#40)
Every SDK request must identify itself to the API for adoption tracking. The sync and async clients already sent X-SDK-Name and X-SDK-Version but omitted X-SDK-Language and X-Client-Type, so 0/166 recent requests could be attributed to the Python SDK vs raw python-httpx/python-requests calls. Add the two missing telemetry headers to both OilPriceAPI (sync) and AsyncOilPriceAPI clients: X-SDK-Language: python X-Client-Type: sdk Change is additive (4 lines) and does not alter existing behavior. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7c7b777 commit 210058e

3 files changed

Lines changed: 111 additions & 0 deletions

File tree

oilpriceapi/async_client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ def __init__(
119119
"User-Agent": f"{SDK_NAME}/{SDK_VERSION} python/{python_version}",
120120
"X-SDK-Name": SDK_NAME,
121121
"X-SDK-Version": SDK_VERSION,
122+
"X-SDK-Language": "python",
123+
"X-Client-Type": "sdk",
122124
}
123125

124126
# Add optional telemetry headers (10% bonus for app_url!)

oilpriceapi/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ def __init__(
142142
"User-Agent": f"{SDK_NAME}/{SDK_VERSION} python/{python_version}",
143143
"X-SDK-Name": SDK_NAME,
144144
"X-SDK-Version": SDK_VERSION,
145+
"X-SDK-Language": "python",
146+
"X-Client-Type": "sdk",
145147
}
146148

147149
# Add optional telemetry headers (10% bonus for app_url!)

tests/test_telemetry_headers.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""
2+
Tests for SDK telemetry headers (issue #4).
3+
4+
The API tracks SDK adoption via request headers. Every SDK request must
5+
identify itself with:
6+
7+
X-SDK-Language: python
8+
X-SDK-Version: <version>
9+
X-Client-Type: sdk
10+
11+
Issue #4 reported that ``X-SDK-Language`` and ``X-Client-Type`` were never
12+
reaching the API (0/166 requests), making it impossible to distinguish SDK
13+
traffic from manual ``python-requests``/``python-httpx`` calls. These tests
14+
encode the acceptance criteria for both the sync and async clients, asserting
15+
the headers are both configured on the client and actually transmitted on the
16+
wire.
17+
"""
18+
19+
import asyncio
20+
21+
import httpx
22+
23+
from oilpriceapi import OilPriceAPI
24+
from oilpriceapi.async_client import AsyncOilPriceAPI
25+
from oilpriceapi.version import SDK_VERSION
26+
27+
28+
class TestSyncTelemetryHeaders:
29+
"""Sync client must advertise SDK telemetry headers."""
30+
31+
def test_headers_include_sdk_language(self):
32+
client = OilPriceAPI(api_key="test_key")
33+
assert client.headers["X-SDK-Language"] == "python"
34+
35+
def test_headers_include_client_type(self):
36+
client = OilPriceAPI(api_key="test_key")
37+
assert client.headers["X-Client-Type"] == "sdk"
38+
39+
def test_headers_include_sdk_version(self):
40+
client = OilPriceAPI(api_key="test_key")
41+
assert client.headers["X-SDK-Version"] == SDK_VERSION
42+
43+
def test_telemetry_headers_sent_on_request(self):
44+
"""Headers must actually be transmitted, not just stored locally."""
45+
captured = {}
46+
47+
def handler(request: httpx.Request) -> httpx.Response:
48+
captured.update(request.headers)
49+
return httpx.Response(
50+
200,
51+
json={"data": {"price": 80.0, "commodity": "BRENT_CRUDE_USD"}},
52+
)
53+
54+
client = OilPriceAPI(api_key="test_key")
55+
client._client = httpx.Client(
56+
base_url=client.base_url,
57+
headers=client.headers,
58+
transport=httpx.MockTransport(handler),
59+
)
60+
client.request("GET", "/v1/prices/latest")
61+
62+
assert captured.get("x-sdk-language") == "python"
63+
assert captured.get("x-client-type") == "sdk"
64+
assert captured.get("x-sdk-version") == SDK_VERSION
65+
66+
67+
class TestAsyncTelemetryHeaders:
68+
"""Async client must advertise SDK telemetry headers."""
69+
70+
def test_headers_include_sdk_language(self):
71+
client = AsyncOilPriceAPI(api_key="test_key")
72+
assert client.headers["X-SDK-Language"] == "python"
73+
74+
def test_headers_include_client_type(self):
75+
client = AsyncOilPriceAPI(api_key="test_key")
76+
assert client.headers["X-Client-Type"] == "sdk"
77+
78+
def test_headers_include_sdk_version(self):
79+
client = AsyncOilPriceAPI(api_key="test_key")
80+
assert client.headers["X-SDK-Version"] == SDK_VERSION
81+
82+
def test_telemetry_headers_sent_on_request(self):
83+
"""Async requests must transmit telemetry headers on the wire."""
84+
captured = {}
85+
86+
def handler(request: httpx.Request) -> httpx.Response:
87+
captured.update(request.headers)
88+
return httpx.Response(
89+
200,
90+
json={"data": {"price": 80.0, "commodity": "BRENT_CRUDE_USD"}},
91+
)
92+
93+
async def run():
94+
client = AsyncOilPriceAPI(api_key="test_key")
95+
client._client = httpx.AsyncClient(
96+
base_url=client.base_url,
97+
headers=client.headers,
98+
transport=httpx.MockTransport(handler),
99+
)
100+
await client.request("GET", "/v1/prices/latest")
101+
await client._client.aclose()
102+
103+
asyncio.run(run())
104+
105+
assert captured.get("x-sdk-language") == "python"
106+
assert captured.get("x-client-type") == "sdk"
107+
assert captured.get("x-sdk-version") == SDK_VERSION

0 commit comments

Comments
 (0)