From fe3cbbb9b447ed58b68cb1a4a70c76da56f191e5 Mon Sep 17 00:00:00 2001 From: "ark-hand[bot]" <315378070+ark-hand[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:10:34 +0800 Subject: [PATCH] fix: report Python SDK identity and package version in User-Agent Sync-Source-Commit: 5061fafcb56f54f7677583f5fea8f3e681d1243e Hand-Written-Reason: No Ark-APIs provenance marker; treated as a hand-written source commit. Release-Version: 0.5.0 --- src/arkruntime/_base_client.py | 2 +- src/arkruntime/_constants.py | 3 +- src/arkruntime/_version.py | 27 +++++++++++++ tests/test_user_agent.py | 71 ++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/arkruntime/_version.py create mode 100644 tests/test_user_agent.py diff --git a/src/arkruntime/_base_client.py b/src/arkruntime/_base_client.py index c0d14c7..08c564d 100644 --- a/src/arkruntime/_base_client.py +++ b/src/arkruntime/_base_client.py @@ -184,7 +184,7 @@ def auth_headers(self) -> dict[str, str]: @property def user_agent(self) -> str: - return "volc-sdk-python/" + VERSION + return "ark-runtime-python/" + VERSION def default_headers(self) -> Dict[str, str]: return { diff --git a/src/arkruntime/_constants.py b/src/arkruntime/_constants.py index 583469c..f6e40f2 100644 --- a/src/arkruntime/_constants.py +++ b/src/arkruntime/_constants.py @@ -4,7 +4,8 @@ import httpx -VERSION = "1.0.0" +from ._version import VERSION as VERSION + BASE_URL = "https://ark.cn-beijing.volces.com/api/v3" diff --git a/src/arkruntime/_version.py b/src/arkruntime/_version.py new file mode 100644 index 0000000..5cd72df --- /dev/null +++ b/src/arkruntime/_version.py @@ -0,0 +1,27 @@ +# Copyright (c) 2026 ByteDance Ltd. and/or its affiliates. +# SPDX-License-Identifier: Apache-2.0 + +import re +from importlib.metadata import version +from pathlib import Path + + +def _get_version() -> str: + # Source and editable imports must use this checkout, even if another + # arkruntime distribution is installed. Releases use wheel metadata, + # which setuptools derives from the same pyproject.toml project.version. + source_root = Path(__file__).resolve().parent.parent + pyproject = source_root.parent / "pyproject.toml" + if source_root.name == "src" and pyproject.is_file(): + # The project declares a static, single-line version. Read only the + # [project] table without requiring a TOML dependency on Python 3.8. + project = re.search(r"(?ms)^\[project\][ \t]*\n(.*?)(?=^\[|\Z)", pyproject.read_text(encoding="utf-8")) + if project is not None: + declared = re.search(r"""(?m)^version\s*=\s*["']([^"'\r\n]+)["'][ \t]*(?:#.*)?$""", project[1]) + if declared is not None: + return declared[1] + raise RuntimeError("Expected a static project.version in " + str(pyproject)) + return version("arkruntime") + + +VERSION = _get_version() diff --git a/tests/test_user_agent.py b/tests/test_user_agent.py new file mode 100644 index 0000000..82cf237 --- /dev/null +++ b/tests/test_user_agent.py @@ -0,0 +1,71 @@ +# Copyright (c) 2026 ByteDance Ltd. and/or its affiliates. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import asyncio +from importlib.metadata import version +from pathlib import Path + +import httpx +import pytest + +from arkruntime import Ark, AsyncArk, _version + + +@pytest.mark.parametrize("custom", [None, "my-app/2.0"]) +@pytest.mark.parametrize("asynchronous", [False, True]) +def test_user_agent_on_requests(custom: str | None, asynchronous: bool) -> None: + requests = [] + + def handle(request: httpx.Request) -> httpx.Response: + requests.append(request) + return httpx.Response(200, json={"id": "test-file", "object": "file"}) + + headers = {"User-Agent": custom} if custom else {} + transport = httpx.MockTransport(handle) + if asynchronous: + + async def run() -> None: + async with AsyncArk(api_key="placeholder", http_client=httpx.AsyncClient(transport=transport)) as client: + await client.files.retrieve("test-file", extra_headers=headers) + + asyncio.run(run()) + else: + with Ark(api_key="placeholder", http_client=httpx.Client(transport=transport)) as client: + client.files.retrieve("test-file", extra_headers=headers) + + assert len(requests) == 1 + assert requests[0].headers.get_list("User-Agent") == [custom or "ark-runtime-python/" + version("arkruntime")] + + +def test_checkout_version_takes_precedence_over_installed_metadata( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + source = tmp_path / "src" / "arkruntime" / "_version.py" + monkeypatch.setattr(_version, "__file__", str(source)) + monkeypatch.setattr(_version, "version", lambda name: "0.0.1") + (tmp_path / "pyproject.toml").write_text( + '[tool.example]\nversion = "9.9.9"\n[project]\nname = "arkruntime"\nversion = "2.3.4rc1"\n' + '[tool.other]\nversion = "8.8.8"\n', + encoding="utf-8", + ) + assert _version._get_version() == "2.3.4rc1" + + +def test_installed_version_uses_distribution_metadata(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(_version, "__file__", str(tmp_path / "site-packages" / "arkruntime" / "_version.py")) + + def metadata_version(name: str) -> str: + assert name == "arkruntime" + return "3.4.5" + + monkeypatch.setattr(_version, "version", metadata_version) + assert _version._get_version() == "3.4.5" + + +def test_missing_source_version_does_not_use_stale_metadata(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(_version, "__file__", str(tmp_path / "src" / "arkruntime" / "_version.py")) + (tmp_path / "pyproject.toml").write_text('[project]\nname = "arkruntime"\n', encoding="utf-8") + with pytest.raises(RuntimeError, match="Expected a static project.version"): + _version._get_version()