Skip to content

fix: Use list-deps --format uv to simplify dependency generation#215

Draft
gyliu513 wants to merge 1 commit intoopendatahub-io:mainfrom
gyliu513:uv
Draft

fix: Use list-deps --format uv to simplify dependency generation#215
gyliu513 wants to merge 1 commit intoopendatahub-io:mainfrom
gyliu513:uv

Conversation

@gyliu513
Copy link

@gyliu513 gyliu513 commented Jan 20, 2026

What does this PR do?

  • Changes
    • Switch distribution/build.py:get_dependencies() to consume llama stack list-deps --format uv output directly.
    • Simplify parsing to preserve upstream quoting behavior while still applying local pymilvus and .extra rewrites.
    • Add a tests/test_build.py unit test to validate dependency grouping and quote-preservation.
  • Benefits
    • Aligns dependency generation with upstream quoting rules, avoiding shell-escaping issues.
    • Reduces custom parsing logic, making the build script easier to maintain.
    • Adds test coverage to guard against regressions in dependency formatting.

@leseb can you help review if this align with your thinking? Thanks!

Test Plan

Summary by CodeRabbit

  • Chores
    • Consolidated and standardized how dependencies are discovered and installed, ensuring consistent handling of extras, quoted packages and special flags (--extra-index-url, --no-deps, --no-cache) and enforcing prerelease allowance for installs.
  • Tests
    • Added tests validating dependency transformations, grouping, quoting rules and preservation of install flags and prerelease behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Refactors dependency extraction in distribution/build.py to parse llama stack list-deps --format uv output, apply regex-based transforms for extras/pinning, and emit unified RUN uv pip install --prerelease=allow ... lines; adds tests/test_build.py; consolidates many pip RUN steps in distribution/Containerfile.

Changes

