feat: Python telemetry client SDK v0.1.0
Standalone Python package (mosaicstack-telemetry) for reporting task-completion telemetry and querying predictions from the Mosaic Stack Telemetry server. - Sync/async TelemetryClient with context manager support - Thread-safe EventQueue with bounded deque - BatchSubmitter with httpx, exponential backoff, Retry-After - PredictionCache with TTL - EventBuilder convenience class - All types standalone (no server dependency) - 55 tests, 90% coverage, mypy strict clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,32 +38,28 @@ from mosaicstack_telemetry.types.predictions import (
|
||||
__version__ = "0.1.0"
|
||||
|
||||
__all__ = [
|
||||
# Client
|
||||
"TelemetryClient",
|
||||
"TelemetryConfig",
|
||||
"EventBuilder",
|
||||
"EventQueue",
|
||||
"PredictionCache",
|
||||
# Types - Events
|
||||
"TaskCompletionEvent",
|
||||
"TaskType",
|
||||
"Complexity",
|
||||
"Harness",
|
||||
"Provider",
|
||||
"QualityGate",
|
||||
"Outcome",
|
||||
"RepoSizeCategory",
|
||||
# Types - Predictions
|
||||
"PredictionQuery",
|
||||
"PredictionResponse",
|
||||
"PredictionData",
|
||||
"PredictionMetadata",
|
||||
"TokenDistribution",
|
||||
"CorrectionFactors",
|
||||
"QualityPrediction",
|
||||
# Types - Common
|
||||
"BatchEventRequest",
|
||||
"BatchEventResponse",
|
||||
"BatchEventResult",
|
||||
"Complexity",
|
||||
"CorrectionFactors",
|
||||
"EventBuilder",
|
||||
"EventQueue",
|
||||
"Harness",
|
||||
"Outcome",
|
||||
"PredictionCache",
|
||||
"PredictionData",
|
||||
"PredictionMetadata",
|
||||
"PredictionQuery",
|
||||
"PredictionResponse",
|
||||
"Provider",
|
||||
"QualityGate",
|
||||
"QualityPrediction",
|
||||
"RepoSizeCategory",
|
||||
"TaskCompletionEvent",
|
||||
"TaskType",
|
||||
"TelemetryClient",
|
||||
"TelemetryConfig",
|
||||
"TelemetryError",
|
||||
"TokenDistribution",
|
||||
]
|
||||
|
||||
@@ -123,7 +123,7 @@ class TelemetryClient:
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
results = data.get("results", [])
|
||||
for query, result_data in zip(queries, results):
|
||||
for query, result_data in zip(queries, results, strict=False):
|
||||
pred = PredictionResponse.model_validate(result_data)
|
||||
self._prediction_cache.put(query, pred)
|
||||
logger.debug("Refreshed %d predictions", len(results))
|
||||
@@ -153,7 +153,7 @@ class TelemetryClient:
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
results = data.get("results", [])
|
||||
for query, result_data in zip(queries, results):
|
||||
for query, result_data in zip(queries, results, strict=False):
|
||||
pred = PredictionResponse.model_validate(result_data)
|
||||
self._prediction_cache.put(query, pred)
|
||||
logger.debug("Refreshed %d predictions", len(results))
|
||||
|
||||
@@ -20,8 +20,8 @@ logger = logging.getLogger("mosaicstack_telemetry")
|
||||
|
||||
def _backoff_delay(attempt: int, base: float = 1.0, maximum: float = 60.0) -> float:
|
||||
"""Calculate exponential backoff with jitter."""
|
||||
delay = min(base * (2**attempt), maximum)
|
||||
jitter = random.uniform(0, delay * 0.5) # noqa: S311
|
||||
delay: float = min(base * (2**attempt), maximum)
|
||||
jitter: float = random.uniform(0, delay * 0.5) # noqa: S311
|
||||
return delay + jitter
|
||||
|
||||
|
||||
@@ -78,9 +78,7 @@ def submit_batch_sync(
|
||||
continue
|
||||
|
||||
if response.status_code == 403:
|
||||
logger.error(
|
||||
"Authentication failed (403): API key may not match instance_id"
|
||||
)
|
||||
logger.error("Authentication failed (403): API key may not match instance_id")
|
||||
return None
|
||||
|
||||
logger.warning(
|
||||
@@ -110,7 +108,11 @@ def submit_batch_sync(
|
||||
logger.debug("Backing off for %.1f seconds before retry", delay)
|
||||
time.sleep(delay)
|
||||
|
||||
logger.error("All %d attempts failed for batch of %d events", config.max_retries + 1, len(events))
|
||||
logger.error(
|
||||
"All %d attempts failed for batch of %d events",
|
||||
config.max_retries + 1,
|
||||
len(events),
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@@ -169,9 +171,7 @@ async def submit_batch_async(
|
||||
continue
|
||||
|
||||
if response.status_code == 403:
|
||||
logger.error(
|
||||
"Authentication failed (403): API key may not match instance_id"
|
||||
)
|
||||
logger.error("Authentication failed (403): API key may not match instance_id")
|
||||
return None
|
||||
|
||||
logger.warning(
|
||||
@@ -201,5 +201,9 @@ async def submit_batch_async(
|
||||
logger.debug("Backing off for %.1f seconds before retry", delay)
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
logger.error("All %d attempts failed for batch of %d events", config.max_retries + 1, len(events))
|
||||
logger.error(
|
||||
"All %d attempts failed for batch of %d events",
|
||||
config.max_retries + 1,
|
||||
len(events),
|
||||
)
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user