Custom `ParamType`s cause `tui` commands to crash
Julian opened this issue · 0 comments
Julian commented
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.)