repomatic.deps.dep_graph module¶
Generate Mermaid dependency graphs from uv lockfiles.
Every box in the graph (the primary dependencies rectangle and each
--group/--extra subgraph) only holds directly-declared dependencies,
drawn as hexagons: the packages under the project’s control, referenced in
pyproject.toml. Transitive dependencies always render outside the boxes,
as plain ovals.
Note
Uses uv export --format cyclonedx1.5 which provides structured JSON
with dependency relationships, replacing the need for pipdeptree.
Warning
The generated Mermaid syntax targets the version bundled with
sphinxcontrib-mermaid, currently 11.12.1. See the hard-coded
MERMAID_VERSION constant in sphinxcontrib-mermaid’s source.
Avoid using Mermaid features introduced after that version.
- repomatic.deps.dep_graph.STYLE_PRIMARY_DEPS_SUBGRAPH: str = 'fill:#1565C020,stroke:#42A5F5'¶
Mermaid style for the primary dependencies subgraph box.
Uses semi-transparent fill (8-digit hex) so the tint adapts to both light and dark page backgrounds.
- repomatic.deps.dep_graph.STYLE_EXTRA_SUBGRAPH: str = 'fill:#7B1FA220,stroke:#BA68C8'¶
Mermaid style for extra dependency subgraph boxes.
Uses semi-transparent fill (8-digit hex) so the tint adapts to both light and dark page backgrounds.
- repomatic.deps.dep_graph.STYLE_GROUP_SUBGRAPH: str = 'fill:#546E7A20,stroke:#90A4AE'¶
Mermaid style for group dependency subgraph boxes.
Uses semi-transparent fill (8-digit hex) so the tint adapts to both light and dark page backgrounds.
- repomatic.deps.dep_graph.STYLE_PRIMARY_NODE: str = 'stroke-width:3px'¶
Mermaid style for root and primary dependency nodes (thick border).
- repomatic.deps.dep_graph.STYLE_DUPLICATE_NODE: str = 'stroke-width:3px,stroke-dasharray:5 5'¶
Mermaid style for duplicate headline nodes (dashed thick border).
The dashes mark the node as a display-only mirror of the real node owned by another subgraph; a dotted identity link ties the two together. Derived from
STYLE_PRIMARY_NODEsince duplicates are always headline (primary) dependencies of their box.
- class repomatic.deps.dep_graph.SubgraphKind(*values)[source]¶
Bases:
EnumKind of dependency selector a subgraph box represents.
- GROUP = 'group'¶
- EXTRA = 'extra'¶
- available(project_root=None)[source]¶
Discover this kind’s declared names from
pyproject.toml.Groups come from the
[dependency-groups]table, extras from[project.optional-dependencies].
- class repomatic.deps.dep_graph.Subgraph(kind, name, owned, duplicates)[source]¶
Bases:
objectOne
--groupor--extrabox in the rendered graph.A box only holds the packages its group or extra declares directly: the dependencies under the project’s control, referenced in
pyproject.toml. Transitive dependencies always render outside the boxes, exactly like the transitive dependencies of the primary set.- kind: SubgraphKind¶
Whether the box represents a dependency group or an optional extra.
- duplicates: set[str]¶
Directly-declared packages owned by a sibling box.
Rendered as display-only duplicate nodes tied to the real node by a dotted identity link. See
attribute_subgraph_packages().
- repomatic.deps.dep_graph.MERMAID_RESERVED_KEYWORDS: frozenset[str] = frozenset({'C4Component', 'C4Container', 'C4Deployment', 'C4Dynamic', '_blank', '_parent', '_self', '_top', 'call', 'class', 'classDef', 'click', 'end', 'flowchart', 'flowchart-v2', 'graph', 'interpolate', 'linkStyle', 'style', 'subgraph'})¶
Mermaid keywords that cannot be used as node IDs.
- repomatic.deps.dep_graph.normalize_package_name(name)[source]¶
Normalize package name for use as Mermaid node ID.
Converts to lowercase and replaces non-alphanumeric characters with underscores. Appends
_0suffix to avoid conflicts with Mermaid reserved keywords.- Return type:
- repomatic.deps.dep_graph.resolve_subgraph_selection(kind, explicit, select_all, excluded, only, config_all, config_excluded)[source]¶
Resolve which groups or extras the graph should render.
Mirrors one selection axis of the
update-dep-graphcommand: explicit CLI values win over the[tool.repomatic] dependency-graphdefaults;--only-*replaces the explicit selection;--all-*expands to every name declared inpyproject.toml;--no-*prunes last.- Parameters:
kind (
SubgraphKind) – The axis to resolve, groups or extras.explicit (
tuple[str,...]) – Names selected one by one (--group/--extra).select_all (
bool) – Select every declared name (--all-groups/--all-extras).excluded (
tuple[str,...]) – Names to prune from the selection (--no-group/--no-extra).only (
tuple[str,...]) – Names selected in exclusive mode (--only-group/--only-extra).config_all (
bool) – Configured default for select_all, applied when no selection flag is passed.config_excluded (
Sequence[str]) – Configured default for excluded.
- Return type:
- Returns:
Selected names, or
Nonewhen the axis is not requested at all.
- repomatic.deps.dep_graph.get_cyclonedx_sbom(package=None, groups=None, extras=None, frozen=True)[source]¶
Run uv export and return the CycloneDX SBOM as a dictionary.
Results are cached to avoid redundant subprocess calls within the same process.
- Parameters:
package (
str|None) – Optional package name to focus the export on.groups (
tuple[str,...] |None) – Optional dependency groups to include (e.g., “test”, “typing”).extras (
tuple[str,...] |None) – Optional extras to include (e.g., “xml”, “json5”).frozen (
bool) – If True, use –frozen to skip lock file updates.
- Return type:
- Returns:
Parsed CycloneDX SBOM dictionary.
- Raises:
subprocess.CalledProcessError – If uv command fails.
json.JSONDecodeError – If output is not valid JSON.
- repomatic.deps.dep_graph.get_package_names_from_sbom(sbom)[source]¶
Extract all package names from a CycloneDX SBOM.
- repomatic.deps.dep_graph.build_dependency_graph(sbom)[source]¶
Build a dependency graph from CycloneDX SBOM data.
- Parameters:
- Return type:
- Returns:
Tuple of (root_name, package_names, edges_list) where: - root_name is the root package name - package_names is the set of all package names - edges_list is a list of (from_name, to_name) tuples
- repomatic.deps.dep_graph.filter_root_edges(root_name, edges, main_deps, subgraphs)[source]¶
Drop root edges that no
pyproject.tomldeclaration backs.uv’s CycloneDX export hangs a dependency-group package off the root as soon as that package lands in the resolved component set, whether or not the group was requested. Exporting click-extra with
--extra sphinxand no--groupis enough forrequeststo come back as a direct dependency of the project: Sphinx pulls it in, thetestgroup happens to declare it too, and the export conflates the two. Neither omitting--groupnor passing--no-default-groupssuppresses it.Left in place, such an edge lands the package in the primary dependencies box, labelled with the specifier of a group nobody asked for, claiming the project depends on something a plain install never installs. So the root’s direct dependencies are re-derived from
uv.lock, which records whatpyproject.tomldeclares rather than what resolution happened to produce.Edges into a box-owned package survive:
render_mermaid()turns those into the box’s dashed arrow. Edges that do not start at the root are never touched, so the dropped package keeps rendering as a transitive dependency of whatever actually pulls it in.- Parameters:
root_name (
str) – The root package name.edges (
list[tuple[str,str]]) – List of (from_name, to_name) edge tuples.main_deps (
set[str] |None) – Names the root declares as main dependencies, fromby_main.Nonewhen the lockfile describes no such package, in which case every edge is kept: missing data is not evidence that an edge is spurious.subgraphs (
Sequence[Subgraph]) – Boxes whose owned packages legitimately hang off the root.
- Return type:
- Returns:
The edge list, without the unbacked root edges.
- repomatic.deps.dep_graph.filter_graph_to_package(packages, edges, package)[source]¶
Filter the graph to only include dependencies of a specific package.
- repomatic.deps.dep_graph.trim_graph_to_depth(root_name, packages, edges, depth)[source]¶
Trim the graph to only include nodes within a given depth from the root.
Performs a breadth-first traversal from the root, keeping only nodes reachable within
depthhops and edges between those nodes.- Parameters:
- Return type:
- Returns:
Filtered (packages, edges) tuple.
- repomatic.deps.dep_graph.render_mermaid(root_name, packages, edges, subgraphs=None, lock_specs=None)[source]¶
Render the dependency graph as a Mermaid flowchart.
Warning
Output must stay compatible with the Mermaid version bundled in
sphinxcontrib-mermaid. See module docstring for details.Every box holds only directly-declared dependencies, drawn as hexagons with a thick border; transitive dependencies render outside the boxes as plain ovals. See the module docstring.
- Parameters:
root_name (
str) – The root package name (used to highlight it).edges (
list[tuple[str,str]]) – List of (from_name, to_name) edge tuples.subgraphs (
list[Subgraph] |None) – Boxes to render, in display order (extras before groups keeps them closer to the main dependencies). SeeSubgraph.lock_specs (
LockSpecifiers|None) – Optional specifiers extracted fromuv.lock. Provides edge labels (by_package) and subgraph node labels (by_subgraph).
- Return type:
- Returns:
Mermaid flowchart string.
- repomatic.deps.dep_graph.attribute_subgraph_packages(subgraph_closures, base_packages, direct_packages, edges, root_name)[source]¶
Attribute each directly-declared package to one owning subgraph box.
Boxes only hold the packages their group/extra declares directly; transitive dependencies stay outside every box (see the module docstring). A directly-declared package can still be claimed by several boxes, but a graph node can live in only one: the declarer whose closure holds the most dependents wins the real node (declaration order breaks ties), since arrows point where the package is consumed and the busiest box is its most natural home. The root is not a dependent, as it reaches every declared package by definition.
The losing declarers list the package as a duplicate headline so every box still shows the dependency it exists to install (rendered as a display-only duplicate node by
render_mermaid()). For example thecarapaceandyamlextras both declare onlypyyaml, which no other package depends on: the dependent counts tie at zero,carapaceowns the node by declaration order, andyamlcarriespyyamlas a duplicate.- Parameters:
subgraph_closures (
list[tuple[str,set[str]]]) – Ordered(name, closure_package_names)pairs. Order is the last-resort tie-break for shared packages (first wins).base_packages (
set[str]) – Packages in the base set, excluded from every box.direct_packages (
dict[str,set[str]]) – Map of subgraph name to the package names it declares directly (fromuv.lock), keyed by SBOM-normalized name.edges (
list[tuple[str,str]]) –(from_name, to_name)dependency edges from the full SBOM, used to count each declaring subgraph’s local dependents.root_name (
str) – The root package name, excluded from dependent counts.
- Return type:
- Returns:
(owned, duplicates). owned maps each subgraph to the declared packages it renders as real nodes; duplicates maps it to declared packages owned by a sibling box.
- repomatic.deps.dep_graph.generate_dependency_graph(package=None, groups=None, extras=None, frozen=True, depth=None, exclude_base=False)[source]¶
Generate a Mermaid dependency graph.
Each requested group/extra renders as a box holding only the packages it declares directly; the transitive dependencies they pull in render outside the boxes, like the transitive dependencies of the main set.
- Parameters:
package (
str|None) – Optional package name to focus on. If None, shows the entire project dependency tree.groups (
tuple[str,...] |None) – Optional dependency groups to include (e.g., “test”, “typing”).extras (
tuple[str,...] |None) – Optional extras to include (e.g., “xml”, “json5”).frozen (
bool) – If True, use –frozen to skip lock file updates.depth (
int|None) – Optional maximum depth from root. If None, shows the full tree.exclude_base (
bool) – If True, exclude main (base) dependencies from the graph, showing only packages unique to the requested groups/extras. Used by--only-groupand--only-extra.
- Return type:
- Returns:
The graph in Mermaid format.