Skip to content
rlsbl.commands.rewrite.uv_path_sources
On this page

Converts path- and workspace-sourced dependencies into registry floors at the version the lock resolves, deleting the source entry that overrode them.

#rlsbl.commands.rewrite.uv_path_sources

#rlsbl.commands.rewrite.uv_path_sources

Convert path/workspace-sourced dependencies into registry floors.

rlsbl rewrite uv-path-sources takes a working tree whose pyproject.toml resolves internal dependencies from local checkouts and turns each one into a registry constraint floored at the version the lock already resolves:

  • [project].dependencies, every [project.optional-dependencies] extra

and every PEP 735 [dependency-groups] group have their direct references (dep @ file:///...) replaced by dep>=<locked version>;

  • the matching [tool.uv.sources] path/workspace entry is deleted, because

a source entry left behind keeps overriding the constraint that was just written. A source declared as a LIST of marker-gated tables is PRUNED instead: only its path/workspace elements go, so an index sibling covering the platforms the checkout does not is left standing;

  • .rlsbl/config.json's internal_dep_floors gains every converted name,

so rlsbl's dep-floors preflight check starts policing the floor it just created (the key is created when absent).

Where the lock is read from ---------------------------

The floor is the version uv.lock resolves, and there is exactly ONE lock that resolves a given manifest. Which file that is comes from :func:rlsbl.uv_workspace.locate_uv_lock -- the shared locator this command originally grew and now shares with the dep-floors and dep-locks checks: the lock beside the manifest, else the lock of the uv workspace root that claims the directory, else nothing. A search that finds nothing is a hard error here, naming both locations probed.

Whichever lock is read, the WRITES stay in the target directory: its own pyproject.toml and its own .rlsbl/config.json (created when absent).

Release-first, enforced -----------------------

A floor is only meaningful if the registry can satisfy it. Before writing anything the command probes PyPI for the exact locked version of each dependency, and:

  • not published is a hard error naming the remedy -- release that

dependency first. Writing the floor anyway would produce a manifest no consumer can resolve, which is the exact failure this command exists to prevent.

  • probe failure (network, HTTP 5xx, anything that is not a clean 404) is

ALSO a hard error. Fail-closed: "we could not ask" is not evidence of publication, and a floor written on a failed probe is indistinguishable from one written on a lie.

The probe is a plain HTTPS GET through effects.urlopen, which executes in every mode including --dry-run -- a read changes nothing on the far side, and a preview that could not probe would have nothing to preview.

Preview and apply -----------------

One verdict item per dependency, plus one for the config-key update. Each dependency's item carries the number of manifest entries it occupies, and the apply re-counts them from disk before writing: a count that moved between preview and apply is a hard abort with nothing further written.

#UvPathSourceError

A hard error in the path-source conversion.

#Conversion

One dependency's pending conversion.

#occurrences

python
def occurrences(self)

Everything the apply would rewrite for this dependency.

#constraint

python
def constraint(self)

#resolve_lock

python
def resolve_lock(project_root)

(locked versions, LockLocation) for the manifest in project_root.

The location comes from :func:rlsbl.uv_workspace.locate_uv_lock; this function adds the two refusals that belong to READING it: no lock at all, and a lock that is present but does not parse. The second is a hard error rather than an absence, because treating it as absent would walk past it to a different lock -- exactly the silent switch this command must not make.

#path_sourced_names

python
def path_sourced_names(doc)

{normalized: declared} for every path/workspace-sourced dependency.

The union of two declarations, because uv accepts either shape:

  • a [tool.uv.sources] entry with path/workspace, whose

dependency array entry is an ORDINARY requirement (often bare "sibling" with no constraint at all) -- the common uv shape;

  • a direct reference in the dependency array itself

("sibling @ file:///...") with no sources entry.

#count_entries

python
def count_entries(doc, name)

(dependency-array entries, sources entries) naming name.

#collect_conversions

python
def collect_conversions(doc, locked, lock_label='uv.lock')

Every path/workspace-sourced dependency, with its locked version.

lock_label names the lock locked was read from, so a refusal points at the file that failed to resolve the package -- which is not always the one beside the manifest (see :func:resolve_lock).

#probe_published

python
def probe_published(name, version)

Hard-error unless version of name is published on PyPI.

Fail-closed: anything that is not a definitive "found" refuses.

#observe

python
def observe(project_root, *, probe=probe_published)

Build the plan: one item per dependency, plus the config update.

#apply_item

python
def apply_item(item, project_root, applied=None)

Apply one item, refusing when its count moved since the preview.

applied is an optional list the caller passes through every item; each successful write appends its key, so an abort can name what is already on disk (this command has no rollback -- see :mod:.abort).

#_apply_config

python
def _apply_config(root, additions)

Add additions to internal_dep_floors. True when it wrote.

#cmd_uv_path_sources

python
def cmd_uv_path_sources(flags, project_root)

rlsbl rewrite uv-path-sources -- path sources become registry floors.

flags["dry-run"] -- plan only.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search