Cohort / File(s) Summary
Dependency processing refactor
distribution/build.py
Switches to --format uv output parsing; replaces shlex/per-package assembly with line-based handling; inlines pymilvuspymilvus[milvus-lite] and namespace extras → [extra]==version; builds RUN uv pip install --prerelease=allow {deps_line} and groups installs by flags (--extra-index-url/--index-url, --no-deps, --no-cache, standard).
Containerfile consolidation
distribution/Containerfile
Replaces many individual pip install RUNs with grouped consolidated RUN lines; adds dedicated PyTorch CPU-indexed install and preserves no-deps/no-cache and git-based installs as separate RUNs.
Test suite for build module
tests/test_build.py
New unit test GetDependenciesTests.test_get_dependencies_transforms_and_groups mocking subprocess output to assert single command invocation, presence of --prerelease=allow in each install line, correct extras/pinning transforms (including quoted forms), flag preservation, and grouping behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I nibble on deps with whiskered delight,
Regex trims tangles in soft evening light.
One-line installs now march in a row,
Tests hum a tune, dependencies glow. 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: simplifying dependency generation by using the --format uv flag with the list-deps command.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@distribution/build.py`:
- Around line 145-150: The replacement currently munges existing extras (e.g.,
turning pymilvus[grpc] into pymilvus[milvus-lite][grpc]); update the logic
around deps_line to detect pymilvus with optional extras and then either add
"milvus-lite" into the existing bracketed extras (unless already present) or add
a new [milvus-lite] when no extras exist; reference the same variable deps_line
and the current regex usage, replacing it with a function-based re.sub callback
that inspects the match, parses existing extras, de-duplicates "milvus-lite",
and returns the correct specifier (pymilvus[...milvus-lite...] or
pymilvus[milvus-lite]).

In `@tests/test_build.py`:
- Around line 1-7: Remove the shebang line ("#!/usr/bin/env python3") at the top
of tests/test_build.py so the file no longer triggers the executable-shebang
pre-commit check, then re-run ruff-format (and pre-commit) to apply formatting
and ensure the file passes checks before committing; alternatively, if you
prefer keeping the shebang, mark the file executable and re-run
ruff-format/pre-commit instead.
🧹 Nitpick comments (1)
tests/test_build.py (1)

36-39: Assert the exact list-deps command.

Right now the test only checks call count. Tighten it to assert the exact command and flags to catch regressions in the CLI invocation.

✅ Suggested assertion
-        run.assert_called_once()
+        run.assert_called_once_with(
+            "llama stack list-deps --format uv distribution/config.yaml",
+            shell=True,
+            capture_output=True,
+            text=True,
+            check=True,
+        )

Comment on lines +1 to +7
#!/usr/bin/env python3
import sys
from pathlib import Path
import subprocess
import unittest
from unittest import mock

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix pre-commit failures (shebang + formatting).

Pre-commit failed because the file has a shebang but isn’t executable, and ruff-format reformatted it. Remove the shebang (or mark executable) and re-run ruff-format before re-pushing.

🧹 Minimal fix (remove shebang)
-#!/usr/bin/env python3
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#!/usr/bin/env python3
import sys
from pathlib import Path
import subprocess
import unittest
from unittest import mock
import sys
from pathlib import Path
import subprocess
import unittest
from unittest import mock
🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 1-1: Pre-commit check-shebang-scripts-are-executable failed: has a shebang but is not marked executable. If it is supposed to be executable, run 'chmod +x tests/test_build.py'.


[error] 1-1: ruff-format reformatted 1 file. 1 file reformatted, 2 files left unchanged.

🤖 Prompt for AI Agents
In `@tests/test_build.py` around lines 1 - 7, Remove the shebang line
("#!/usr/bin/env python3") at the top of tests/test_build.py so the file no
longer triggers the executable-shebang pre-commit check, then re-run ruff-format
(and pre-commit) to apply formatting and ensure the file passes checks before
committing; alternatively, if you prefer keeping the shebang, mark the file
executable and re-run ruff-format/pre-commit instead.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@distribution/build.py`:
- Around line 137-144: The loop handling result.stdout in distribution/build.py
assumes every non-empty raw_line starts with "uv pip install " before slicing
into deps_line; guard this by checking raw_line.startswith("uv pip install ")
before doing deps_line = raw_line[len("uv pip install "):], and skip or
log/raise for unexpected lines (e.g., warnings) so malformed install commands
are not produced; adjust the loop around the for line in
result.stdout.splitlines() / raw_line variables accordingly.
♻️ Duplicate comments (2)
distribution/build.py (1)

145-150: Handle existing pymilvus extras to avoid invalid specifiers.
Current regex injects [milvus-lite] even when extras already exist, producing invalid pymilvus[milvus-lite][grpc] (Line 145‑150). Update the rewrite to merge extras instead.

🛠️ Suggested fix
-            deps_line = re.sub(
-                r"\bpymilvus(?!\[milvus-lite\])\b",
-                "pymilvus[milvus-lite]",
-                deps_line,
-            )
+            # If extras already exist, append milvus-lite; otherwise add it.
+            deps_line = re.sub(
+                r"\bpymilvus\[(?![^\]]*milvus-lite)([^\]]+)\]",
+                r"pymilvus[\1,milvus-lite]",
+                deps_line,
+            )
+            deps_line = re.sub(
+                r"\bpymilvus\b(?!\[)",
+                "pymilvus[milvus-lite]",
+                deps_line,
+            )
tests/test_build.py (1)

1-1: Remove the shebang (or mark the file executable).
The pre‑commit hook fails because the file has a shebang but isn’t executable (Line 1). Remove it or chmod +x.

🧹 Minimal fix
-#!/usr/bin/env python3

Comment on lines 137 to 144
for line in result.stdout.splitlines():
line = line.strip()
if not line: # Skip empty lines
raw_line = line.strip()
if not raw_line:
continue

