I'm using the click library for a CLI application. I have various options that the user can specify, and most of them have prompts turned on. However, even if an option isn't required, if you set click to prompt for the option, it won't accept an empty response (like just hitting enter). For example:
#click.option('-n', '--name', required=True, prompt=True)
#click.option('-d', '--description', required=False, prompt=True)
>>> myscript -n Joe
>>> Description: [Enter pressed]
>>> Description: [Enter pressed; click doesn't accept an empty parameter]
Is there a way to get around this, or would this require a feature request?
when you add default="" then an empty string is also accepted:
#click.option('-d', '--description', prompt=True, default="")
Note that required is not a possible argument for option, at least according to the docs
Related
I'm using the Click to build a command-line application. For audit purposes, need to get access full original command that the user executed. I had no luck getting the original user command before the Click parsing arguments. I couldn't find any similar use case in their documentation. something like :
#click.group()
#click.option('--debug/--no-debug', default=False)
#click.pass_context
def cli(ctx, debug):
if debug:
# print('Original unparssed command with arguments')
I wanted to check if anyone knows a trick to get that before opening an issue on the github.
I want to enhance an existing command-line application done with Python click to allow for certain options depending on what the --format option is set to.
In my special case, I would like to enable --delimiter option when the --format option equals csv and an --indent option when the --format option equals json. It should all fit into one command, so I do not want to introduce subcommands if not absolutely necessary.
I looked into the group mechanism of click and also the additional extension package click-option-group but I think this is only about grouping the commands, but does not fulfill the goal above.
This question is similar: python-click: dependent options on another option, but the difference is that it has predefined values for the "sub option", whereas in my case --indent is a user-specified value, e.g. 2, 4, 1, et cetera...
Furthermore, as all the answers that go into my direction are rather old, I would look for a more up-to-date answer, as to what is possible as of today. What also would be great if it were possible to use functionality of a Python library with preference on click instead of having to add additional code outside of the library.
Thanks for your help.
You can use the cloup library like this:
import cloup
from cloup.constraints import (
If, require_one, Equal
)
#cloup.command(show_constraints=True)
#click.option('-f', '--format', required=True,
type=click.Choice(['csv', 'json']))
#click.option('-d', '--delimiter', required=False, type=click.Choice(['\t', ', ']))
#click.option('-i', '--indent', required=False, type=int)
#cloup.constraint(If(Equal('format', 'csv'), then=require_one.hidden()), ['delimiter'])
#cloup.constraint(If(Equal('format', 'json'), then=require_one.hidden()), ['indent'])
def formatter(format, delimiter, indent):
pass
You can use a callback function like this:
def fix_required(ctx, param, value):
if value == 'csv':
ctx.command.params[1].required = True
else:
ctx.command.params[2].required = True
#click.command()
#click.option('-f', '--format', required=True,
type=click.Choice(['csv', 'json']), callback=fix_required)
#click.option('-i', '--delimiter', required=False, type=click.Choice(['\t', ', ']))
#click.option('-i', '--indent', required=False, type=int)
def formatter(format, delimiter, indent):
pass
The params list is ordered after the order of the option annotations, so it is predictable and should be kept up to date if you add options to your command or edit their order.
I'm developing a simple project with the purpose or learning Python, actually I have version 3.6 and I wanted to build a command line tool to generate password with specific criteria. So fare here is what I got:
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-a", "-auto", help="auto mode", action="store_true")
group.add_argument("-m", "-manual", help="manual mode", action="store_true")
parser.parse_args()
the dead end is I have no idea how to limit a command, example -l -lenght to the reqisite of having -m another stop is, how do I define -l so that it can accept a value, for example -l:8 to specify a password of 8 characters, I also want to put a range limit on -l values, example -l:8-256 and finally, I dont understand the difference between - and -- when defining the parameters.
I have already done all the part of generating passwords, just need a way to control its flow from outside so implementing parameters looked like a good way of doing this.
What you are looking for is the choices option.
add_argument('--length', type=int, choices=range(8,257)
This will only accept integer values between 8 and 256 (inclusive).
As for your second question, - indicates the shorthand for an option, which is common in most CLI tools, well -- indicates the long hand. It is common practice is CLI tools to provide both a long and a short hand for options.
You can define a custom type to check for valid values:
from argparse import ArgumentTypeError
def passwd_len(s):
try:
s = int(s)
except ValueError:
raise ArgumentTypeError("'%s' is not an integer" % (s,))
if not (8 <= s <= 256):
raise ArgumentTypeError("%d is not between 8 and 256, inclusive" % (s,))
return s
parser.add_argument("--length", "-l", type=passwd_len)
The difference between -- and - is one of conventions. Historically, options were single-letter arguments prefixed with a -, and groups of options could be combined: -ab was the same as -a -b. In order to
support that convention and allow longer option names, a new convention
of using -- to prefix multi-character names was devised. --length is a single option, not a group of six options -l, -e, -n, -g, -t, and -h. Long options cannot be grouped.
I would define the -a and -m options like this:
group = parser.add_mutually_exclusive_group()
group.add_argument("-a", "--auto", help="auto mode", action="store_const", const="auto", dest="mode")
group.add_argument("-m", "--manual", help="manual mode", action="store_const", const="manual", dest="mode")
group.add_argument("--mode", choices=["auto", "manual"])
Now instead of having two Boolean attributes that can never have the same value, you have just one attribute whose value you can check directly. Think of --mode as being the canonical way of choosing a mode, with -a and -m as shortcuts for selecting a specific mode.
I am trying to debug a script which takes command line arguments as an input. Arguments are text files in the same directory. Script gets file names from sys.argv list. My problem is I cannot launch the script with arguments in pycharm.
I have tried to enter arguments into "Script parameters" field in "Run" > "Edit configuration" menu like so:
-s'file1.txt', -s'file2.txt'
But it did not work. How do I launch my script with arguments?
P.S. I am on Ubuntu
In PyCharm the parameters are added in the Script Parameters as you did but, they are enclosed in double quotes "" and without specifying the Interpreter flags like -s. Those flags are specified in the Interpreter options box.
Script Parameters box contents:
"file1.txt" "file2.txt"
Interpeter flags:
-s
Or, visually:
Then, with a simple test file to evaluate:
if __name__ == "__main__":
import sys
print(sys.argv)
We get the parameters we provided (with sys.argv[0] holding the script name of course):
['/Path/to/current/folder/test.py', 'file1.txt', 'file2.txt']
For the sake of others who are wondering on how to get to this window. Here's how:
You can access this by clicking on Select Run/Debug Configurations (to the left of ) and going to the Edit Configurations. A
gif provided for clarity.
On PyCharm Community or Professional Edition 2019.1+ :
From the menu bar click Run -> Edit Configurations
Add your arguments in the Parameters textbox (for example file2.txt file3.txt, or --myFlag myArg --anotherFlag mySecondArg)
Click Apply
Click OK
In addition to Jim's answer (sorry not enough rep points to make a comment), just wanted to point out that the arguments specified in PyCharm do not have special characters escaped, unlike what you would do on the command line. So, whereas on the command line you'd do:
python mediadb.py /media/paul/New\ Volume/Users/paul/Documents/spinmaster/\*.png
the PyCharm parameter would be:
"/media/paul/New Volume/Users/paul/Documents/spinmaster/*.png"
Notice that for some unknown reason, it is not possible to add command line arguments in the PyCharm Edu version. It can be only done in Professional and Community editions.
Add the following to the top of your Python file.
import sys
sys.argv = [
__file__,
'arg1',
'arg2'
]
Now, you can simply right click on the Python script.
The first parameter is the name of the script you want to run. From the second parameter onwards it is the the parameters that you want to pass from your command line. Below is a test script:
from sys import argv
script, first, second = argv
print "Script is ",script
print "first is ",first
print "second is ",second
from sys import argv
script, first, second = argv
print "Script is ",script
print "first is ",first
print "second is ",second
And here is how you pass the input parameters :
'Path to your script','First Parameter','Second Parameter'
Lets say that the Path to your script is /home/my_folder/test.py, the output will be like :
Script is /home/my_folder/test.py
first is First Parameter
second is Second Parameter
It took me some time to figure out that input parameters are comma separated.
I believe it's included even in Edu version. Just right click the solid green arrow button (Run) and choose "Add parameters".
It works in the edu version for me. It was not necessary for me to specify a -s option in the interpreter options.
In edit configuration of PyCharm when you are giving your arguments as string, you should not use '' (these quotations) for giving your input.
Instead of -s'file1.txt', -s'file2.txt'
simply use:
-s file1.txt, -s file2.txt
you can used -argName"argValue" like -d"rd-demo" to add Pycharm arguments
-d"rd-demo" -u"estate"
Arguments added in Parameters Section after selected edit Configuration from IDE
I'm using argparse, and in order to debug my scripts I also using Edit Configuration. For example below the scripts gets 3 parameters (Path, Set1, N) and an optional parameter (flag):
'Path' and 'Set1' from type str.
'N' from type int.
The optional parameter 'flag' from type boolean.
impor argparse
parser = argparse.ArgumentParser(prog='main.py')
parser.add_argument("Path", metavar="path", type=str)
parser.add_argument("Set1", type=str, help="The dataset's name.")
parser.add_argument("N", type=int, help="Number of images.")
parser.add_argument("--flag", action='store_true')
params = parser.parse_args()
In order to to run this in a debug or not by using command line, all needed is:
bar menu Run-->Edit Configuration
Define the Name for your debug/run script.
Set the parameters section. For the above example enter as follow:
The defualt 3 parameters must me included --> "c:\mypath" "name" 50
For the optional parameter --> "c:\mypath" "name" 50 "--flag"
parameter section
(The title should be OptionParser: show extra help, but the word help is not allowed in the title)
I have this OptionParser:
parser = OptionParser(
usage='usage: %s [options]' % (args[0]),
version='%s Version %s' % (args[0], version))
parser.add_option('-a', '--action', choices=['menu'] , help='Allowed actions are: menu, and any menu action', default='menu')
parser.add_option('-1', '--file1', type='string', help='First file')
parser.add_option('-2', '--file2', type='string', help='Second file')
parser.add_option('--debug', action='store_true', help='run in debug mode')
Calling with --help gives me:
Usage: /home/gonvaled/projects/bin/process_json.py [options]
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-a ACTION, --action=ACTION
Allowed actions are: menu, and any menu action
-1 FILE1, --file1=FILE1
First file
-2 FILE2, --file2=FILE2
Second file
--debug run in debug mode
This is fine, but there are missing actions. The problem is that some actions are defined in a Menu object (my implementation), and can only be shown by doing:
menu.showCommands()
That is, those actions are not defined for OptionParser, but are still accessible via the command line, since any action other than menu will be passed transparently to the Menu object to be processed. Thus, the help provided by OptionParser is not aware of those actions.
How can I tell the OptionParser object that when showing the help text, some external piece of information must be added? Appending at the end would be enough. I have taken a look at the docs, but no such option seems available.
EDIT
Actually running my program has shown that not only the help is missing. Even worse: optparse complains about unknown actions:
gonvaled#pegasus ~ ยป process_json.py -a prettyfy-from-obj
Usage: /home/gonvaled/projects/bin/process_json.py [options]
process_json.py: error: option -a: invalid choice: 'prettyfy-from-obj' (choose from 'menu')
How can I tell optparse to accept unspecified values for action?
The list you send to the choices parameter must be an exhaustive list of all possible parameters that the option can allow. If the list is ['menu'], then the only value allowed will be menu. That explains the errors you were getting.
Note: the optparse module is deprecated, you should use argparse
The simpler solution
Don't use choices and validate the argument afterwards.
parser.add_option('-a', '--action', help='Allowed actions are: menu, and any menu action', default='menu')
#...
options, args = parser.parse_args()
if options.action not in menu.showCommands():
parser.error('Invalid action specified: %s' % options.action)
Alternatively, if you can't do that, if you use the action calling menu.call(action, params) and it raises a UnavailableAction exception:
parser.add_option('-a', '--action', help='Allowed actions are: menu, and any menu action', default='menu')
#...
options, args = parser.parse_args()
# do something as normal
try:
menu.call(options.action, params)
except UnavailableAction:
print "Incorrect option" # (1)
# crash appropriately
If the parser object is available, you can change (1) for the following:
parser.error('Invalid action specified: %s' % options.action)
NOTE: this completely defeats the purpose of using choices, but can be applicable if you can not do the validation in the scope of the function/method/module that parses the CLI options.
Any other solution to this issue depend on your ability to introspect the menu object to get the list of available actions.
Using choices
Assuming that menu.showCommands just provides a list of strings of the available commands, my first guess would be to do something like:
menu_choices = menu.showCommands().split()
menu_choices.append('menu')
parser.add_option('-a', '--action', choices=menu_choices , help='Allowed actions are: menu, and any menu action', default='menu')
If there is no way to get the list of actions in that format, you may have to "parse" the output of menu.showCommands
Using callback
You could also implement it as a callback for the -a option:
def menu_option(option, opt_str, value, parser):
if not value not in menu.showCommands().split():
raise OptionValueError('Invalid action for menu')
parser.add_option('-a', '--action', action='callback', callback=menu_option, help='Allowed actions are: menu, and any menu action', default='menu')
This option is a little more flexible. For example, if you use a function called has_action(menu, string) to check if the menuobject has a string action available, you could do:
def menu_option(option, opt_str, value, parser):
if not has_action(menu, value):
raise OptionValueError('Invalid action specified for menu: %s' % value)
More complicated solutions
There are probably a lot more complicated solutions. Some ideas (that may or - more likely - not work) are:
subclassing OptionParser and create your own that has what you need (so that choices has no effect)
if menu has its own OptionParser, chain-load it using callbacks
if menu has its own sub parser, have argparse use it directly