magmax / python-inquirer

A collection of common interactive command line user interfaces, based on Inquirer.js (https://github.com/SBoudrias/Inquirer.js/)
MIT License
982 stars 98 forks source link

Whenever "choices" argument of List is a function, the terminal gets very buggy. #550

Closed vitorstone closed 2 months ago

vitorstone commented 3 months ago

Let's say I have this function:

def get_bitbucket_project_choices(answers):
    print("hey")
    return ['option 1', 'option2', 'option3']

Case 1) Calling the function so that choices is essentially a list

questions = [
        inquirer.List(
            name='bitbucket_project',
            message='Select the Bitbucket Project',
            choices=get_bitbucket_project_choices({}),
)]

Result: The terminal prompts me for the options correctly, and it's very smooth. Also, "hey" is printed only once, and as I move my cursor up an down, I get a smooth behavior. "hey" is never printed again.

Case 2) I pass the actual function to choices, without calling it

questions = [
        inquirer.List(
            name='bitbucket_project',
            message='Select the Bitbucket Project',
            choices=get_bitbucket_project_choices,
)]

Expected: I would expect the function to behave just like before. Actual: It prints "hey" multiple times, meaning that it actually calls the function again and again every time I press the up and down arrow keys. It also prints the question multiple times. The terminal looks very buggy.

Cube707 commented 3 months ago

Providing a callable as the choises argument means it gets used to hook the realtime inout of the user and provide feedback. This is for example usefull if you want to validate input or modify the choices based on selection in real time. This is expected behaviour.

The terminal gets messed up because your callable prints stuff, so inquire doesn't know of the additional hey line and messes up when trying to clear the previous lines.

Sidenote: You function-name implys it is an expensive call that querrys a web-api, this should never be passed in as a callback.


May I ask what made you expect this to work? Why would you want to use only the callback and not call your function yourself?

vitorstone commented 3 months ago

Hi, thank you for replying!

My goal, amongst others, was to ask for the user to select the bitbucket project (and yes, it would query bitbucket), then to ask him again to select a bitbucket repository (it would query again based on the project he selected). That's why I didn't want to call the function myself (With that, I believe it answers your last question).

Otherwise, if I had to call the function myself, I would need to break it down into multiple inquirer.prompt statements, and chain the answer from the previous question into the function to calculate the list to the next question; which would work too, but I believe that's why the callback functionality exists in the first place. To make it simpler.

Also, I only put a print("hey") just to demonstrate that the function was being called multiple times. I also removed the body of my function and put a dummy list there just to simplify as well. The terminal still misbehaves without this print statement. By misbehave I mean:

I understand that passing a function to "choices" let's us modify the list returned based on answers from previous questions. Which is exactly what I want, and I like that.

However, I still don't understand why inquirer would need to execute this function every time I press the down or up arrow, within the same question. I was hoping that it would only call it once (right before asking a specific question) then it would load the list, then the user would choose from the loaded values. That's why I think it's a bug. Why is the function called each time I press the arrow key? That doesn't make sense to me.

Cube707 commented 3 months ago

I believe that's why the callback functionality exists in the first place.

No, you misunderstand what a callback is meant for. You can use it to hook into the users interaction. It allows you to execute your code each time the user interacts with the prompt, but before the user hits enter. (best example is real-time validation)

Otherwise, if I had to call the function myself, I would need to break it down into multiple inquirer.prompt statements

Yes this is exactly what you have to do for the case you describe.

That's why I didn't want to call the function myself

why? Two functions for to different API-querrys are more readable anyway.

  • pressing up and down arrow keys seem way slower

thats why callbacks need to be quick. having a expensive callback function results in slow behavior...

  • it weirdly prints '[[[[[[[[' at some point whenever I'm pressing the down arrow key, and the chars are not rendered properly.

thats strange and shouldn't happen. I will have a look, but without a proper MWE I am not sure I will be able to reproduce it.


However, I still don't understand why inquirer would need to execute this function every time I press the down or up arrow, within the same question.

It is true that we could probably optimise this a little. It is probably not needed to execute the callback on every Arrow-key. But every interaction (selection, deselection, enter, etc) will trigger the callback.

Cube707 commented 3 months ago

but maybe this helps:

import inquirer

def API_querry(list):
    return list

projects = inquirer.checkbox("select Project", choices=API_querry(["A", "B", "C"]))
repos = inquirer.checkbox("select Project", choices=API_querry(projects))

All question types have a short-version that constructs an object and prompts for it. Building your own list is more useful if you have multiple static questions