5

The snippet below prompts for the user and password, and defaults to env variables.

Now while the password input is well hidden while typing, I'd like also to have the default between brackets hidden as well, so far if I enter this, the password default is in clear 1234:

➜  export PASSWORD=1234
➜  python test.py
➜  User [myuser]: you can see here
➜  Password [1234]: 
user you can see here
password you cant see


import os
import click

@click.command()
@click.option('--user', prompt=True, default=lambda: os.environ.get('USER', ''))
@click.option('--password', prompt=True, default=lambda: os.environ.get('PASSWORD', ''), hide_input=True)
def test(user, password):
    print('user {}'.format(user))
    print('password {}'.format(password))
    print(password)


if __name__ == '__main__':
    test()

2 Answers 2

10

You could make a class whose string representation hides the password:

class HiddenPassword(object):
    def __init__(self, password=''):
        self.password = password
    def __str__(self):
        return '*' * len(self.password)

Then in your code you'd just have to check whether this class was used and update the password:

@click.command()
@click.option('--user',
              prompt=True,
              default=lambda: os.environ.get('USER', ''))
@click.option('--password',
              prompt=True,
              default=lambda: HiddenPassword(os.environ.get('PASSWORD', '')),
              hide_input=True)
def test(user, password):
    print('user: {}'.format(user))
    if isinstance(password, HiddenPassword):
        password = password.password
    print('password: {}'.format(password))

In action:

$ PASSWORD=foobar python test.py
User [mVChr]:
Password [******]:
user: mVChr
password: foobar
$ PASSWORD=foobar python test.py
User [mVChr]: dan
Password [******]:
user: dan
password: user-entered-pw
Sign up to request clarification or add additional context in comments.

4 Comments

that works well, I just changed to return '*' * 4 so that no info is inferable
Good idea, I was being too cutesy. You could even have it return 'env $PASSWORD' or something.
Instead of lambda, use @click.option('--password', prompt=True, hide_input=True, envvar="PASSWORD")
Unfortunately it does not work for me with click 8.1.3 because I always get a str and not a HiddenPassword instance.
3

Could not find a better solution, but maybe this will do. You can use callback for validation to check the input value and replace it to environment value if no input was provided.

def callback(ctx, param, value):
  if not value:
    return os.environ.get('PASSWORD', '')
  return value
...
@click.option('--password', prompt=True, callback=callback, default='', hide_input=True)

3 Comments

I like the solution of @mVChr a little more, the callback works well, I just prefer to display stars, suggesting the user that something is already entered
No problem, I like his solution as well :)
And I like yours. :) Always a fan of less custom code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.