Textualize/trogon

Custom `ParamType`s cause `tui` commands to crash

Julian opened this issue · 0 comments

Given a simple CLI with a custom click.ParamType:

import click
from trogon import tui


@tui()
@click.group()
def main():
    pass


class _TestSuiteCases(click.ParamType):
    name = "thingy"

    def convert(self, value, param, ctx) -> int:
        return 37


@main.command()
@click.argument("input", type=_TestSuiteCases())
def foo(input):
    pass


main()

Running bar tui starts up a TUI successfully, but moving the cursor on top of the foo subcommand crashes with e.g.:

╭────────────────────────────────────────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ /Users/julian/.dotfiles/.local/share/virtualenvs/bowtie/lib/python3.10/site-packages/textual/widget.py:3100 in _on_compose                                                                                                                                     │
│                                                                                                                                                                                                                                                                │
│   3097 │                                                                                        ╭──────────────────────────────── locals ────────────────────────────────╮                                                                                     │
│   3098 │   async def _on_compose(self) -> None:                                                 │ self = ParameterControls(id='id_192ff4fe', pseudo_classes={'enabled'}) │                                                                                     │
│   3099 │   │   try:                                                                             ╰────────────────────────────────────────────────────────────────────────╯                                                                                     │
│ ❱ 3100 │   │   │   widgets = compose(self)                                                                                                                                                                                                                     │
│   3101 │   │   except TypeError as error:                                                                                                                                                                                                                      │
│   3102 │   │   │   raise TypeError(                                                                                                                                                                                                                            │
│   3103 │   │   │   │   f"{self!r} compose() method returned an invalid result; {error}"                                                                                                                                                                        │
│                                                                                                                                                                                                                                                                │
│ /Users/julian/.dotfiles/.local/share/virtualenvs/bowtie/lib/python3.10/site-packages/textual/_compose.py:26 in compose                                                                                                                                         │
│                                                                                                                                                                                                                                                                │
│   23 │   app._compose_stacks.append(compose_stack)                                            ╭───────────────────────────────────── locals ──────────────────────────────────────╮                                                                            │
│   24 │   app._composed.append(composed)                                                       │           app = Trogon(title='Trogon', classes={'-dark-mode'})                    │                                                                            │
│   25 │   try:                                                                                 │         child = Label(classes={'command-form-label'}, pseudo_classes={'enabled'}) │                                                                            │
│ ❱ 26 │   │   for child in node.compose():                                                     │ compose_stack = []                                                                │                                                                            │
│   27 │   │   │   if composed:                                                                 │      composed = [ControlGroupsContainer(pseudo_classes={'enabled'})]              │                                                                            │
│   28 │   │   │   │   nodes.extend(composed)                                                   │          node = ParameterControls(id='id_192ff4fe', pseudo_classes={'enabled'})   │                                                                            │
│   29 │   │   │   │   composed.clear()                                                         │         nodes = []                                                                │                                                                            │
│                                                                                               ╰───────────────────────────────────────────────────────────────────────────────────╯                                                                            │
│                                                                                                                                                                                                                                                                │
│ /Users/julian/.dotfiles/.local/share/virtualenvs/bowtie/lib/python3.10/site-packages/trogon/widgets/parameter_controls.py:122 in compose                                                                                                                       │
│                                                                                                                                                                                                                                                                │
│   119 │   │   │   │   # We always need to display the original group of controls,                                                                                                                                                                              │
│   120 │   │   │   │   # regardless of whether there are defaults                                                                                                                                                                                               │
│   121 │   │   │   │   if multiple or not default.values:                                                                                                                                                                                                       │
│ ❱ 122 │   │   │   │   │   widget_group = list(self.make_widget_group())                                                                                                                                                                                        │
│   123 │   │   │   │   │   with ControlGroup() as control_group:                                                                                                                                                                                                │
│   124 │   │   │   │   │   │   if len(widget_group) == 1:                                                                                                                                                                                                       │
│   125 │   │   │   │   │   │   │   control_group.add_class("single-item")                                                                                                                                                                                       │
│                                                                                                                                                                                                                                                                │
│ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────── locals ──────────────────────────────────────────────────────────────────────────────────────────────────────────╮                                 │
│ │       argument_type = <__main__._TestSuiteCases object at 0x105dbb520>                                                                                                                                                     │                                 │
│ │             default = MultiValueParamData(values=[])                                                                                                                                                                       │                                 │
│ │ first_focus_control = None                                                                                                                                                                                                 │                                 │
│ │           help_text = ''                                                                                                                                                                                                   │                                 │
│ │           is_option = False                                                                                                                                                                                                │                                 │
│ │               label = <text 'input thingy  *required' [Span(5, 12, 'dim'), Span(14, 15, 'bold red')]>                                                                                                                      │                                 │
│ │            multiple = False                                                                                                                                                                                                │                                 │
│ │                name = 'input'                                                                                                                                                                                              │                                 │
│ │              schema = ArgumentSchema(name='input', type=<__main__._TestSuiteCases object at 0x105dbb520>, required=True, key='id_192ff4fe', default=MultiValueParamData(values=[]), choices=None, multiple=False, nargs=1) │                                 │
│ │                self = ParameterControls(id='id_192ff4fe', pseudo_classes={'enabled'})                                                                                                                                      │                                 │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯                                 │
│                                                                                                                                                                                                                                                                │
│ /Users/julian/.dotfiles/.local/share/virtualenvs/bowtie/lib/python3.10/site-packages/trogon/widgets/parameter_controls.py:172 in make_widget_group                                                                                                             │
│                                                                                                                                                                                                                                                                │
│   169 │   │   # At this point we don't care about filling in the default values.                                                                                                                                                                               │
│   170 │   │   for _type in parameter_types:                                                                                                                                                                                                                    │
│   171 │   │   │   control_method = self.get_control_method(_type)                                                                                                                                                                                              │
│ ❱ 172 │   │   │   control_widgets = control_method(                                                                                                                                                                                                            │
│   173 │   │   │   │   default, label, multiple, schema, schema.key                                                                                                                                                                                             │
│   174 │   │   │   )                                                                                                                                                                                                                                            │
│   175 │   │   │   yield from control_widgets                                                                                                                                                                                                                   │
│                                                                                                                                                                                                                                                                │
│ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────── locals ────────────────────────────────────────────────────────────────────────────────────────────────────────╮                                     │
│ │           _type = <__main__._TestSuiteCases object at 0x105dbb520>                                                                                                                                                     │                                     │
│ │  control_method = None                                                                                                                                                                                                 │                                     │
│ │         default = MultiValueParamData(values=[])                                                                                                                                                                       │                                     │
│ │       is_option = False                                                                                                                                                                                                │                                     │
│ │           label = <text 'input thingy  *required' [Span(5, 12, 'dim'), Span(14, 15, 'bold red')]>                                                                                                                      │                                     │
│ │        multiple = False                                                                                                                                                                                                │                                     │
│ │            name = 'input'                                                                                                                                                                                              │                                     │
│ │  parameter_type = <__main__._TestSuiteCases object at 0x105dbb520>                                                                                                                                                     │                                     │
│ │ parameter_types = [<__main__._TestSuiteCases object at 0x105dbb520>]                                                                                                                                                   │                                     │
│ │        required = True                                                                                                                                                                                                 │                                     │
│ │          schema = ArgumentSchema(name='input', type=<__main__._TestSuiteCases object at 0x105dbb520>, required=True, key='id_192ff4fe', default=MultiValueParamData(values=[]), choices=None, multiple=False, nargs=1) │                                     │
│ │            self = ParameterControls(id='id_192ff4fe', pseudo_classes={'enabled'})                                                                                                                                      │                                     │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯                                     │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
TypeError: 'NoneType' object is not callable

The above exception was the direct cause of the following exception:

╭────────────────────────────────────────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ /Users/julian/.dotfiles/.local/share/virtualenvs/bowtie/lib/python3.10/site-packages/textual/widget.py:3102 in _on_compose                                                                                                                                     │
│                                                                                                                                                                                                                                                                │
│   3099 │   │   try:                                                                             ╭──────────────────────────────── locals ────────────────────────────────╮                                                                                     │
│   3100 │   │   │   widgets = compose(self)                                                      │ self = ParameterControls(id='id_192ff4fe', pseudo_classes={'enabled'}) │                                                                                     │
│   3101 │   │   except TypeError as error:                                                       ╰────────────────────────────────────────────────────────────────────────╯                                                                                     │
│ ❱ 3102 │   │   │   raise TypeError(                                                                                                                                                                                                                            │
│   3103 │   │   │   │   f"{self!r} compose() method returned an invalid result; {error}"                                                                                                                                                                        │
│   3104 │   │   │   ) from error                                                                                                                                                                                                                                │
│   3105 │   │   except Exception:                                                                                                                                                                                                                               │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
TypeError: ParameterControls(id='id_192ff4fe', pseudo_classes={'enabled'}) compose() method returned an invalid result; 'NoneType' object is not callable

(This looks quite nice though! Well done. Certainly looking forward to integrating it into an app or two.)