Repository: marimo-team/marimo

Issue:
relative path in tool uv sources should be respected in `--sandbox`
### Description

I'm hoping to do something like the folllowing:

```
#!/usr/bin/env -S uv run --with marimo marimo edit --sandbox

# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "my_custom_package==0.1.0",
#     "marimo",
#     "rich",
# ]
#
# [tool.uv.sources]
# my_custom_package = { path = "../" }
# ///
```

I'm using a relative path one level up because this file is located in `./notebooks/marimo-notebook-name.py` and the root of the repository has `my_custom_package`.

If I use `#!/usr/bin/env -S uv run --script` in the shebang the relative path works correctly but with `marimo edit --sandbox` it doesn't.

### Suggested solution

`marimo edit --sandbox` should work exactly like `uv`'s script tag.

### Will you submit a PR?

- [ ] Yes

### Alternative

_No response_

### Additional context

_No response_

Discussion:

Solution patch:
diff --git a/frontend/src/components/chat/chat-panel.tsx b/frontend/src/components/chat/chat-panel.tsx
index bbcb921a..16d8592e 100644
--- a/frontend/src/components/chat/chat-panel.tsx
+++ b/frontend/src/components/chat/chat-panel.tsx
@@ -258,8 +258,9 @@ const ChatPanelBody = () => {
     append,
     handleSubmit,
     error,
-    isLoading,
+    status,
     reload,
+    stop,
   } = useChat({
     keepLastMessageOnError: true,
     // Throttle the messages and data updates to 100ms
@@ -293,6 +294,8 @@ const ChatPanelBody = () => {
     },
   });
 
+  const isLoading = status === "submitted" || status === "streaming";
+
   const createNewThread = (initialMessage: string) => {
     const newChat: Chat = {
       id: generateUUID(),
@@ -418,6 +421,14 @@ const ChatPanelBody = () => {
         <div ref={messagesEndRef} />
       </div>
 
+      {isLoading && (
+        <div className="w-full flex justify-center items-center z-20 border-t">
+          <Button variant="linkDestructive" size="sm" onClick={stop}>
+            Stop
+          </Button>
+        </div>
+      )}
+
       {messages && messages.length > 0 && (
         <ChatInput
           input={input}
diff --git a/frontend/src/components/ui/slider.tsx b/frontend/src/components/ui/slider.tsx
index c602edc8..fe1d1ba6 100644
--- a/frontend/src/components/ui/slider.tsx
+++ b/frontend/src/components/ui/slider.tsx
@@ -28,6 +28,7 @@ const Slider = React.forwardRef<
         "relative flex touch-none select-none hover:cursor-pointer",
         "data-[orientation=horizontal]:w-full data-[orientation=horizontal]:items-center",
         "data-[orientation=vertical]:h-full data-[orientation=vertical]:justify-center",
+        "data-[disabled]:cursor-not-allowed",
         className,
       )}
       {...props}
@@ -46,6 +47,7 @@ const Slider = React.forwardRef<
             "absolute bg-blue-500 dark:bg-primary",
             "data-[orientation=horizontal]:h-full",
             "data-[orientation=vertical]:w-full",
+            "data-[disabled]:opacity-50",
           )}
         />
       </SliderPrimitive.Track>
@@ -54,7 +56,7 @@ const Slider = React.forwardRef<
           <TooltipTrigger asChild={true}>
             <SliderPrimitive.Thumb
               data-testid="thumb"
-              className="block h-4 w-4 rounded-full shadow-xsSolid border border-blue-500 dark:border-primary dark:bg-accent bg-white hover:bg-blue-300 focus:bg-blue-300 transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50"
+              className="block h-4 w-4 rounded-full shadow-xsSolid border border-blue-500 dark:border-primary dark:bg-accent bg-white hover:bg-blue-300 focus:bg-blue-300 transition-colors focus-visible:outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
               onFocus={openActions.setTrue}
               onBlur={openActions.setFalse}
               onMouseEnter={openActions.setTrue}
diff --git a/frontend/src/plugins/impl/MultiselectPlugin.tsx b/frontend/src/plugins/impl/MultiselectPlugin.tsx
index 78546c66..ab726100 100644
--- a/frontend/src/plugins/impl/MultiselectPlugin.tsx
+++ b/frontend/src/plugins/impl/MultiselectPlugin.tsx
@@ -78,7 +78,7 @@ export const Multiselect = ({
   }, [options, searchQuery]);
 
   const handleValueChange = (newValues: string[] | null) => {
-    if (!newValues) {
+    if (!newValues || newValues.length === 0) {
       setValue([]);
       return;
     }
diff --git a/frontend/src/plugins/impl/SliderPlugin.tsx b/frontend/src/plugins/impl/SliderPlugin.tsx
index 9f9a4811..f0752d49 100644
--- a/frontend/src/plugins/impl/SliderPlugin.tsx
+++ b/frontend/src/plugins/impl/SliderPlugin.tsx
@@ -22,6 +22,7 @@ interface Data {
   showValue: boolean;
   fullWidth: boolean;
   includeInput: boolean;
+  disabled?: boolean;
 }
 
 export class SliderPlugin implements IPlugin<T, Data> {
@@ -39,6 +40,7 @@ export class SliderPlugin implements IPlugin<T, Data> {
     showValue: z.boolean().default(false),
     fullWidth: z.boolean().default(false),
     includeInput: z.boolean().default(false),
+    disabled: z.boolean().optional(),
   });
 
   render(props: IPluginProps<T, Data>): JSX.Element {
@@ -81,6 +83,7 @@ const SliderComponent = ({
   fullWidth,
   valueMap,
   includeInput,
+  disabled,
 }: SliderProps): JSX.Element => {
   const id = useId();
 
@@ -132,6 +135,7 @@ const SliderComponent = ({
             }
           }}
           valueMap={valueMap} // Pass valueMap to Slider
+          disabled={disabled}
         />
         {showValue && (
           <div className="text-xs text-muted-foreground min-w-[16px]">
@@ -152,6 +156,7 @@ const SliderComponent = ({
             step={step}
             className="w-24"
             aria-label={`${label || "Slider"} value input`}
+            isDisabled={disabled}
           />
         )}
       </div>
diff --git a/marimo/_cli/sandbox.py b/marimo/_cli/sandbox.py
index efd673b3..ccf1e831 100644
--- a/marimo/_cli/sandbox.py
+++ b/marimo/_cli/sandbox.py
@@ -210,7 +210,7 @@ def construct_uv_command(
     pyproject = (
         PyProjectReader.from_filename(name)
         if name is not None
-        else PyProjectReader({})
+        else PyProjectReader({}, config_path=None)
     )
 
     uv_cmd = ["uv", "run"]
diff --git a/marimo/_output/formatters/df_formatters.py b/marimo/_output/formatters/df_formatters.py
index 971f2237..bc0dc69e 100644
--- a/marimo/_output/formatters/df_formatters.py
+++ b/marimo/_output/formatters/df_formatters.py
@@ -7,10 +7,14 @@ import narwhals.stable.v1 as nw
 
 from marimo import _loggers
 from marimo._messaging.mimetypes import KnownMimeType
-from marimo._output.formatters.formatter_factory import FormatterFactory
+from marimo._output.formatters.formatter_factory import (
+    FormatterFactory,
+    Unregister,
+)
 from marimo._output.md import md
 from marimo._plugins.ui._impl import tabs
 from marimo._plugins.ui._impl.table import get_default_table_page_size, table
+from marimo._runtime.patches import patch_polars_write_json
 
 LOGGER = _loggers.marimo_logger()
 
@@ -35,13 +39,15 @@ class PolarsFormatter(FormatterFactory):
     def package_name() -> str:
         return "polars"
 
-    def register(self) -> None:
+    def register(self) -> Unregister | None:
         import polars as pl
 
         from marimo._output import formatting
 
         if not include_opinionated():
-            return
+            return None
+
+        unpatch_polars_write_json = patch_polars_write_json()
 
         @formatting.opinionated_formatter(pl.DataFrame)
         def _show_marimo_dataframe(
@@ -79,6 +85,8 @@ class PolarsFormatter(FormatterFactory):
                 }
             )._mime_()
 
+        return unpatch_polars_write_json
+
 
 class PyArrowFormatter(FormatterFactory):
     @staticmethod
diff --git a/marimo/_output/formatters/formatter_factory.py b/marimo/_output/formatters/formatter_factory.py
index a347117b..3eb985ea 100644
--- a/marimo/_output/formatters/formatter_factory.py
+++ b/marimo/_output/formatters/formatter_factory.py
@@ -9,6 +9,8 @@ from marimo._config.config import Theme
 
 LOGGER = _loggers.marimo_logger()
 
+Unregister = Callable[[], None]
+
 
 # Abstract base class for formatters that are installed at runtime.
 class FormatterFactory(abc.ABC):
@@ -25,7 +27,7 @@ class FormatterFactory(abc.ABC):
         raise NotImplementedError
 
     @abc.abstractmethod
-    def register(self) -> Callable[[], None] | None:
+    def register(self) -> Unregister | None:
         """Registers formatters.
 
         Formatters can be registered using the formatters.formatter decorator.
diff --git a/marimo/_plugins/ui/_impl/input.py b/marimo/_plugins/ui/_impl/input.py
index c7a791e2..c4037cdb 100644
--- a/marimo/_plugins/ui/_impl/input.py
+++ b/marimo/_plugins/ui/_impl/input.py
@@ -185,6 +185,7 @@ class slider(UIElement[Numeric, Numeric]):
         value (Optional[Numeric]): Default value.
         debounce (bool): Whether to debounce the slider to only send the value
             on mouse-up or drag-end. Defaults to False.
+        disabled (bool, optional): Whether the slider is disabled. Defaults to False.
         orientation (Literal["horizontal", "vertical"]): The orientation of the
             slider, either "horizontal" or "vertical". Defaults to "horizontal".
         show_value (bool): Whether to display the current value of the slider.
@@ -222,6 +223,7 @@ class slider(UIElement[Numeric, Numeric]):
         step: Optional[Numeric] = None,
         value: Optional[Numeric] = None,
         debounce: bool = False,
+        disabled: bool = False,
         orientation: Literal["horizontal", "vertical"] = "horizontal",
         show_value: bool = False,
         include_input: bool = False,
@@ -293,6 +295,7 @@ class slider(UIElement[Numeric, Numeric]):
                     "step": 1,
                     "steps": steps,
                     "debounce": debounce,
+                    "disabled": disabled,
                     "orientation": orientation,
                     "show-value": show_value,
                     "include-input": include_input,
@@ -335,6 +338,7 @@ class slider(UIElement[Numeric, Numeric]):
                     "step": step if step is not None else None,
                     "steps": [],
                     "debounce": debounce,
+                    "disabled": disabled,
                     "orientation": orientation,
                     "show-value": show_value,
                     "include-input": include_input,
diff --git a/marimo/_runtime/patches.py b/marimo/_runtime/patches.py
index 287bea58..b45b4f27 100644
--- a/marimo/_runtime/patches.py
+++ b/marimo/_runtime/patches.py
@@ -10,6 +10,9 @@ from typing import TYPE_CHECKING, Any, Callable
 
 from marimo._dependencies.dependencies import DependencyManager
 from marimo._runtime import marimo_browser, marimo_pdb
+from marimo._utils.platform import is_pyodide
+
+Unpatch = Callable[[], None]
 
 if TYPE_CHECKING:
     from collections.abc import Iterator
@@ -419,3 +422,72 @@ def patch_jedi_parameter_completion() -> None:
         SignatureParamName.infer = wrap_infer(original_dynamic_infer)
     if not getattr(SignatureParamName.__init__, "patched", False):
         SignatureParamName.__init__ = enhanced_init
+
+
+def patch_polars_write_json() -> Unpatch:
+    """Patch polars.DataFrame.write_json to work in WASM environments.
+
+    In WASM, file system operations may fail. This patch attempts to use
+    write_json first, and if it fails, falls back to write_csv and then
+    converts the CSV to JSON.
+    """
+    if not is_pyodide():
+        return lambda: None
+
+    import io
+    import pathlib
+
+    import polars
+
+    original_write_json = polars.DataFrame.write_json
+
+    def patched_write_json(
+        self: polars.DataFrame,
+        file: io.IOBase | str | pathlib.Path | None = None,
+        *args: Any,
+        **kwargs: Any,
+    ) -> str | None:
+        try:
+            # First try the original method
+            return original_write_json(self, file, *args, **kwargs)
+        except Exception:
+            # Fallback to CSV
+            import json
+
+            buffer = io.BytesIO()
+
+            # Write to CSV
+            self.write_csv(buffer)
+
+            # Read CSV as text
+            buffer.seek(0)
+            csv_content = buffer.read().decode("utf-8")
+
+            # Parse CSV to create JSON
+            lines = csv_content.strip().split("\n")
+            json_data: list[dict[str, str]] = []
+            if lines:
+                headers: list[str] = lines[0].split(",")
+                for line in lines[1:]:
+                    values: list[str] = line.split(",")
+                    json_data.append(dict(zip(headers, values)))
+
+            if file is None:
+                return json.dumps(json_data)
+            elif isinstance(file, io.IOBase):
+                json.dump(json_data, file)
+            elif isinstance(file, pathlib.Path):
+                file.write_text(json.dumps(json_data))
+            else:
+                with open(file, "w", encoding="utf-8") as f:
+                    json.dump(json_data, f)
+
+            return None
+
+    # Apply the patch
+    polars.DataFrame.write_json = patched_write_json  # type: ignore
+
+    def unpatch_polars_write_json() -> None:
+        polars.DataFrame.write_json = original_write_json  # type: ignore
+
+    return unpatch_polars_write_json
diff --git a/marimo/_utils/inline_script_metadata.py b/marimo/_utils/inline_script_metadata.py
index f979fa59..ebb025f1 100644
--- a/marimo/_utils/inline_script_metadata.py
+++ b/marimo/_utils/inline_script_metadata.py
@@ -2,6 +2,7 @@
 from __future__ import annotations
 
 import re
+from pathlib import Path
 from typing import Any, cast
 
 from marimo import _loggers
@@ -12,16 +13,21 @@ LOGGER = _loggers.marimo_logger()
 
 
 class PyProjectReader:
-    def __init__(self, project: dict[str, Any]):
+    def __init__(self, project: dict[str, Any], *, config_path: str | None):
         self.project = project
+        self.config_path = config_path
 
     @staticmethod
     def from_filename(name: str) -> PyProjectReader:
-        return PyProjectReader(_get_pyproject_from_filename(name) or {})
+        return PyProjectReader(
+            _get_pyproject_from_filename(name) or {}, config_path=name
+        )
 
     @staticmethod
     def from_script(script: str) -> PyProjectReader:
-        return PyProjectReader(read_pyproject_from_script(script) or {})
+        return PyProjectReader(
+            read_pyproject_from_script(script) or {}, config_path=None
+        )
 
     @property
     def extra_index_urls(self) -> list[str]:
@@ -64,7 +70,9 @@ class PyProjectReader:
     def requirements_txt_lines(self) -> list[str]:
         """Get dependencies from string representation of script."""
         try:
-            return _pyproject_toml_to_requirements_txt(self.project)
+            return _pyproject_toml_to_requirements_txt(
+                self.project, self.config_path
+            )
         except Exception as e:
             LOGGER.warning(f"Failed to parse dependencies: {e}")
             return []
@@ -102,6 +110,7 @@ def _get_pyproject_from_filename(name: str) -> dict[str, Any] | None:
 
 def _pyproject_toml_to_requirements_txt(
     pyproject: dict[str, Any],
+    config_path: str | None = None,
 ) -> list[str]:
     """
     Convert a pyproject.toml file to a requirements.txt file.
@@ -115,6 +124,11 @@ def _pyproject_toml_to_requirements_txt(
     #
     # [tool.uv.sources]
     # python-gcode = { git = "https://github.com/fetlab/python_gcode", rev = "new" }
+
+    Args:
+        pyproject: A dict containing the pyproject.toml contents.
+        config_path: The path to the pyproject.toml or inline script metadata. This
+            is used to resolve relative paths used in the dependencies.
     """  # noqa: E501
     dependencies = cast(list[str], pyproject.get("dependencies", []))
     if not dependencies:
@@ -155,7 +169,12 @@ def _pyproject_toml_to_requirements_txt(
             )
         # Handle local paths
         elif "path" in source:
-            new_dependency = f"{dependency} @ {source['path']}"
+            source_path = Path(source["path"])
+            # If path is relative and we have a config path, resolve it relative to the config path
+            if not source_path.is_absolute() and config_path:
+                config_dir = Path(config_path).parent
+                source_path = (config_dir / source_path).resolve()
+            new_dependency = f"{dependency} @ {str(source_path)}"
 
         # Handle URLs
         elif "url" in source:


Test patch:
diff --git a/tests/_cli/test_sandbox.py b/tests/_cli/test_sandbox.py
index 583562cc..27ae4c4e 100644
--- a/tests/_cli/test_sandbox.py
+++ b/tests/_cli/test_sandbox.py
@@ -195,7 +195,7 @@ def test_construct_uv_cmd_with_index_urls() -> None:
         }
     }
     with patch("marimo._cli.sandbox.PyProjectReader.from_filename") as mock:
-        mock.return_value = PyProjectReader(pyproject)
+        mock.return_value = PyProjectReader(pyproject, config_path=None)
         uv_cmd = construct_uv_command(
             ["edit", "test.py", "--sandbox"],
             "test.py",
@@ -223,7 +223,7 @@ def test_construct_uv_cmd_with_index_configs() -> None:
         }
     }
     with patch("marimo._cli.sandbox.PyProjectReader.from_filename") as mock:
-        mock.return_value = PyProjectReader(pyproject)
+        mock.return_value = PyProjectReader(pyproject, config_path=None)
         uv_cmd = construct_uv_command(
             ["edit", "test.py", "--sandbox"],
             name="test.py",
@@ -248,7 +248,7 @@ def test_construct_uv_cmd_with_sandbox_flag() -> None:
 def test_construct_uv_cmd_empty_dependencies() -> None:
     # Test empty dependencies triggers refresh
     with patch("marimo._cli.sandbox.PyProjectReader.from_filename") as mock:
-        mock.return_value = PyProjectReader({})
+        mock.return_value = PyProjectReader({}, config_path=None)
         uv_cmd = construct_uv_command(
             ["edit", "test.py"],
             name="test.py",
diff --git a/tests/_runtime/test_patches.py b/tests/_runtime/test_patches.py
index ca615474..0d0200f9 100644
--- a/tests/_runtime/test_patches.py
+++ b/tests/_runtime/test_patches.py
@@ -1,11 +1,23 @@
 # Copyright 2024 Marimo. All rights reserved.
 from __future__ import annotations
 
+import io
+from types import ModuleType
+from typing import TYPE_CHECKING
+from unittest.mock import patch
+
+import pytest
+
+from marimo._dependencies.dependencies import DependencyManager
 from marimo._runtime.capture import capture_stderr
+from marimo._runtime.patches import patch_polars_write_json
 from marimo._runtime.runtime import Kernel
 from marimo._utils.platform import is_pyodide
 from tests.conftest import ExecReqProvider
 
+if TYPE_CHECKING:
+    from pathlib import Path
+
 
 class TestMicropip:
     @staticmethod
@@ -175,3 +187,59 @@ async def test_webbrowser_easter_egg(
 
     assert "<iframe" not in outputs[-1]
     assert "<img" in outputs[-1]
+
+
+@pytest.mark.skipif(
+    not DependencyManager.polars.has(),
+    reason="Polars is not installed",
+)
+def test_polars_write_json_patch(tmp_path: Path):
+    import sys
+
+    import polars as pl
+
+    file_path = tmp_path / "test.json"
+
+    # Fake put pyodide in sys.modules
+    sys.modules["pyodide"] = ModuleType("pyodide")
+
+    # Make write_json throw an error
+    with patch(
+        "polars.DataFrame.write_json",
+        side_effect=ValueError("Test error"),
+    ):
+        # Test it fails
+        df = pl.DataFrame({"a": [1, 2], "b": ["x", "y"]})
+        with pytest.raises(ValueError, match="Test error"):
+            df.write_json(file_path)
+
+        # Patch to fallback to write_csv
+        unpatch_polars_write_json = patch_polars_write_json()
+
+        expected_json = '[{"a": "1", "b": "x"}, {"a": "2", "b": "y"}]'
+
+        # Test it succeeds with file path
+        df.write_json(file_path)
+        assert file_path.read_text() == expected_json
+
+        # Test it succeeds with string path
+        df.write_json(str(file_path))
+        assert file_path.read_text() == expected_json
+
+        # Test it succeeds with buffer
+        buffer = io.StringIO()
+        df.write_json(buffer)
+        assert buffer.getvalue() == expected_json
+
+        # Test it succeeds with None
+        assert df.write_json() == expected_json
+
+        # Patch the patch
+        unpatch_polars_write_json()
+
+        # Test it fails again
+        with pytest.raises(ValueError, match="Test error"):
+            df.write_json(file_path)
+
+    # Remove pyodide from sys.modules
+    del sys.modules["pyodide"]
diff --git a/tests/_server/export/snapshots/notebook_with_outputs.ipynb.txt b/tests/_server/export/snapshots/notebook_with_outputs.ipynb.txt
index b89012b6..725dfc2d 100644
--- a/tests/_server/export/snapshots/notebook_with_outputs.ipynb.txt
+++ b/tests/_server/export/snapshots/notebook_with_outputs.ipynb.txt
@@ -139,7 +139,7 @@
     {
      "data": {
       "text/markdown": [
-       "&lt;marimo-slider data-initial-value=&#x27;0&#x27; data-label=&#x27;null&#x27; data-start=&#x27;0&#x27; data-stop=&#x27;10&#x27; data-steps=&#x27;[]&#x27; data-debounce=&#x27;false&#x27; data-orientation=&#x27;&amp;quot;horizontal&amp;quot;&#x27; data-show-value=&#x27;false&#x27; data-include-input=&#x27;false&#x27; data-full-width=&#x27;false&#x27;&gt;&lt;/marimo-slider&gt;"
+       "&lt;marimo-slider data-initial-value=&#x27;0&#x27; data-label=&#x27;null&#x27; data-start=&#x27;0&#x27; data-stop=&#x27;10&#x27; data-steps=&#x27;[]&#x27; data-debounce=&#x27;false&#x27; data-disabled=&#x27;false&#x27; data-orientation=&#x27;&amp;quot;horizontal&amp;quot;&#x27; data-show-value=&#x27;false&#x27; data-include-input=&#x27;false&#x27; data-full-width=&#x27;false&#x27;&gt;&lt;/marimo-slider&gt;"
       ]
      },
      "metadata": {},
diff --git a/tests/_utils/test_inline_script_metadata.py b/tests/_utils/test_inline_script_metadata.py
index 4dc480f4..4bea07f9 100644
--- a/tests/_utils/test_inline_script_metadata.py
+++ b/tests/_utils/test_inline_script_metadata.py
@@ -157,6 +157,30 @@ def test_pyproject_toml_to_requirements_txt_with_local_path():
     ]
 
 
+def test_pyproject_toml_to_requirements_txt_with_relative_path():
+    pyproject = {
+        "dependencies": [
+            "marimo",
+            "polars",
+        ],
+        "tool": {
+            "uv": {
+                "sources": {
+                    "marimo": {
+                        "path": "../local/marimo",
+                    }
+                }
+            }
+        },
+    }
+    # Test with a config path to verify relative path resolution
+    config_path = "/Users/me/project/script.py"
+    assert _pyproject_toml_to_requirements_txt(pyproject, config_path) == [
+        "marimo @ /Users/me/local/marimo",
+        "polars",
+    ]
+
+
 @pytest.mark.parametrize(
     "version_spec",
     [
@@ -193,15 +217,17 @@ def test_pyproject_toml_to_requirements_txt_with_versioned_dependencies(
 
 def test_get_python_version_requirement():
     pyproject = {"requires-python": ">=3.11"}
-    assert PyProjectReader(pyproject).python_version == ">=3.11"
+    assert (
+        PyProjectReader(pyproject, config_path=None).python_version == ">=3.11"
+    )
 
     pyproject = {"dependencies": ["polars"]}
-    assert PyProjectReader(pyproject).python_version is None
+    assert PyProjectReader(pyproject, config_path=None).python_version is None
 
-    assert PyProjectReader({}).python_version is None
+    assert PyProjectReader({}, config_path=None).python_version is None
 
     pyproject = {"requires-python": {"invalid": "type"}}
-    assert PyProjectReader(pyproject).python_version is None
+    assert PyProjectReader(pyproject, config_path=None).python_version is None
 
 
 def test_get_dependencies_with_python_version():
@@ -217,7 +243,9 @@ import marimo
 
     pyproject = read_pyproject_from_script(SCRIPT)
     assert pyproject is not None
-    assert PyProjectReader(pyproject).python_version == ">=3.11"
+    assert (
+        PyProjectReader(pyproject, config_path=None).python_version == ">=3.11"
+    )
 
     SCRIPT_NO_PYTHON = """
 # /// script
@@ -228,7 +256,10 @@ import marimo
 """
     pyproject_no_python = read_pyproject_from_script(SCRIPT_NO_PYTHON)
     assert pyproject_no_python is not None
-    assert PyProjectReader(pyproject_no_python).python_version is None
+    assert (
+        PyProjectReader(pyproject_no_python, config_path=None).python_version
+        is None
+    )
     assert PyProjectReader.from_script(SCRIPT_NO_PYTHON).dependencies == [
         "polars"
     ]