diff --git a/src/usethis/_integrations/backend/uv/call.py b/src/usethis/_integrations/backend/uv/call.py index 4c3c7b30..3cd4534f 100644 --- a/src/usethis/_integrations/backend/uv/call.py +++ b/src/usethis/_integrations/backend/uv/call.py @@ -67,7 +67,7 @@ def call_uv_subprocess(args: list[str], change_toml: bool) -> str: if usethis_config.subprocess_verbose: new_args = [*new_args[:2], "--verbose", *new_args[2:]] - elif args[:2] != ["python", "list"]: + elif args[:2] != ["python", "list"] and args[:2] != ["self", "version"]: new_args = [*new_args[:2], "--quiet", *new_args[2:]] try: diff --git a/src/usethis/_integrations/backend/uv/version.py b/src/usethis/_integrations/backend/uv/version.py new file mode 100644 index 00000000..c6d6c929 --- /dev/null +++ b/src/usethis/_integrations/backend/uv/version.py @@ -0,0 +1,19 @@ +import json + +from usethis._integrations.backend.uv.call import call_uv_subprocess +from usethis._integrations.backend.uv.errors import UVSubprocessFailedError + +FALLBACK_UV_VERSION = "0.9.9" + + +def get_uv_version() -> str: + try: + json_str = call_uv_subprocess( + ["self", "version", "--output-format=json"], + change_toml=False, + ) + except UVSubprocessFailedError: + return FALLBACK_UV_VERSION + + json_dict: dict = json.loads(json_str) + return json_dict.get("version", FALLBACK_UV_VERSION) diff --git a/tests/docs/test_readme.py b/tests/docs/test_readme.py index 6648d02a..c0ae41cb 100644 --- a/tests/docs/test_readme.py +++ b/tests/docs/test_readme.py @@ -24,11 +24,11 @@ def test_assemble_readme_from_docs(usethis_dev_dir: Path): parts.append( _get_doc_file(usethis_dev_dir / "docs" / "start" / "example-usage.md") .replace( # README uses absolute links, docs use relative links - "](start/detailed-example.md)", + "](detailed-example.md)", "](https://usethis.readthedocs.io/en/stable/start/detailed-example)", ) .replace( - "](cli/reference.md)", + "](../cli/reference.md)", "](https://usethis.readthedocs.io/en/stable/cli/reference)", ) ) diff --git a/tests/usethis/_integrations/backend/uv/test_version.py b/tests/usethis/_integrations/backend/uv/test_version.py new file mode 100644 index 00000000..4d7e8519 --- /dev/null +++ b/tests/usethis/_integrations/backend/uv/test_version.py @@ -0,0 +1,59 @@ +import os +from pathlib import Path + +import pytest + +from usethis._config import usethis_config +from usethis._integrations.backend.uv.errors import UVSubprocessFailedError +from usethis._integrations.backend.uv.version import FALLBACK_UV_VERSION, get_uv_version +from usethis._integrations.ci.github.errors import GitHubTagError +from usethis._integrations.ci.github.tags import get_github_latest_tag +from usethis._test import change_cwd + + +class TestGetUVVersion: + @pytest.mark.usefixtures("_vary_network_conn") + def test_latest_version(self): + if os.getenv("CI"): + pytest.skip("Avoid flaky pipelines by testing version bumps manually") + + try: + assert ( + get_github_latest_tag(owner="astral-sh", repo="uv") + == FALLBACK_UV_VERSION + ) + except GitHubTagError as err: + if usethis_config.offline or "rate limit exceeded for url" in str(err): + pytest.skip( + "Failed to fetch GitHub tags (connection issues); skipping test" + ) + raise err + + def test_matches_pattern(self, tmp_path: Path): + # Act + with change_cwd(tmp_path): + version = get_uv_version() + + # Assert + assert isinstance(version, str) + assert version.count(".") == 2 + major, minor, patch = version.split(".") + assert major.isdigit() + assert minor.isdigit() + assert patch.isdigit() + + def test_mock_subprocess_failure(self, monkeypatch: pytest.MonkeyPatch): + # Arrange + def mock_call_uv_subprocess(*_, **__) -> str: + raise UVSubprocessFailedError + + monkeypatch.setattr( + "usethis._integrations.backend.uv.version.call_uv_subprocess", + mock_call_uv_subprocess, + ) + + # Act + version = get_uv_version() + + # Assert + assert version == FALLBACK_UV_VERSION