Coverage for src/lexigram/admin/clusters/base.py: 0%

19 statements  

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

1"""Cluster dataclass for grouping resources and pages. 

2 

3.. experimental:: 

4""" 

5 

6from __future__ import annotations 

7 

8from dataclasses import dataclass, field 

9from typing import Any 

10 

11 

12@dataclass(frozen=True, kw_only=True) 

13class Cluster: 

14 """A grouping of resources and pages with shared label, icon, and permissions. 

15 

16 ``slug`` is the URL segment under which the cluster center lives 

17 (``/admin/{slug}``); when empty it resolves to ``name``. ``group`` is 

18 the navigation group name its contributors declare; when empty it 

19 resolves to ``name`` as well. Registration-time resolution happens in 

20 :class:`lexigram.admin.clusters.registry.ClusterRegistry`. 

21 

22 Example: 

23 >>> content = Cluster( 

24 ... name="content", 

25 ... slug="content", 

26 ... group="content", 

27 ... label="Content", 

28 ... icon="document", 

29 ... ) 

30 >>> users = Cluster( 

31 ... name="users", 

32 ... label="Users & Access", 

33 ... icon="users", 

34 ... ) 

35 """ 

36 

37 name: str 

38 label: str 

39 icon: str | None = None 

40 order: int = 0 

41 collapsible: bool = True 

42 collapsed_by_default: bool = False 

43 slug: str = "" 

44 group: str = "" 

45 description: str | None = None 

46 

47 # Populated at registration time 

48 resources: list[type[Any]] = field(default_factory=list) 

49 pages: list[type[Any]] = field(default_factory=list) 

50 

51 def __hash__(self) -> int: 

52 """Hash by name (the list fields are unhashable). 

53 

54 Returns: 

55 The hash of the cluster name. 

56 """ 

57 return hash(self.name) 

58 

59 

60__all__ = ["Cluster"]