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:
2026-02-07 23:25:27 -06:00
parent 0b29302f43
commit f02207e33c
7 changed files with 752 additions and 56 deletions

View File

@@ -10,9 +10,7 @@ from mosaicstack_telemetry.client import TelemetryClient
from mosaicstack_telemetry.config import TelemetryConfig
from mosaicstack_telemetry.types.events import (
Complexity,
Outcome,
Provider,
TaskCompletionEvent,
TaskType,
)
from mosaicstack_telemetry.types.predictions import (
@@ -24,7 +22,6 @@ from tests.conftest import (
TEST_API_KEY,
TEST_INSTANCE_ID,
TEST_SERVER_URL,
make_batch_response_json,
make_event,
)
@@ -146,9 +143,7 @@ class TestTelemetryClientPredictions:
)
assert client.get_prediction(query) is None
def test_get_prediction_after_cache_populated(
self, config: TelemetryConfig
) -> None:
def test_get_prediction_after_cache_populated(self, config: TelemetryConfig) -> None:
"""get_prediction returns cached value."""
client = TelemetryClient(config)
query = PredictionQuery(
@@ -241,9 +236,7 @@ class TestTelemetryClientPredictions:
assert result.metadata.sample_size == 60
@respx.mock
async def test_refresh_predictions_server_error(
self, config: TelemetryConfig
) -> None:
async def test_refresh_predictions_server_error(self, config: TelemetryConfig) -> None:
"""refresh_predictions handles server errors gracefully."""
query = PredictionQuery(
task_type=TaskType.IMPLEMENTATION,
@@ -262,9 +255,7 @@ class TestTelemetryClientPredictions:
# Cache should still be empty
assert client.get_prediction(query) is None
async def test_refresh_predictions_empty_list(
self, config: TelemetryConfig
) -> None:
async def test_refresh_predictions_empty_list(self, config: TelemetryConfig) -> None:
"""refresh_predictions with empty list is a no-op."""
client = TelemetryClient(config)
await client.refresh_predictions([])