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'sinternal_dep_floorsgains 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
def occurrences(self)Everything the apply would rewrite for this dependency.
#constraint
def constraint(self)#resolve_lock
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
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 withpath/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
def count_entries(doc, name)(dependency-array entries, sources entries) naming name.
#collect_conversions
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
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
def observe(project_root, *, probe=probe_published)Build the plan: one item per dependency, plus the config update.
#apply_item
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
def _apply_config(root, additions)Add additions to internal_dep_floors. True when it wrote.
#cmd_uv_path_sources
def cmd_uv_path_sources(flags, project_root)rlsbl rewrite uv-path-sources -- path sources become registry floors.
flags["dry-run"] -- plan only.