Coverage for src/lexigram/admin/navigation/assembler.py: 0%

42 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-24 23:18 +0800

1"""Navigation assembler — builds unified sidebar from contributors and resources.""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Sequence 

6from typing import Any 

7 

8from lexigram.admin.contributors.registry import ContributorRegistry 

9from lexigram.contracts.admin.types import NavigationContribution 

10 

11 

12class NavigationAssembler: 

13 """Merges navigation from contributors and resources into grouped sidebar. 

14 

15 Groups are keyed by the ``group`` field of ``NavigationContribution``. 

16 Items within each group are sorted by ``order``. 

17 """ 

18 

19 def __init__( 

20 self, 

21 contributor_registry: ContributorRegistry, 

22 resource_items: Sequence[NavigationContribution] | None = None, 

23 ) -> None: 

24 self._registry = contributor_registry 

25 self._resource_items = list(resource_items or []) 

26 

27 async def build(self) -> dict[str, list[NavigationContribution]]: 

28 """Build grouped navigation from all sources. 

29 

30 Returns a dict keyed by group name, with items sorted by order. 

31 """ 

32 groups: dict[str, list[NavigationContribution]] = {} 

33 

34 for item in self._resource_items: 

35 groups.setdefault(item.group, []).append(item) 

36 

37 for contributor in self._registry.get_all(): 

38 for item in contributor.get_navigation_items(): 

39 groups.setdefault(item.group, []).append(item) 

40 

41 for group_name, items in groups.items(): 

42 groups[group_name] = sorted(items, key=lambda n: n.order) 

43 

44 return groups 

45 

46 

47def contributions_to_flat_nav( 

48 groups: dict[str, list[NavigationContribution]], 

49) -> list[dict[str, Any]]: 

50 """Convert grouped NavigationContribution objects to AdminShell-compatible flat dicts. 

51 

52 Args: 

53 groups: Output from ``NavigationAssembler.build()`` — dict keyed by group name. 

54 

55 Returns: 

56 Flat list of dicts suitable for ``AdminShell._prepare_navigation()``. 

57 Group headers have ``is_group=True``. Items include label, href, icon, 

58 and optionally permission and badge. 

59 """ 

60 result: list[dict[str, Any]] = [] 

61 

62 top_items: list[dict[str, Any]] = [] 

63 grouped: dict[str, list[dict[str, Any]]] = {} 

64 

65 for group_name, items in groups.items(): 

66 if not items: 

67 continue 

68 for item in items: 

69 nav_dict: dict[str, Any] = { 

70 "label": item.label, 

71 "href": item.url, 

72 "icon": item.icon, 

73 } 

74 if item.permission: 

75 nav_dict["permission"] = item.permission 

76 if item.badge_endpoint: 

77 nav_dict["badge"] = item.badge_endpoint 

78 

79 if not group_name: 

80 top_items.append(nav_dict) 

81 else: 

82 grouped.setdefault(group_name, []).append(nav_dict) 

83 

84 result.extend(top_items) 

85 

86 for group_name in sorted(grouped.keys()): 

87 group_items = grouped[group_name] 

88 result.append({"is_group": True, "label": group_name.replace("_", " ").title()}) 

89 result.extend(group_items) 

90 

91 return result 

92 

93 

94__all__ = ["NavigationAssembler", "contributions_to_flat_nav"]