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

1"""Dependency metadata and topological sort for admin contributors.""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Iterable, Sequence 

6from typing import Protocol, TypeVar 

7 

8 

9class ContributorDependencyError(ValueError): 

10 """Contributor dependency graph is invalid.""" 

11 

12 

13class ContributorWithDependenciesProtocol(Protocol): 

14 name: str 

15 depends_on: tuple[str, ...] 

16 

17 

18T = TypeVar("T", bound=ContributorWithDependenciesProtocol) 

19 

20 

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] = [] 

26 

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) 

43 

44 for contributor_name in by_name: 

45 visit(contributor_name, []) 

46 return ordered 

47 

48 

49__all__ = [ 

50 "ContributorDependencyError", 

51 "ContributorWithDependenciesProtocol", 

52 "sort_contributors", 

53]