Using click.command to make a function as a command - python

I am trying to make the function log into a command using the following code inside simple.py:
import click
#click.command()
#click.option('-v', '--verbose', count=True)
def log(verbose):
click.echo(f"Verbosity: {verbose}")
When I type the following on the command terminal:log -vvv , I get an error as : Command 'log' not found, but there are 16 similar ones.
#click.command should have converted the function log into a command? But, it doesn't work here. Could someone explain,please? Thanks!
I have tried the following commands:
log -vvv
Command 'log' not found, but there are 16 similar ones.
python3 simple.py log
Usage: simple.py [OPTIONS]
Try 'simple.py --help' for help.
Error: Got unexpected extra argument (log)
Could someone please explain what does #click.command() actually do and how's it different from running simple.py. The documentation does not make it very clear to me as well. Thanks!

import click
#click.command()
#click.option('-v', '--verbose', count=True)
def log(verbose):
click.echo(f"Verbosity: {verbose}")
if __name__ == '__main__':
log()
Then calling it like
$ python simple.py
Verbosity: 0
$ python simple.py -v
Verbosity: 1
The way you try to run it, suggest you think about command group, i.e. nesting commands
import click
#click.group()
def cli():
pass
#cli.command('log')
#click.option('-v', '--verbose', count=True)
def log(verbose):
click.echo(f"Verbosity: {verbose}")
#cli.command('greet')
def greet():
click.echo("Hello")
if __name__ == '__main__':
cli()
Then
$ python simple.py greet
Hello
$ python simple.py log -v -v # or just -vv
Verbosity: 2
Next step would be setuptools integration, etc.

Related

Why is the input string read by OptParser?

I am currently creating a launcher (launcher.py) in Python 2.7 which can be used from GUI and terminal input. The launcher combines already eisting Software, that has its own GUI and its own terminal mode.
I added an OptionParser (option.py) to start the embedded tools (app.py) directly from the launcher.
App.py executes and is generating correct results, BUT terminal always throws following error:
Usage: launcher.py [options] arg1 arg2
launcher.py: error: no such option -i
here is the option.py file: (is imported as module to launcher.py)
def execute():
tapp_object = tapp.TALP()
input_file = sys.argv[sys.argv.index('-input') + 1]
tapp_object.run(gui=False, filename=input_file)
class Options(OptionParser):
usage = "Usage: %prog arg1 "
parser = OptionParser(usage=usage)
def tapp_batch(option, opt, value, parser):
p = mp.Process(target=execute)
p.start()
p.join()
parser.add_option('--tapp',
'--tAPPLICATION',
action="callback",
callback=tapp_batch,
help='''start APP from batch mode using:
-launcher.py --tapp -input filename.yml
add filename.yml file containing''' )
i tried to do an dirty fix, calling a 'pass' function for flag -i but then it throws the same error for -n -->
Usage: launcher.py [options] arg1 arg2
launcher.py: error: no such option -n
reading full -input string. Why ? and how do I fix it. Please help.

Click unable to register group command

I am trying to run a click cli through a bash with a different command groups run through a single command collection.
src/preprocessing_extract_data/scripts/main.py
import click
#click.group()
def run_preprocessing_extract_data():
pass
#run_preprocessing_extract_data.command()
#click.option(
"--start_date",
type=click.DateTime(formats=["%Y-%m-%d"]),
required=True,
help="Start date for the pipeline",
)
#click.option(
"--end_date",
type=click.DateTime(formats=["%Y-%m-%d"]),
required=True,
help="End date for the pipeline",
)
def main(start_date, end_date):
...
if __name__ == "__main__":
main()
src/scripts/main.py
from click import CommandCollection
from src.preprocessing_extract_data.scripts.main import run_preprocessing_extract_data
if __name__ == "__main__":
cmds = [
run_preprocessing_extract_data,
# a few more similar command groups
]
cli = CommandCollection(sources=cmds)
cli()
scripts/entrypoint.sh
#!/bin/sh
start_date="$1"
end_date="$2"
python src/scripts/main.py run_preprocessing_extract_data --start_date=$start_date --end_date=$end_date
I run it using ./scripts/entrypoint.sh --start_date="2020-11-01" --end_date="2021-12-01" --today="2021-12-10" but it keeps failing and throws the following error:
Usage: main.py [OPTIONS] COMMAND [ARGS]...
Try 'main.py --help' for help.
Error: No such command 'run_preprocessing_extract_data'.
From the docs:
The default implementation for such a merging system is the CommandCollection class. It accepts a list of other multi commands and makes the commands available on the same level.
Hence, your script now has a command main; you can check this by running your script with --help (or no arguments at all): python src/scripts/main.py --help.
Hence you can do the following:
python src/scripts/main.py main --start_date="$start_date" --end_date="$end_date"
By the way, invoking your shell script should be done without the --start_date: ./scripts/entrypoint.sh "2020-11-01" "2021-12-01".