# New format: just packages, possibly with flags
# Fixes: https://issues.redhat.com/browse/RHAIENG-2710
# TODO: remove this once we have a stable version of kubernetes.
# Use --prerelease=allow to permit pre-release dependencies (e.g. kubernetes==35.0.0a1)
cmd_parts = ["RUN", "uv", "pip", "install", "--prerelease=allow"]
packages_str = line

# Parse packages and flags from the line
# Use shlex.split to properly handle quoted package names
parts_list = shlex.split(packages_str)
packages = []
flags = []
extra_index_url = None

i = 0
while i < len(parts_list):
if parts_list[i] == "--extra-index-url" and i + 1 < len(parts_list):
extra_index_url = parts_list[i + 1]
flags.extend([parts_list[i], parts_list[i + 1]])
i += 2
elif parts_list[i] == "--index-url" and i + 1 < len(parts_list):
flags.extend([parts_list[i], parts_list[i + 1]])
i += 2
elif parts_list[i] in ["--no-deps", "--no-cache"]:
flags.append(parts_list[i])
i += 1
else:
packages.append(parts_list[i])
i += 1

# Sort and deduplicate packages
packages = sorted(set(packages))

# Add quotes to packages with > or < to prevent bash redirection
packages = [
f"'{package}'" if (">" in package or "<" in package) else package
for package in packages
]
# Format: uv pip install <packages/flags>
deps_line = raw_line[len("uv pip install ") :]

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against non‑install stdout lines before slicing.
If llama stack list-deps emits warnings or other stdout lines, deps_line = raw_line[len("uv pip install "):] will generate malformed install commands. Add a prefix check to skip or raise on unexpected lines (Line 137‑144).

