File: group.py

package info (click to toggle)
python-cyclopts 3.12.0-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 3,288 kB
  • sloc: python: 11,445; makefile: 24
file content (144 lines) | stat: -rw-r--r-- 4,609 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import itertools
from collections.abc import Iterable
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Optional,
    Union,
    cast,
)

from attrs import field

from cyclopts.utils import UNSET, SortHelper, frozen, is_iterable, resolve_callables, to_tuple_converter

if TYPE_CHECKING:
    from cyclopts.argument import ArgumentCollection
    from cyclopts.parameter import Parameter


def _group_default_parameter_must_be_none(instance, attribute, value: Optional["Parameter"]):
    if value is None:
        return

    if value.group:
        raise ValueError("Group default_parameter cannot have a group.")


# Used for Group.sorted
_sort_key_counter = itertools.count()


@frozen
class Group:
    name: str = ""

    help: str = ""

    # All below parameters are keyword-only
    _show: Optional[bool] = field(default=None, alias="show", kw_only=True)

    _sort_key: Any = field(
        default=None,
        alias="sort_key",
        converter=lambda x: UNSET if x is None else x,
        kw_only=True,
    )

    # This can ONLY ever be a Tuple[Callable, ...]
    validator: Union[None, Callable[["ArgumentCollection"], Any], Iterable[Callable[["ArgumentCollection"], Any]]] = (
        field(
            default=None,
            converter=lambda x: cast(tuple[Callable, ...], to_tuple_converter(x)),
            kw_only=True,
        )
    )

    default_parameter: Optional["Parameter"] = field(
        default=None,
        validator=_group_default_parameter_must_be_none,
        kw_only=True,
    )

    @property
    def show(self):
        return bool(self.name) if self._show is None else self._show

    @property
    def sort_key(self):
        return None if self._sort_key is UNSET else self._sort_key

    @classmethod
    def create_default_arguments(cls):
        return cls("Arguments")

    @classmethod
    def create_default_parameters(cls):
        return cls("Parameters")

    @classmethod
    def create_default_commands(cls):
        return cls("Commands")

    @classmethod
    def create_ordered(cls, name="", help="", *, show=None, sort_key=None, validator=None, default_parameter=None):
        """Create a group with a globally incrementing :attr:`~Group.sort_key`.

        Used to create a group that will be displayed **after** a previously instantiated :meth:`Group.create_ordered` group on the help-page.

        Parameters
        ----------
        name: str
            Group name used for the help-page and for group-referenced-by-string.
            This is a title, so the first character should be capitalized.
            If a name is not specified, it will not be shown on the help-page.
        help: str
            Additional documentation shown on the help-page.
            This will be displayed inside the group's panel, above the parameters/commands.
        show: Optional[bool]
            Show this group on the help-page.
            Defaults to :obj:`None`, which will only show the group if a ``name`` is provided.
        sort_key: Any
            If provided, **prepended** to the globally incremented counter value (i.e. has priority during sorting).

        validator: Union[None, Callable[["ArgumentCollection"], Any], Iterable[Callable[["ArgumentCollection"], Any]]]
            Group validator to collectively apply.
        default_parameter: Optional[cyclopts.Parameter]
            Default parameter for elements within the group.
        """
        count = next(_sort_key_counter)
        if sort_key is None:
            sort_key = (UNSET, count)
        elif is_iterable(sort_key):
            sort_key = (tuple(sort_key), count)
        else:
            sort_key = (sort_key, count)
        return cls(
            name,
            help,
            show=show,
            sort_key=sort_key,
            validator=validator,
            default_parameter=default_parameter,
        )


def sort_groups(groups: list[Group], attributes: list[Any]) -> tuple[list[Group], list[Any]]:
    """Sort groups for the help-page.

    Note, much logic is similar to here and ``HelpPanel.sort``, so any changes here should probably be reflected over there as well.
    """
    assert len(groups) == len(attributes)

    if not groups:
        return groups, attributes

    sorted_entries = SortHelper.sort(
        [
            SortHelper(resolve_callables(group._sort_key, group), group.name, (group, attribute))
            for group, attribute in zip(groups, attributes)
        ]
    )
    out_groups, out_attributes = zip(*[x.value for x in sorted_entries])
    return list(out_groups), list(out_attributes)