python click framework - custom multi command implementation in oop's method

I have written some scripts that I'm trying to integrate with click. All the scripts are written in python OOP's.
The issue is that i am trying to build command section in oop's way but couldn't do it properly.
let me show you, what i am trying to do and please note that i am sharing here dummy code it is very similar to the real code.
First thing the directory structure:
-customcmd <dir>
|
|->commands <dir>
| -> abc-command.py
| -> __init__.py
|
|->__init__.py
|->main.py
|->setup.py
1) I have created one file called main.py, which contains following code:
import click
import os
plugin_folder = os.path.join(os.path.dirname(__file__), 'commands')
class MyCLI(click.MultiCommand):
def list_commands(self, ctx):
rv = []
for filename in os.listdir(plugin_folder):
if filename.startswith('__'):
continue
if filename.endswith('.py'):
rv.append(filename[:-3])
rv.sort()
return rv
def get_command(self, ctx, name):
ns = {}
fn = os.path.join(plugin_folder, name + '.py')
with open(fn) as f:
code = compile(f.read(), fn, 'exec')
eval(code, ns, ns)
return ns['cli']
cli = MyCLI()#help='This tool\'s subcommands are loaded from a ''plugin folder dynamically.'
if __name__ == '__main__':
cli()
2) abc-command.py
#click.command()
#click.option("--file-loc", '-fl', type=open, required=True, default=None, help="Path to the file")
def cli(file_loc):
"""
This is test command
"""
print("Path to file {}".format(file_loc))
Output of above code when you call main.py:
(automation) K:\Pythonenv\automation\customcmd>python main.py
Usage: main.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
policyresult This is test command
Output of above code when you call sub command:
(automation) K:\Pythonenv\automation\customcmd>python main.py policyresult --help
Usage: main.py policyresult [OPTIONS]
This is test command
Options:
-fl, --file-loc OPEN Path to the file [required]
--help Show this message and exit.
3) This is how I converted the procedural code of abc-command.py code:
class policyresult():
def __init__(self):
pass
#click.command()
#click.option("--file-loc", '-fl', type=open, required=True, default=None, help="Path to the file")
def cli(self,file_loc):
"""
This is test command
"""
print("Path to file {}".format(file_loc))
obj = policyresult()
obj.cli()
Output of above code doesn't match with the previous output when the code was procedural in abc-command.py:
Here i am calling the main.py
(automation) K:\Pythonenv\automation\customcmd>python main.py
Usage: main.py [OPTIONS]
Try "main.py --help" for help.
Error: Missing option "--file-loc" / "-fl".
In the above output you can see it is directly going into the sub-command options things and giving error as well.
As far as i understand list_commands() which is in main.py can't list out the commands, this part i can't understand why it is not working properly.
I tried various things but couldn't find the proper way to implement OOP's in abc-command.py because of that my ouput doesn't match.
I am new to this click framework, please suggest any new changes in my approach if needed.
please look into this, sorry for this weird way to explaining this.
abc-command.py is evaluated before click parses command options because of this line in the file invoking the cli method:
obj.cli()
Also, in the get_command method implemented for the multi-command, commands are supposed to expose a 'cli' name in their namespace.
To fix this error, in abc-command.py update the line invoking the cli command with:
cli = obj.cli
so that a cli name is exposed in abc-command.py module

Python unittest does not take passed parameters

