From 1024f506e561e55d985a3f8c5a3e8256323a87d4 Mon Sep 17 00:00:00 2001 From: Platform Bot Date: Wed, 29 Jul 2026 09:13:56 +0000 Subject: [PATCH] feat(client): default User-Agent header for SDK attribution (DAK-7617) Set a default `User-Agent: dakera-py/` header on both the sync (requests) and async (httpx) clients so the Dakera engine's server-side install telemetry (DAK-7617) can attribute Python SDK usage. The version is read from installed package metadata via `importlib.metadata.version("dakera")` to avoid a circular import (`dakera.__version__` is defined after these modules are imported in `__init__`), falling back to `dakera-py/unknown`. A caller-supplied `headers` override still wins, matching existing header-merge behaviour. Co-Authored-By: Claude Opus 4.8 --- src/dakera/async_client.py | 15 ++++++++++++++- src/dakera/client.py | 14 +++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/dakera/async_client.py b/src/dakera/async_client.py index 5388a79..bb7e747 100644 --- a/src/dakera/async_client.py +++ b/src/dakera/async_client.py @@ -28,6 +28,8 @@ import json import random from collections.abc import AsyncGenerator +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as _pkg_version from typing import Any try: @@ -150,6 +152,14 @@ WarmingTargetTier, ) +# DAK-7617: default User-Agent so the engine can attribute Python SDK usage. +# Read from installed package metadata to avoid a circular import of ``dakera`` +# (``__version__`` is defined after this module is imported in ``__init__``). +try: + _USER_AGENT = f"dakera-py/{_pkg_version('dakera')}" +except PackageNotFoundError: # pragma: no cover + _USER_AGENT = "dakera-py/unknown" + class AsyncDakeraClient: """ @@ -204,7 +214,10 @@ def __init__( else: self._retry_config = RetryConfig(max_retries=max_retries) - default_headers: dict[str, str] = {"Content-Type": "application/json"} + default_headers: dict[str, str] = { + "Content-Type": "application/json", + "User-Agent": _USER_AGENT, + } if api_key: default_headers["Authorization"] = f"Bearer {api_key}" if headers: diff --git a/src/dakera/client.py b/src/dakera/client.py index b37290a..8945903 100644 --- a/src/dakera/client.py +++ b/src/dakera/client.py @@ -8,6 +8,8 @@ import random import time from collections.abc import Generator +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as _pkg_version from typing import Any, Union from urllib.parse import urljoin @@ -126,6 +128,14 @@ WarmingTargetTier, ) +# DAK-7617: default User-Agent so the engine can attribute Python SDK usage. +# Read from installed package metadata to avoid a circular import of ``dakera`` +# (``__version__`` is defined after this module is imported in ``__init__``). +try: + _USER_AGENT = f"dakera-py/{_pkg_version('dakera')}" +except PackageNotFoundError: # pragma: no cover + _USER_AGENT = "dakera-py/unknown" + class DakeraClient: """ @@ -185,7 +195,9 @@ def __init__( self._retry_config = RetryConfig(max_retries=max_retries) self._session = requests.Session() - self._session.headers.update({"Content-Type": "application/json"}) + self._session.headers.update( + {"Content-Type": "application/json", "User-Agent": _USER_AGENT} + ) if api_key: self._session.headers.update({"Authorization": f"Bearer {api_key}"})