Coverage for src/lexigram/admin/clusters/base.py: 95%
19 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:56 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:56 +0800
1"""Cluster dataclass for grouping resources and pages.
3.. experimental::
4"""
6from __future__ import annotations
8from dataclasses import dataclass, field
9from typing import Any
12@dataclass(frozen=True, kw_only=True)
13class Cluster:
14 """A grouping of resources and pages with shared label, icon, and permissions.
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`.
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 """
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
47 # Populated at registration time
48 resources: list[type[Any]] = field(default_factory=list)
49 pages: list[type[Any]] = field(default_factory=list)
51 def __hash__(self) -> int:
52 """Hash by name (the list fields are unhashable).
54 Returns:
55 The hash of the cluster name.
56 """
57 return hash(self.name)
60__all__ = ["Cluster"]