Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions marimo/_runtime/packages/pypi_package_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,26 @@ def update_notebook_script_metadata(

version_map = self._get_version_map()

def _is_direct_reference(package: str) -> bool:
"""Check if a package is a direct reference (git, URL, or local path).

Direct references should bypass the _is_installed check because:
- Git URLs (git+https://...) won't appear in version_map with that prefix
- Direct URL references (package @ https://...) use @ syntax
- Local paths (package @ file://...) use @ syntax
- These should be passed directly to uv which handles them correctly
"""
# Git URLs: git+https://, git+ssh://, git://
if package.startswith("git+") or package.startswith("git://"):
return True
# Direct references with @ (PEP 440 direct references)
if " @ " in package:
return True
# URLs (https://, http://, file://)
if "://" in package:
return True
Comment on lines +261 to +262
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we do just this?

return False

def _is_installed(package: str) -> bool:
without_brackets = package.split("[")[0]
return without_brackets.lower() in version_map
Expand All @@ -256,11 +276,13 @@ def _maybe_add_version(package: str) -> str:
return f"{package}=={version}"
return package

# Filter to packages that are found in "uv pip list"
# Filter to packages that are found in "uv pip list" OR are direct references
# Direct references (git URLs, direct URLs, local paths) bypass the installed check
# because they won't appear in the version map with their full reference syntax
packages_to_add = [
_maybe_add_version(im)
_maybe_add_version(im) if not _is_direct_reference(im) else im
for im in packages_to_add
if _is_installed(im)
if _is_direct_reference(im) or _is_installed(im)
]

if filepath.endswith(".md") or filepath.endswith(".qmd"):
Expand Down
115 changes: 115 additions & 0 deletions tests/_runtime/packages/test_package_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,121 @@ async def _install(
assert captured_logs == ["Installing test-package==1.0.0...\n"]


def test_update_script_metadata_with_git_dependencies() -> None:
"""Test that git dependencies are passed through to uv add --script."""
runs_calls: list[list[str]] = []

class MockUvPackageManager(UvPackageManager):
def run(
self,
command: list[str],
log_callback: Optional[LogCallback] = None,
) -> bool:
del log_callback
runs_calls.append(command)
return True

def _get_version_map(self) -> dict[str, str]:
# Git dependencies won't be in the version map with their git+ prefix
return {"python-gcode": "0.1.0"}

pm = MockUvPackageManager()

# Test 1: Git dependency with https
pm.update_notebook_script_metadata(
"nb.py",
packages_to_add=["git+https://github.com/fetlab/python_gcode"],
upgrade=False,
)
# Should pass through git URL even though not in version map
assert runs_calls == [
[
"uv",
"--quiet",
"add",
"--script",
"nb.py",
"git+https://github.com/fetlab/python_gcode",
],
]
runs_calls.clear()

# Test 2: Git dependency with ssh
pm.update_notebook_script_metadata(
"nb.py",
packages_to_add=["git+ssh://[email protected]/user/repo.git"],
upgrade=False,
)
assert runs_calls == [
[
"uv",
"--quiet",
"add",
"--script",
"nb.py",
"git+ssh://[email protected]/user/repo.git",
],
]
runs_calls.clear()

# Test 3: Direct URL reference with @
pm.update_notebook_script_metadata(
"nb.py",
packages_to_add=["package @ https://example.com/package.whl"],
upgrade=False,
)
assert runs_calls == [
[
"uv",
"--quiet",
"add",
"--script",
"nb.py",
"package @ https://example.com/package.whl",
],
]
runs_calls.clear()

# Test 4: Local path reference with @
pm.update_notebook_script_metadata(
"nb.py",
packages_to_add=["mypackage @ file:///path/to/package"],
upgrade=False,
)
assert runs_calls == [
[
"uv",
"--quiet",
"add",
"--script",
"nb.py",
"mypackage @ file:///path/to/package",
],
]
runs_calls.clear()

# Test 5: Mix of regular and git dependencies
pm.update_notebook_script_metadata(
"nb.py",
packages_to_add=[
"python-gcode", # Regular package, should get version
"git+https://github.com/user/repo.git", # Git dep, no version
],
upgrade=False,
)
assert runs_calls == [
[
"uv",
"--quiet",
"add",
"--script",
"nb.py",
"python-gcode==0.1.0",
"git+https://github.com/user/repo.git",
],
]


def test_package_manager_run_manager_not_installed() -> None:
"""Test PackageManager.run when manager is not installed."""
pm = PipPackageManager()
Expand Down
30 changes: 30 additions & 0 deletions tests/_server/api/endpoints/test_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,33 @@ def test_add_package_no_metadata_update_when_no_filename(
)
assert response.status_code == 200
mock_package_manager_with_metadata.update_notebook_script_metadata.assert_not_called()


def test_add_package_with_git_dependency(
client: TestClient, mock_package_manager_with_metadata: Mock
) -> None:
"""Test add_package with git dependency calls metadata update correctly."""
with patch(
"marimo._config.settings.GLOBAL_SETTINGS.MANAGE_SCRIPT_METADATA", True
):
with patch(
"marimo._server.api.endpoints.packages._get_filename",
return_value="test.py",
):
response = client.post(
"/api/packages/add",
headers=HEADERS,
json={"package": "git+https://github.com/user/repo.git"},
)
assert response.status_code == 200
result = response.json()
assert result == {"success": True, "error": None}

# Verify metadata update was called with the git dependency
mock_package_manager_with_metadata.update_notebook_script_metadata.assert_called_once()
call_args = mock_package_manager_with_metadata.update_notebook_script_metadata.call_args
assert call_args.kwargs["filepath"] == "test.py"
assert (
"git+https://github.com/user/repo.git"
in call_args.kwargs["packages_to_add"]
)
Loading