I have following code:
class WebuiSeleniumTest(unittest.TestCase):
def setup_parser(self):
parser = argparse.ArgumentParser(description='Automation Testing!')
parser.add_argument('-p', '--platform', help='Platform for desired_caps', default='Mac OS X 10.9')
parser.add_argument('-b', '--browser-name', help='Browser Name for desired_caps', default='chrome')
parser.add_argument('-v', '--version', default='')
return parser.parse_args()
def test_parser(self):
args = self.setup_parser()
print args
if __name__ == "__main__":
unittest.main()
When i try to run this in terminal with the command "python myfile.py -b firefox" I get AttributeError: 'module' object has no attribute 'firefox' and the help output is generated.
When I isolate it and run it without the if __name__ == "__main__" it works fine. Why does it try to apply my passed argument on unittest ? I need it as a String in my code.
Calling your script with python myfile.py -b firefox does indeed go to unittest, and not to your argument parser.
Unittest tries to parse the arguments you gave, e.g. if you call your script like this:
python myfile.py --help
You see the valid options:
Usage: myfile.py [options] [test] [...]
Options:
-h, --help Show this message
-v, --verbose Verbose output
-q, --quiet Minimal output
-f, --failfast Stop on first failure
-c, --catch Catch control-C and display results
-b, --buffer Buffer stdout and stderr during test runs
Examples:
parse.py - run default set of tests
parse.py MyTestSuite - run suite 'MyTestSuite'
parse.py MyTestCase.testSomething - run MyTestCase.testSomething
parse.py MyTestCase - run all 'test*' test methods
in MyTestCase
Looking at the help output -b would buffer (I guess suppress?) stdout/stderr. The argument firefox is taken as the name of the test to run in your module. And there is no function method existing, it outputs this error:
AttributeError: 'module' object has no attribute 'firefox'
Now, what you probably want to do is to call test_parser, and if you do that with python myfile.py WebuiSeleniumTest.test_parser then you cannot pass any additional arguments. And that's probably your question in the end. There is this question which gives some possible solutions for testing argparse as unit test.

Why does my use of click.argument produce "got an unexpected keyword argument 'help'?

Running the following code results in this error:
TypeError: init() got an unexpected keyword argument 'help'
Code:
import click
#click.command()
#click.argument('command', required=1, help="start|stop|restart")
#click.option('--debug/--no-debug', default=False, help="Run in foreground")
def main(command, debug):
print (command)
print (debug)
if __name__ == '__main__':
main()
Full error output:
$ python3 foo.py start
Traceback (most recent call last):
File "foo.py", line 5, in <module>
#click.option('--debug/--no-debug', default=False, help="Run in foreground")
File "/home/cbetti/python/lib/python3/dist-packages/click-4.0-py3.4.egg/click/decorators.py", line 148, in decorator
_param_memo(f, ArgumentClass(param_decls, **attrs))
File "/home/cbetti/python/lib/python3/dist-packages/click-4.0-py3.4.egg/click/core.py", line 1618, in __init__
Parameter.__init__(self, param_decls, required=required, **attrs)
TypeError: __init__() got an unexpected keyword argument 'help'
Why does this error occur?
I run into this again and again because the trace output does not correspond to the parameter causing the error. Notice ArgumentClass in the trace, that's your hint.
'help' is an acceptable parameter to #click.option. The click library prefers, however, that you document your own arguments. The #click.argument help parameter is causing this exception.
This code works: (notice the lack of , help="start|stop|restart" in #click.argument)
import click
#click.command()
#click.argument('command', required=1)
#click.option('--debug/--no-debug', default=False, help="Run in foreground")
def main(command, debug):
""" COMMAND: start|stop|restart """
print (command)
print (debug)
if __name__ == '__main__':
main()
Output:
$ python3 foo.py start
start
False
Help Output:
Usage: test.py [OPTIONS] COMMAND
COMMAND: start|stop|restart
Options:
--debug / --no-debug Run in foreground
--help Show this message and exit.
You are defining commands as arguments. Note that click has a better way to define commands then what you are trying here.
#click.group()
def main():
pass
#main.command()
def start():
"""documentation for the start command"""
print("running command `start`")
#main.command()
def stop():
"""documentation for the stop command"""
print("running command `stop`")
if __name__ == '__main__':
main()
will result in the following default help text:
Usage: test_cli.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
start documentation for the start command
stop documentation for the stop command
Having said that, should you really need arguments, you cannot use the help parameter. The click documentation indeed states that you should document your own arguments. However, I have no idea how to do that. Any hints?
EDIT
As mentioned in the comments: this is to align with the Unix standard to document arguments in the main help text.
click library does not allow -help parameter inside click.arguments (including the current version 6.7 when this comment has been written).
But, you can use the -help parameter inside click.options.
You can check current click documentation at http://click.pocoo.org/6/documentation/ or previous at http://click.pocoo.org/5/documentation/ this behaviour has not change recently.
Then, it is a WAD. It is not a BUG.
For me it was because my variable looked like DnsCryptExractDir and I have to chnage it too this dnscryptexractdir because *args could not find it.
For same error I got it because my argument name was url_Hook (camelCase). After I changed it to url_hook it got resolved.

Categories

Resources