Coverage for src / lexigram / contracts / admin / dependencies.py: 0%
31 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-15 18:57 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-15 18:57 +0800
1"""Dependency metadata and topological sort for admin contributors."""
3from __future__ import annotations
5from collections.abc import Iterable, Sequence
6from typing import Protocol, TypeVar
9class ContributorDependencyError(ValueError):
10 """Contributor dependency graph is invalid."""
13class ContributorWithDependenciesProtocol(Protocol):
14 name: str
15 depends_on: tuple[str, ...]
18T = TypeVar("T", bound=ContributorWithDependenciesProtocol)
21def sort_contributors(contributors: Iterable[T]) -> list[T]:
22 by_name = {c.name: c for c in contributors}
23 visiting: set[str] = set()
24 visited: set[str] = set()
25 ordered: list[T] = []
27 def visit(name: str, trail: Sequence[str]) -> None:
28 if name in visited:
29 return
30 if name in visiting:
31 raise ContributorDependencyError(
32 "contributor dependency cycle: " + " -> ".join([*trail, name])
33 )
34 if name not in by_name:
35 raise ContributorDependencyError(f"missing dependency: {name}")
36 visiting.add(name)
37 contributor = by_name[name]
38 for dep in contributor.depends_on:
39 visit(dep, [*trail, name])
40 visiting.remove(name)
41 visited.add(name)
42 ordered.append(contributor)
44 for contributor_name in by_name:
45 visit(contributor_name, [])
46 return ordered
49__all__ = [
50 "ContributorDependencyError",
51 "ContributorWithDependenciesProtocol",
52 "sort_contributors",
53]