Skip to content

Commit eb40146

Browse files
authored
Remove nonexistent telemetry quota bonus claim (#91)
1 parent f17936a commit eb40146

8 files changed

Lines changed: 165 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
All notable changes to the OilPriceAPI Python SDK will be documented in this file.
44

5+
## [1.12.7] - 2026-08-12
6+
7+
### Fixed
8+
9+
- Removed a nonexistent request-limit bonus claim from sync and async
10+
usage-attribution header comments.
11+
- Added red-first recursive authored and installed-wheel claim coverage so
12+
telemetry or application metadata cannot be presented as changing account
13+
entitlements.
14+
515
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
616
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
717

@@ -151,7 +161,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
151161
- **Data Sources Resource**: `client.data_sources.list()`, `get()`, `create()`, `update()`, `delete()`, `test()`, `logs()`, `health()`, `rotate_credentials()` for data connector management
152162
- **Enhanced Alerts**: Added `test()`, `triggers()`, `analytics_history()` methods to existing alerts resource
153163
- **Data Connector Support**: `client.get_data_connector_prices()` for BYOS (Bring Your Own Subscription) prices
154-
- **Telemetry Headers**: `app_url` and `app_name` parameters for API usage attribution (10% rate limit bonus for app_url)
164+
- **Telemetry Headers**: `app_url` and `app_name` parameters for API usage attribution
155165

156166
### Fixed
157167

oilpriceapi/async_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def __init__(
121121
"X-Client-Type": "sdk",
122122
}
123123

124-
# Add optional telemetry headers (10% bonus for app_url!)
124+
# Add optional usage-attribution headers.
125125
if self.app_url:
126126
self.headers["X-App-URL"] = self.app_url
127127
if self.app_name:

oilpriceapi/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def __init__(
142142
"X-Client-Type": "sdk",
143143
}
144144

145-
# Add optional telemetry headers (10% bonus for app_url!)
145+
# Add optional usage-attribution headers.
146146
if self.app_url:
147147
self.headers["X-App-URL"] = self.app_url
148148
if self.app_name:

oilpriceapi/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
Used in __init__.py, client.py, and async_client.py.
66
"""
77

8-
__version__ = "1.12.6"
8+
__version__ = "1.12.7"
99
SDK_VERSION = __version__
1010
SDK_NAME = "oilpriceapi-python"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
66

77
[project]
88
name = "oilpriceapi"
9-
version = "1.12.6"
9+
version = "1.12.7"
1010
description = "Official Python SDK for source-timestamped OilPriceAPI energy data"
1111
authors = [
1212
{name = "OilPriceAPI", email = "support@oilpriceapi.com"}

scripts/validate_storefront_claims.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,39 @@
4141
_HTML_TAG_PATTERN = re.compile(r"<[^>]{1,500}>")
4242
_MAX_ACTION_COUNT_GAP = 64
4343
_MAX_RATE_SPAN = 200
44+
_MAX_TELEMETRY_REWARD_SPAN = 320
45+
_TELEMETRY_IDENTITY_PATTERN = re.compile(
46+
r"\b(?:telemetry|app(?:lication)?[- ]+(?:metadata|url|name)|"
47+
r"app[_ -]?url|app[_ -]?name|x-app-(?:url|name))\b",
48+
re.IGNORECASE,
49+
)
50+
_TELEMETRY_STRONG_REWARD_PATTERN = re.compile(
51+
r"\b(?:bonus|increase(?:s|d)?|unlock(?:s|ed)?|"
52+
r"earn(?:s|ed)?|grant(?:s|ed)?|reward(?:s|ed)?|boost(?:s|ed)?)\b",
53+
re.IGNORECASE,
54+
)
55+
_TELEMETRY_MODIFIER_REWARD_PATTERN = re.compile(
56+
r"\b(?:more|extra|additional)\b", re.IGNORECASE
57+
)
58+
_TELEMETRY_QUOTA_SIGNAL_PATTERN = re.compile(
59+
r"\b(?:api[- ]+)?(?:requests?|calls?|quota|limits?|allowances?|credits?)\b|"
60+
r"(?<![\w.])\d+(?:\.\d+)?\s*%",
61+
re.IGNORECASE,
62+
)
63+
_TELEMETRY_MODIFIER_GAP_WORDS = {
64+
"account",
65+
"annual",
66+
"api",
67+
"call",
68+
"daily",
69+
"hourly",
70+
"monthly",
71+
"quota",
72+
"rate",
73+
"request",
74+
"usage",
75+
}
76+
_MAX_STRONG_REWARD_SPAN = 160
4477
BLOCKED: Sequence[Tuple[str, Pattern[str]]] = (
4578
("real-time claim", re.compile(r"\breal[ -]?time\b", re.IGNORECASE)),
4679
(
@@ -230,6 +263,52 @@ def _fixed_rate_claims(text: str) -> List[str]:
230263
return claims
231264

232265

266+
def _telemetry_reward_claims(text: str) -> List[str]:
267+
"""Find attribution identity + reward + quota signals in one bounded sentence."""
268+
claims: List[str] = []
269+
seen: Set[Tuple[int, str]] = set()
270+
271+
for segment_offset, segment in _bounded_rate_segments(text):
272+
searchable = _HTML_TAG_PATTERN.sub(" ", segment)
273+
identities = list(_TELEMETRY_IDENTITY_PATTERN.finditer(searchable))
274+
quota_signals = list(_TELEMETRY_QUOTA_SIGNAL_PATTERN.finditer(searchable))
275+
strong_rewards = list(_TELEMETRY_STRONG_REWARD_PATTERN.finditer(searchable))
276+
modifier_rewards = list(_TELEMETRY_MODIFIER_REWARD_PATTERN.finditer(searchable))
277+
reward_pairs: List[Tuple[int, int]] = []
278+
for reward in strong_rewards:
279+
for quota_signal in quota_signals:
280+
start = min(reward.start(), quota_signal.start())
281+
end = max(reward.end(), quota_signal.end())
282+
if end - start <= _MAX_STRONG_REWARD_SPAN:
283+
reward_pairs.append((start, end))
284+
for reward in modifier_rewards:
285+
for quota_signal in quota_signals:
286+
if reward.end() > quota_signal.start():
287+
continue
288+
gap = searchable[reward.end() : quota_signal.start()]
289+
gap_words = re.findall(r"[a-z]+", gap.lower())
290+
if len(gap) <= 48 and all(
291+
word in _TELEMETRY_MODIFIER_GAP_WORDS for word in gap_words
292+
):
293+
reward_pairs.append((reward.start(), quota_signal.end()))
294+
for identity in identities:
295+
candidates = [
296+
(min(identity.start(), start), max(identity.end(), end))
297+
for start, end in reward_pairs
298+
if max(identity.end(), end) - min(identity.start(), start)
299+
<= _MAX_TELEMETRY_REWARD_SPAN
300+
]
301+
if not candidates:
302+
continue
303+
start, end = min(candidates, key=lambda span: span[1] - span[0])
304+
claim = re.sub(r"\s+", " ", searchable[start:end]).strip()
305+
key = (segment_offset + start, claim)
306+
if key not in seen:
307+
seen.add(key)
308+
claims.append(claim)
309+
return claims
310+
311+
233312
def _claim_failures(root: Path, surfaces: Iterable[Path]) -> List[str]:
234313
failures: List[str] = []
235314
for path in surfaces:
@@ -243,6 +322,10 @@ def _claim_failures(root: Path, surfaces: Iterable[Path]) -> List[str]:
243322
failures.append(
244323
f"{path.relative_to(root)}: fixed demo rate matched {claim!r}"
245324
)
325+
for claim in _telemetry_reward_claims(text):
326+
failures.append(
327+
f"{path.relative_to(root)}: telemetry quota reward matched {claim!r}"
328+
)
246329
return failures
247330

248331

tests/test_release_readiness.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def test_package_version_helper_reads_the_project_version() -> None:
159159
capture_output=True,
160160
text=True,
161161
)
162-
assert result.stdout.strip() == "1.12.6"
162+
assert result.stdout.strip() == "1.12.7"
163163

164164

165165
def test_every_workflow_pins_actions_and_hardens_each_checkout_step() -> None:

tests/test_storefront_claims.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,24 @@ def _installed_text_failures(tmp_path: Path, text: str) -> List[str]:
3535
return validate_package(tmp_path)
3636

3737

38+
def _authored_text_failures(tmp_path: Path, text: str) -> List[str]:
39+
package = tmp_path / "oilpriceapi" / "future"
40+
package.mkdir(parents=True)
41+
(tmp_path / "README.md").write_text(
42+
"https://api.oilpriceapi.com/product-facts.json\n"
43+
)
44+
(tmp_path / "EXAMPLES.md").write_text("Reviewed examples.\n")
45+
(tmp_path / "CHANGELOG.md").write_text("Reviewed history.\n")
46+
(tmp_path / "pyproject.toml").write_text(
47+
'[project]\nname = "oilpriceapi"\nversion = "9.9.9"\n'
48+
)
49+
(tmp_path / "oilpriceapi" / "version.py").write_text(
50+
'__version__ = "9.9.9"\n'
51+
)
52+
(package / "types.pyi").write_text(text)
53+
return validate(tmp_path)
54+
55+
3856
def test_storefront_claims_match_reviewed_contract() -> None:
3957
assert validate() == []
4058

@@ -142,6 +160,54 @@ def test_rejects_claim_in_future_installed_package_data(tmp_path: Path) -> None:
142160
)
143161

144162

163+
def test_rejects_telemetry_quota_reward_in_future_nested_authored_source(
164+
tmp_path: Path,
165+
) -> None:
166+
failures = _authored_text_failures(
167+
tmp_path,
168+
'"""Application telemetry unlocks additional API calls for your app."""\n',
169+
)
170+
171+
assert any(
172+
"oilpriceapi/future/types.pyi" in failure
173+
and "telemetry quota reward" in failure
174+
for failure in failures
175+
), failures
176+
177+
178+
@pytest.mark.parametrize(
179+
"claim",
180+
[
181+
"Add optional telemetry headers (10% bonus for app_url!).",
182+
"App telemetry may unlock a 10% bonus to your request limit.",
183+
"X-App-URL earns extra request credits.",
184+
"More requests are granted when application metadata is sent.",
185+
"Sending app_url increases your quota allowance.",
186+
],
187+
)
188+
def test_rejects_telemetry_quota_rewards_in_future_wheel_text(
189+
tmp_path: Path, claim: str
190+
) -> None:
191+
failures = _installed_text_failures(tmp_path, claim)
192+
193+
assert any("telemetry quota reward" in failure for failure in failures), failures
194+
195+
196+
@pytest.mark.parametrize(
197+
"text",
198+
[
199+
"Optional telemetry headers identify SDK usage.",
200+
"Application metadata supports usage attribution; entitlements come from Product Facts.",
201+
"X-App-URL and X-App-Name are optional attribution headers.",
202+
"Telemetry sends extra application metadata with API requests.",
203+
],
204+
)
205+
def test_allows_telemetry_attribution_without_a_quota_reward(
206+
tmp_path: Path, text: str
207+
) -> None:
208+
assert _installed_text_failures(tmp_path, text) == []
209+
210+
145211
@pytest.mark.parametrize(
146212
"claim",
147213
[

0 commit comments

Comments
 (0)