fix: preserve [[tool.uv.index]] semantics in marimo edit --sandbox - #10548
fix: preserve [[tool.uv.index]] semantics in marimo edit --sandbox#10548jacobcbeaudin wants to merge 2 commits into
Conversation
marimo edit --sandbox flattened every [[tool.uv.index]] entry to a bare --index <url> flag, which dropped three semantics uv defines: - default = true: uv consults a default index last, but the flattening promoted it to first priority, shadowing every other index. It now maps to --default-index (the <name>=<url> form is used when the default index is named, keeping its credential selector). Only the first default entry is honored, matching uv, which ignores the rest; a warning is logged for later ones. - name: the credential selector for UV_INDEX_<NAME>_USERNAME/PASSWORD. Named entries now map to --index <name>=<url> so authenticated indexes work again. - explicit = true: has no CLI equivalent, so it cannot be enforced; explicit entries are appended after regular indexes. They can still serve packages beyond their [tool.uv.sources] assignments, which the docs now note. Non-table entries in the index array (valid TOML that uv rejects with a schema error) are skipped instead of crashing flag construction. Separately, a stage-1 'uv export --script' failure was silently swallowed (except CalledProcessError: pass with capture_output=True), so e.g. a 401 from an authenticated index surfaced later as a confusing 'package not found' resolution error. The fallback now logs uv's stderr as a warning when the script contains a PEP 723 block, with URL query strings redacted (uv redacts userinfo credentials but not query-string tokens); plain files keep the silent fallback they rely on. Also types [[tool.uv.index]] entries as a TypedDict (the previous list[dict[str, str]] annotation was wrong for the boolean keys). The flags emitted here are all supported since uv 0.4.23, the same floor the previous bare --index form had. Fixes marimo-team#10547 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
All contributors have signed the CLA ✍️ ✅ |
…larify prose Review-driven polish; no behavior change except log-message wording: - Add PyProjectReader.has_script_metadata and use it in the export fallback so the guard reads as what it means. - Dedupe the [[tool.uv.index]] tests with _uv_cmd_for_indexes, and the export-failure tests with _captured_marimo_warnings and _failing_uv_export; assertions are unchanged. - Assert that the multiple-default warning fires; it was the one behavior with no failing test on regression. - Note that first-default-wins is observed uv behavior (uv 0.12), not documented, in the comment and the docs. - Document that IndexConfig entries are unvalidated user TOML, and comment the url-less skip branch. - Rewrite the PR's comments, docstrings, log messages, and docs section in short, active sentences (ASD-STE100-informed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@mscolnick I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
2 issues found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="marimo/_utils/inline_script_metadata.py">
<violation number="1" location="marimo/_utils/inline_script_metadata.py:77">
P2: When a PEP 723 block contains valid empty TOML, `has_script_metadata` returns false and stage-1 `uv export` failures stay silent. Preserve metadata presence separately from the parsed mapping instead of deriving it from truthiness.</violation>
<violation number="2" location="marimo/_utils/inline_script_metadata.py:89">
P3: `index_configs` claims every element is an `IndexConfig` even though it deliberately exposes non-table TOML entries. Return a type that includes unvalidated values, or validate and filter entries before returning so typed callers cannot skip the required narrowing.
(Based on your team's feedback about strict typing over `Any` and unchecked assertions.)</violation>
</file>
Architecture diagram
sequenceDiagram
participant CLI as CLI Entry
participant Sandbox as Sandbox Builder
participant Reader as PyProjectReader
participant UV as uv Process
participant Logger as Logger
Note over CLI,Sandbox: marimo edit --sandbox flow
CLI->>Sandbox: construct_uv_command()
Sandbox->>Reader: read notebook metadata
alt PEP 723 metadata present (has_script_metadata)
Sandbox->>UV: uv export --script (try)
alt Export succeeds
UV-->>Sandbox: requirement lines
else Export fails (CalledProcessError)
UV-->>Sandbox: stderr (401, etc.)
Sandbox->>Logger: CHANGED: log warning with redacted URLs
Logger-->>Sandbox: warning logged
Sandbox->>Sandbox: fallback to raw dependency list
end
else No metadata block
Sandbox->>Sandbox: silent fallback (existing behavior)
end
Note over Sandbox,UV: Index flag construction (CHANGED logic)
Sandbox->>Reader: get index_configs (typed entries)
Reader-->>Sandbox: list of IndexConfig
loop Each [[tool.uv.index]] entry
alt Entry is not a dict (valid TOML, invalid uv)
Sandbox->>Sandbox: skip entry (no crash)
else Entry missing url
Sandbox->>Sandbox: skip entry (let uv report)
else Entry has default=true (first one)
Sandbox->>Sandbox: map to --default-index [name=]url
else Entry has default=true (duplicate)
Sandbox->>Logger: warn about duplicate default
Sandbox->>Sandbox: skip entry
else Entry has explicit=true
Sandbox->>Sandbox: collect as --index [name=]url (deferred)
else Regular entry
Sandbox->>Sandbox: map to --index [name=]url (in order)
end
end
Sandbox->>Sandbox: append explicit entries after regular ones
Sandbox->>UV: uv run with constructed flags
Note over UV: --default-index for default entries<br/>--index name=url for named entries<br/>bare --index for unnamed entries
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.
Re-trigger cubic
| header of a markdown notebook). It is empty when the file has | ||
| no metadata. | ||
| """ | ||
| return bool(self.project) |
There was a problem hiding this comment.
P2: When a PEP 723 block contains valid empty TOML, has_script_metadata returns false and stage-1 uv export failures stay silent. Preserve metadata presence separately from the parsed mapping instead of deriving it from truthiness.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At marimo/_utils/inline_script_metadata.py, line 77:
<comment>When a PEP 723 block contains valid empty TOML, `has_script_metadata` returns false and stage-1 `uv export` failures stay silent. Preserve metadata presence separately from the parsed mapping instead of deriving it from truthiness.</comment>
<file context>
@@ -48,6 +66,16 @@ def from_script(script: str) -> PyProjectReader:
+ header of a markdown notebook). It is empty when the file has
+ no metadata.
+ """
+ return bool(self.project)
+
@property
</file context>
There was a problem hiding this comment.
The first part is correct: a block that contains only comments or empty comment lines parses to {}, so has_script_metadata returns False. (A block with zero lines between the markers does not match the PEP 723 regular expression. This block parses to None, the same as no block.)
But the failure in your comment cannot occur. I did a test with uv 0.12.3:
uv export --scriptgives exit code 0 for all empty-block types (comments-only, empty-comment, and zero-line blocks). An empty dependency set resolves with no index and no network. Thus theexceptbranch does not operate for these files.- If the
uv exportoperation does not complete, the fallback result is not worse. For an empty block, the fallback gives[], and a correct export operation gives no packages (one empty line). The two results give the same set of packages: none. Thus a warning gives the user no information.
The warning is for a block that has content. One example is a 401 from an authenticated index. Content makes the dict not empty, and the warning shows. I did a test of this path also. uv export gives exit code 2 for a block with an incorrect [tool.uv] table. The new warning shows uv's error text.
A new flag that shows if a block was found will add data to the constructor and to both factory methods. A user will see no change. Because of this, we keep the bool(self.project) check.
|
|
||
| @property | ||
| def index_configs(self) -> list[dict[str, str]]: | ||
| def index_configs(self) -> list[IndexConfig]: |
There was a problem hiding this comment.
P3: index_configs claims every element is an IndexConfig even though it deliberately exposes non-table TOML entries. Return a type that includes unvalidated values, or validate and filter entries before returning so typed callers cannot skip the required narrowing.
(Based on your team's feedback about strict typing over Any and unchecked assertions.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At marimo/_utils/inline_script_metadata.py, line 89:
<comment>`index_configs` claims every element is an `IndexConfig` even though it deliberately exposes non-table TOML entries. Return a type that includes unvalidated values, or validate and filter entries before returning so typed callers cannot skip the required narrowing.
(Based on your team's feedback about strict typing over `Any` and unchecked assertions.) </comment>
<file context>
@@ -58,7 +86,7 @@ def extra_index_urls(self) -> list[str]:
@property
- def index_configs(self) -> list[dict[str, str]]:
+ def index_configs(self) -> list[IndexConfig]:
# See https://docs.astral.sh/uv/reference/settings/#index
return self.project.get("tool", {}).get("uv", {}).get("index", []) # type: ignore[no-any-return]
</file context>
There was a problem hiding this comment.
Your comment is correct: the annotation shows the correct structure, but marimo does not make sure that the data agrees with the annotation. But we selected this pattern, and the full file uses it:
- Each
PyProjectReaderproperty shows the correct structure of TOML that is not validated (dependencies -> list[str],extra_index_urls -> list[str]). Thetype: ignoreidentifies the trust boundary. - This PR made the annotation more accurate, not less. The old annotation was
list[dict[str, str]], which was incorrect for the boolean keys. - The docstring tells you that the type is "a goal, not a guarantee".
construct_uv_flagsis the only consumer. It examines each entry withisinstance, and the check has a comment and a test. marimo does not remove incorrect entries, because uv reads the script itself and rejects them. My test on uv 0.12.3 gaveinvalid type: string "...", expected struct IndexWireand exit code 2. The new fallback warning shows that error to the user.
If we add one more consumer with type annotations, we can move the validation into the property. That change will make the property the only accessor in the file that validates its value.
This pull request was authored by a coding agent. (Claude Code, driven and reviewed by @jacobcbeaudin)
📝 Summary
Closes #10547
marimo edit --sandboxchanged each[[tool.uv.index]]entry in a notebook's PEP 723 metadata into a bare--index <url>flag. This removed the three meanings that uv gives to an index entry. This PR maps each entry to the flag that keeps its meaning.Before / after — for this script metadata:
uv run--index https://pypi.org/simple/ --index https://internal.example.com/simple/--default-index https://pypi.org/simple/ --index internal=https://internal.example.com/simple/The old flags broke three behaviors:
default = true— uv examines a default index last. A plain--indexmade it first and put it before every other index (in the example above, uv never examinedinternal). The entry now becomes--default-index. If more than one entry hasdefault = true, marimo uses the first entry and logs a warning for the others. uv also uses only the first entry (behavior seen on uv 0.12; the uv docs do not specify this case).name— the name selects theUV_INDEX_<NAME>_USERNAME/UV_INDEX_<NAME>_PASSWORDcredentials. The old flags removed the name, which broke authenticated indexes in sandbox mode. A named entry now becomes--index <name>=<url>, and a named default becomes--default-index <name>=<url>.explicit = true— uv has no flag for this key, so marimo cannot fully obey it. marimo puts explicit entries after the regular indexes. These entries can still supply packages that[tool.uv.sources]does not assign to them; the docs now say so.marimo now skips a non-table entry in the index array (valid TOML that uv rejects with a schema error). Before, such an entry crashed flag construction.
Related fix: the code swallowed a stage-1
uv export --scriptfailure (except CalledProcessError: passwithcapture_output=True). Thus an error such as a 401 from an authenticated index appeared later as a confusing "package not found" error. The fallback now logs uv's stderr as a warning when the script contains a PEP 723 block (a newPyProjectReader.has_script_metadataproperty makes this check). The log redacts URL query strings: uv redacts userinfo credentials, but some registries put auth tokens in query strings. A plain.pyfile with no metadata block keeps its silent fallback.The PR also types
[[tool.uv.index]]entries as aTypedDict. The old annotation,list[dict[str, str]], was wrong for the boolean keys.uv supports all flags used here since uv 0.4.23. The old bare
--indexform had the same minimum version.📋 Pre-Review Checklist
✅ Merge Checklist