On this page
Path dependency detection and rewriting for PyPI packages, converting local file references in pyproject.toml to versioned constraints.
#rlsbl.dep_rewrite
#rlsbl.dep_rewrite
Path-dependency detection and rewriting for Python projects.
Two consumers share this module, and they want the same primitives applied to different section families:
- The PyPI build (:mod:
rlsbl.targets.pypi) rewrites a COPY of
pyproject.toml in a temp directory so a monorepo package's sibling path dependencies become version constraints in the built artifact. It covers the sections that end up in published metadata -- :data:SECTIONS_PUBLISHED.
- **
rlsbl rewrite uv-path-sources** rewrites the WORKING TREE, converting
every path/workspace-sourced internal dependency into a registry floor. It covers :data:SECTIONS_ALL, which adds PEP 735 [dependency-groups]: a dev-only path source is exactly as unbuildable for a consumer's checkout as a runtime one, even though it never reaches published metadata.
The section family is always passed explicitly. A default would silently decide which of those two jobs a caller is doing.
#iter_dep_arrays
def iter_dep_arrays(doc, families)Yield (section_label, array) for each dependency array in doc.
doc is a parsed tomlkit document; the arrays are yielded live, so a caller can mutate them in place and dump the document afterwards. families selects which section families to visit -- see :data:SECTIONS_PUBLISHED / :data:SECTIONS_ALL.
#detect_path_deps_in
def detect_path_deps_in(doc, families)Path dependencies declared in doc, restricted to families.
Returns a list of dicts with keys name, original, line_in_deps (index within its array) and section.
#rewrite_dep_arrays
def rewrite_dep_arrays(doc, rewrites, families)Rewrite path deps named in rewrites, in place. Returns the count.
rewrites maps package name to a version constraint (">=1.2.0"). Only PATH dependencies are rewritten: a dependency already carrying a registry constraint is left alone.
#_split_requirement_parts
def _split_requirement_parts(text)(name, extras, marker) of a requirement, or None when unparseable.
extras keeps its brackets ("[cli]") or is empty; marker keeps its leading ; ("; python_version < '3.12'") or is empty. Both are carried through a rewrite verbatim: dropping an extra changes what gets installed, and dropping a marker changes WHERE it gets installed.
#find_dep_entries
def find_dep_entries(doc, names, families)Every dependency-array entry naming one of names.
names is a mapping of normalized name to the name as declared, so the caller decides the normalization (PEP 503 for PyPI). Returns a list of dicts with section, index, original, normalized.
#floor_dep_entries
def floor_dep_entries(doc, floors, families)Rewrite entries naming a package in floors to name[extras]>=version.
floors maps normalized package name to the floor version string. Unlike :func:rewrite_dep_arrays this touches an entry whatever its current form -- a bare "sibling", an existing "sibling>=0.1" and a direct "sibling @ file:///..." all become "sibling>=<floor>". That is the point of the conversion: the floor must be the LOCKED version, not whatever the manifest happened to say while the dependency resolved from a checkout. Returns the number of entries rewritten.
#_normalize
def _normalize(name)PEP 503 normalization, local to avoid a cycle through dep_floors.
#detect_uv_path_sources
def detect_uv_path_sources(doc)Path/workspace entries in [tool.uv.sources].
Returns {package_name: kind} where kind is "path" or "workspace". Registry-neutral sources (git, url, index) are not returned: they already resolve to something a consumer can fetch. A source declared as a LIST of marker-gated tables counts when any of its entries is a path or workspace source.
#_source_kind
def _source_kind(entry)"path", "workspace" or None for one [tool.uv.sources] entry.
#remove_uv_sources
def remove_uv_sources(doc, names)Remove the path/workspace sources for names. Returns the count.
A source declared as a LIST of marker-gated tables is pruned rather than deleted: only its path/workspace elements go, and a non-path sibling (an index variant for the platforms the checkout does not cover, say) stays. Deleting the whole key would drop a declaration the caller never asked about and that nothing else restores. A list left with exactly one element stays a list -- uv reads it identically, and collapsing it would rewrite formatting this function was not asked to touch.
A list whose every element is a path/workspace source is removed entirely, like a plain table. An emptied sources table is removed too (and an emptied [tool.uv] with it), so the rewrite does not leave a header for nothing.
The count is per NAME, not per element: it answers "how many of the names I asked about were sourced from a checkout", which is what both callers compare against their preview.
#detect_path_deps
def detect_path_deps(pyproject_path)Detect path dependencies in a pyproject.toml file.
Covers the published sections only (:data:SECTIONS_PUBLISHED) -- this is the build's question: "does the artifact's metadata reference a checkout?"
Returns a list of dicts with keys: - name: the dependency package name - original: the full original dependency string - line_in_deps: index within the dependencies array - section: "dependencies" or "optional-dependencies.
#rewrite_pyproject_deps
def rewrite_pyproject_deps(content, rewrites)Rewrite path dependencies in pyproject.toml content to versioned constraints.
Args:
content: raw pyproject.toml content stringrewrites: dict mapping package name to version constraint string
(e.g., {"core": ">=1.2.0"})
Returns the modified content as a string with formatting preserved.
#build_rewrite_map
def build_rewrite_map(workspace_root, projects, graph)Build a mapping of dependency names to version constraints.
For each project in the workspace that has a detectable version, adds name: ">=version" to the map. This map is intended to be passed to rewrite_pyproject_deps.
Args:
workspace_root: absolute path to the workspace rootprojects: list of project dicts (each with "name" and "path")graph: a WorkspaceGraph instance (unused currently, reserved for
future constraint refinement)
Returns a dict mapping package name to version constraint string.