diff --git a/rsconnect/subprocesses/inspect_environment.py b/rsconnect/subprocesses/inspect_environment.py index 6703dab2..2d3bc320 100644 --- a/rsconnect/subprocesses/inspect_environment.py +++ b/rsconnect/subprocesses/inspect_environment.py @@ -262,6 +262,7 @@ def uv_export(dirname: str, lock_filename: str, uv_path: str = "uv"): lock_path = lock_filename if not os.path.isabs(lock_filename): lock_path = os.path.join(dirname, lock_filename) + lock_path = os.path.abspath(lock_path) if not os.path.exists(lock_path): raise EnvironmentException("uv.lock not found: %s" % lock_filename) diff --git a/tests/test_environment.py b/tests/test_environment.py index 7c44b0df..f4ad9ec9 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -321,7 +321,8 @@ def test_requirements_file_rejects_editable_outside_bundle(tmp_path: Path): @pytest.mark.parametrize("fallback", [False, True]) -def test_uv_lock_export(tmp_path, monkeypatch, fallback): +@pytest.mark.parametrize("lock_location", ["absolute_dir", "bare", "relative", "absolute_lock"]) +def test_uv_lock_export(tmp_path, monkeypatch, fallback, lock_location): project_dir = tmp_path / "project" project_dir.mkdir() (project_dir / "pyproject.toml").write_text( @@ -398,7 +399,14 @@ def test_uv_lock_export(tmp_path, monkeypatch, fallback): monkeypatch.setattr(rsconnect.environment.uv, "find_uv_bin", missing_bundled_uv) else: monkeypatch.setenv("PATH", "") - env = Environment.create_python_environment(str(project_dir), requirements_file="uv.lock", python=str(python)) + monkeypatch.chdir(project_dir) + dirname, lock_file = { + "absolute_dir": (str(project_dir), "uv.lock"), + "bare": ("", "uv.lock"), + "relative": (".", "./uv.lock"), + "absolute_lock": ("", str(project_dir / "uv.lock")), + }[lock_location] + env = Environment.create_python_environment(dirname, requirements_file=lock_file, python=str(python)) assert env.filename == "requirements.txt.lock" assert "aiofiles==24.1.0" in env.contents diff --git a/tests/test_main.py b/tests/test_main.py index b75ee962..414084c5 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -3,6 +3,7 @@ import os import re import shutil +import subprocess import sys import venv from os.path import join @@ -18,6 +19,7 @@ from rsconnect import VERSION from rsconnect.api import RSConnectClient, RSConnectServer from rsconnect.bundle import file_checksum +from rsconnect.exception import RSConnectException from rsconnect.json_web_token import SECRET_KEY_ENV from rsconnect.log import console_logger, logger as rs_logger from rsconnect.main import cli, env_management_callback, make_notebook_html_bundle @@ -2392,6 +2394,36 @@ def test_missing_requirements_reports_content_directory( assert f"The requirements file '{expected_file}' does not exist in '{expected_directory}'." in result.output +@pytest.mark.parametrize( + ("command", "filename"), + [ + (["write-manifest", "notebook"], "nb.ipynb"), + (["write-manifest", "quarto"], "doc.qmd"), + (["deploy", "notebook"], "nb.ipynb"), + (["deploy", "quarto"], "doc.qmd"), + ], +) +@pytest.mark.parametrize("prefix", ["", "./"]) +def test_document_commands_export_uv_lock_for_bare_and_relative_paths(tmp_path, monkeypatch, command, filename, prefix): + monkeypatch.chdir(tmp_path) + (tmp_path / "pyproject.toml").write_text("[project]\nname='demo'\nversion='0.0.0'\n") + (tmp_path / filename).write_text('{"cells":[],"metadata":{},"nbformat":4,"nbformat_minor":5}') + subprocess.run([sys.executable, "-m", "uv", "lock", "--offline"], check=True) + + with mock.patch("rsconnect.main.which_quarto", return_value="quarto"), mock.patch( + "rsconnect.main.quarto_inspect", return_value={"engines": ["jupyter"]} + ), mock.patch("rsconnect.main.RSConnectExecutor", side_effect=RSConnectException("deployment reached")) as executor: + result = CliRunner().invoke(cli, [*command, "-r", "uv.lock", "--exclude-renv", prefix + filename]) + + if command[0] == "deploy": + assert "deployment reached" in result.output + executor.assert_called_once() + else: + assert result.exit_code == 0, result.output + assert (tmp_path / "manifest.json").exists() + assert (tmp_path / "requirements.txt.lock").exists() + + class TestWriteManifestNodeJS: def test_help(self): runner = CliRunner()