🛠️ Proposed fix
-            # Format: uv pip install <packages/flags>
-            deps_line = raw_line[len("uv pip install ") :]
+            # Format: uv pip install <packages/flags>
+            if not raw_line.startswith("uv pip install "):
+                continue
+            deps_line = raw_line[len("uv pip install ") :]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for line in result.stdout.splitlines():
line = line.strip()
if not line: # Skip empty lines
raw_line = line.strip()
if not raw_line:
continue
# New format: just packages, possibly with flags
# Fixes: https://issues.redhat.com/browse/RHAIENG-2710
# TODO: remove this once we have a stable version of kubernetes.
# Use --prerelease=allow to permit pre-release dependencies (e.g. kubernetes==35.0.0a1)
cmd_parts = ["RUN", "uv", "pip", "install", "--prerelease=allow"]
packages_str = line
# Parse packages and flags from the line
# Use shlex.split to properly handle quoted package names
parts_list = shlex.split(packages_str)
packages = []
flags = []
extra_index_url = None
i = 0
while i < len(parts_list):
if parts_list[i] == "--extra-index-url" and i + 1 < len(parts_list):
extra_index_url = parts_list[i + 1]
flags.extend([parts_list[i], parts_list[i + 1]])
i += 2
elif parts_list[i] == "--index-url" and i + 1 < len(parts_list):
flags.extend([parts_list[i], parts_list[i + 1]])
i += 2
elif parts_list[i] in ["--no-deps", "--no-cache"]:
flags.append(parts_list[i])
i += 1
else:
packages.append(parts_list[i])
i += 1
# Sort and deduplicate packages
packages = sorted(set(packages))
# Add quotes to packages with > or < to prevent bash redirection
packages = [
f"'{package}'" if (">" in package or "<" in package) else package
for package in packages
]
# Format: uv pip install <packages/flags>
deps_line = raw_line[len("uv pip install ") :]
for line in result.stdout.splitlines():
raw_line = line.strip()
if not raw_line:
continue
# Format: uv pip install <packages/flags>
if not raw_line.startswith("uv pip install "):
continue
deps_line = raw_line[len("uv pip install ") :]
🤖 Prompt for AI Agents
In `@distribution/build.py` around lines 137 - 144, The loop handling
result.stdout in distribution/build.py assumes every non-empty raw_line starts
with "uv pip install " before slicing into deps_line; guard this by checking
raw_line.startswith("uv pip install ") before doing deps_line = raw_line[len("uv
pip install "):], and skip or log/raise for unexpected lines (e.g., warnings) so
malformed install commands are not produced; adjust the loop around the for line
in result.stdout.splitlines() / raw_line variables accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@distribution/build.py`:
- Around line 159-163: The regex rewriting in deps_line is too broad and
rewrites any dotted package name; restrict it to only match package names
starting with the intended prefix by updating the re.sub call so it looks for a
leading "llama_stack_provider_" package token, capture the provider base and the
extra segment separately, and replace with base[extra] plus the original
comparator; in other words, modify the pattern used in the re.sub on deps_line
to match something like "llama_stack_provider_<name>.<extra><comparator>" and
change the replacement to use the captured groups (base, extra, comparator) so
only llama_stack_provider_* packages get converted to the .extra -> [extra]
form.
♻️ Duplicate comments (3)
distribution/build.py (2)

137-144: Guard against unexpected stdout lines before slicing.

If llama stack list-deps emits warnings, Line 143 will slice non‑install lines into malformed commands. Consider skipping or raising when the expected prefix isn’t present.

🔧 Proposed fix
-            # Format: uv pip install <packages/flags>
-            deps_line = raw_line[len("uv pip install ") :]
+            # Format: uv pip install <packages/flags>
+            if not raw_line.startswith("uv pip install "):
+                continue
+            deps_line = raw_line[len("uv pip install ") :]

145-150: Handle existing pymilvus extras to avoid invalid specifiers.

Line 147 will turn pymilvus[grpc] into pymilvus[milvus-lite][grpc], which is invalid. Please merge extras when already present.

💡 Proposed fix
-            deps_line = re.sub(
-                r"\bpymilvus(?!\[milvus-lite\])\b",
-                "pymilvus[milvus-lite]",
-                deps_line,
-            )
+            # If extras already exist, append milvus-lite; otherwise add it.
+            deps_line = re.sub(
+                r"\bpymilvus\[(?![^\]]*milvus-lite)([^\]]+)\]",
+                r"pymilvus[\1,milvus-lite]",
+                deps_line,
+            )
+            deps_line = re.sub(
+                r"\bpymilvus\b(?!\[)",
+                "pymilvus[milvus-lite]",
+                deps_line,
+            )
tests/test_build.py (1)

1-7: Remove shebang or mark executable to satisfy pre-commit.

The pre-commit check fails because the file has a shebang but isn’t executable. Remove the shebang or chmod +x the file.

🧹 Minimal fix (remove shebang)
-#!/usr/bin/env python3
🧹 Nitpick comments (1)
distribution/Containerfile (1)

20-22: Consider de‑duplicating repeated packages in the consolidated install line.

Line 20 repeats entries like pymilvus[milvus-lite]>=2.4.10 and aiosqlite, which adds noise and extra work for the resolver. If these are unintended, consider de‑duping at the generator/list‑deps stage so the Containerfile stays minimal.

Based on learnings, this file is auto-generated—please adjust the generator rather than editing this file directly. Based on learnings, ...

Comment on lines +159 to +163
deps_line = re.sub(
r"\.([a-zA-Z_][a-zA-Z0-9_]*)(==|>=|<=|>|<|~=|!=)",
r"[\1]\2",
deps_line,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, examine the code in distribution/build.py around lines 159-163
fd -t f "build.py" | xargs -I {} sh -c 'echo "File: {}"; wc -l {}'

Repository: opendatahub-io/llama-stack-distribution

Length of output: 185


🏁 Script executed:

# Find and read the specific file
cat -n distribution/build.py | sed -n '150,170p'

Repository: opendatahub-io/llama-stack-distribution

Length of output: 1326


🏁 Script executed:

# Get more context around the regex to understand its purpose
cat -n distribution/build.py | sed -n '140,180p'

Repository: opendatahub-io/llama-stack-distribution

Length of output: 2242


🏁 Script executed:

# Check if the config file exists
fd -t f "config.yaml" distribution/

Repository: opendatahub-io/llama-stack-distribution

Length of output: 107


🏁 Script executed:

# Try to understand what the script does and what distributions are being processed
rg -B5 -A5 "\.extra" distribution/build.py | head -50

Repository: opendatahub-io/llama-stack-distribution

Length of output: 787


🏁 Script executed:

# Read the config.yaml to see what packages are processed
cat distribution/config.yaml | head -100

Repository: opendatahub-io/llama-stack-distribution

Length of output: 3465


🏁 Script executed:

# Search for any references to dotted packages or legitimate third-party packages
rg "zope\.|\.interface|\.extra" distribution/config.yaml

Repository: opendatahub-io/llama-stack-distribution

Length of output: 65


🏁 Script executed:

# Look for all package references to understand the scope
rg -i "==|>=|<=|>|<|~=|!=" distribution/config.yaml | head -30

Repository: opendatahub-io/llama-stack-distribution

Length of output: 345


🏁 Script executed:

# Check test files to see if there are test cases that cover this regex behavior
cat -n tests/test_build.py

Repository: opendatahub-io/llama-stack-distribution

Length of output: 3328


Scope the .extra rewrite to avoid mutating dotted package names.

The current regex rewrites any dotted name before a version comparator (e.g., zope.interface==6.4zope[interface]==6.4), despite the intent stated in the comments being to handle only llama_stack_provider_* packages. While the current codebase only contains llama_stack_provider_ragas.inline and llama_stack_provider_ragas.remote, scoping the regex to the intended package prefix prevents accidental corruption if other packages are added.

🛠️ Proposed refinement
-            deps_line = re.sub(
-                r"\.([a-zA-Z_][a-zA-Z0-9_]*)(==|>=|<=|>|<|~=|!=)",
-                r"[\1]\2",
-                deps_line,
-            )
+            deps_line = re.sub(
+                r"\b(llama_stack_provider_[a-zA-Z0-9_]+)\.([a-zA-Z_][a-zA-Z0-9_]*)(==|>=|<=|>|<|~=|!=)",
+                r"\1[\2]\3",
+                deps_line,
+            )
🤖 Prompt for AI Agents
In `@distribution/build.py` around lines 159 - 163, The regex rewriting in
deps_line is too broad and rewrites any dotted package name; restrict it to only
match package names starting with the intended prefix by updating the re.sub
call so it looks for a leading "llama_stack_provider_" package token, capture
the provider base and the extra segment separately, and replace with base[extra]
plus the original comparator; in other words, modify the pattern used in the
re.sub on deps_line to match something like
"llama_stack_provider_<name>.<extra><comparator>" and change the replacement to
use the captured groups (base, extra, comparator) so only llama_stack_provider_*
packages get converted to the .extra -> [extra] form.

@ktdreyer
Copy link
Contributor

Thanks, I did not know about --format uv. I filed llamastack/llama-stack#4672 to standardize on this in the upstream docs.

@gyliu513 gyliu513 marked this pull request as draft January 20, 2026 22:11
def test_get_dependencies_transforms_and_groups(self) -> None:
fake_stdout = "\n".join(
[
"uv pip install pillow 'fastapi==1.0' aiosqlite 'pymilvus>=2.4.10'",
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not a fan of managing dependencies inside .py files.

@gyliu513 It might be cleaner to handle this through standard configuration files like pyproject.toml or requirements.txt.

@github-actions
Copy link
Contributor

This pull request has been automatically marked as stale because it has not had activity within 60 days. It will be automatically closed if no further activity occurs within 30 days.

@github-actions github-actions bot added the stale label Mar 24, 2026
@mergify
Copy link
Contributor

mergify bot commented Mar 24, 2026

This pull request has merge conflicts that must be resolved before it can be merged. @gyliu513 please rebase it. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify bot added the needs-rebase label Mar 24, 2026
@github-actions github-actions bot removed the stale label Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants