Why uv rm Removes Source Declarations from pyproject.toml

Summary

Removing a dependency with uv rm also strips the entry from the [tool.uv.sources] table, because uv treats that entry as part of the installed package definition.
To keep the source declaration while un‑installing the package, you must manipulate the pyproject.toml manually (or with a script) rather than using uv rm.

Root Cause

  • uv rm <pkg> removes all references to <pkg> in the project file, including the custom source line under [tool.uv.sources].
  • The editable=true source mapping is considered part of the package specification, so uv cannot distinguish “remove the locked version” from “keep the source entry”.

Why This Happens in Real Systems

  • Unified command model: uv aims to keep the manifest consistent; deleting a package should leave no dangling configuration.
  • Editable installs are treated as a single source of truth—if the package isn’t listed as a dependency, its source entry is deemed irrelevant.
  • This behavior mirrors other packaging tools (e.g., pip uninstall removes the requirement line from a requirements.txt).

Real-World Impact

  • Developer friction: Every time a library’s own dependencies change, developers must:
    • Delete the source entry.
    • Re‑add the library to re‑inject the source line.
  • CI/CD instability: Automated pipelines that uv rm a package can unintentionally corrupt the pyproject.toml, causing builds to fail until the source line is restored.
  • Jupyter notebook churn: Restarting kernels does not reload the updated source, forcing the manual cycle described above.

Example or Code (if necessary and relevant)

# pyproject.toml (excerpt)
[tool.uv.sources]
mylib = { path = "/PATH/TO/MY/LIBRARY", editable = true }

[project]
dependencies = [
    "mylib",
    # other deps…
]

How Senior Engineers Fix It

  • Edit pyproject.toml directly: Remove mylib from the dependencies list but leave the entry under [tool.uv.sources].
  • Automate with a tiny script to strip the dependency while preserving the source line:
    import tomllib, tomli_w, pathlib

proj = pathlib.Path(“pyproject.toml”)
data = tomllib.loads(proj.read_text())

Remove from dependencies

deps = data.get(“project”, {}).get(“dependencies”, [])
data[“project”][“dependencies”] = [d for d in deps if not d.startswith(“mylib”)]

proj.write_text(tomli_w.dumps(data))

- **Use `uv sync`** after editing: `uv sync` will reinstall the remaining dependencies, respecting the unchanged source entry.
- **Pin the library version** in the main project (e.g., `mylib @ file:///PATH/TO/MY/LIBRARY`) instead of relying on `[tool.uv.sources]`; this keeps the source reference even if the package is removed from `dependencies`.

## Why Juniors Miss It
- **Assume** `uv rm` behaves like a simple uninstall, not realizing it also edits the source table.
- **Overlook** the separation between *dependency list* and *source mapping* in `pyproject.toml`.
- **Rely** on interactive tools (e.g., Jupyter) and miss the necessity of a static file edit, leading to repeated trial‑and‑error cycles.

Leave a Comment