Python: Why is my Click CLI not executing the desired functions? - python

I have a function with click decorators. The entire purpose of the function is to add a CLI option to the work being done by the tq_oex_interchange class.
#click.command()
#click.option('-input_file', help='The input file to process')
#click.option('-output_file', help='The output file to create')
#click.option('-part_num_col', help='The column containing the part numbers')
#click.option('-summate_cols', help='The columns to summate')
def run(input_file, output_file, part_num_col, summate_cols):
from re import split
summate_cols = [int(i) for i in split(',', summate_cols)]
part_num_col = int(part_num_col)
teek = tq_oex_interchange()
click.echo("Working...")
teek.scan_file(input_file, output_file, part_num_col, summate_cols)
However, when i execute my script with the command
python tq_oex.py -input_file C:\Users\barnej78\Desktop\test_0.csv -output_file C:\Users\barnej78\Desktop\cmd.csv -part_num_col 3 -summate_cols 5,6
Nothing happens, not even the click echo is executed.
Additionally,
python tq_oex.py --help
Also doesn't do anything.
There is no error or exception output for any of these command.
What am I doing wrong?

Have you been able to successfully run the example code from here?
http://click.pocoo.org/5/
I'd start with that and make sure it works. Then write a test for it, using the Click test docs:
http://click.pocoo.org/5/testing/
That way when you start tweaking it, you can runs tests to see what breaks...
With Click applications, I often start simple, with one argument, and then add another to make sure it still works:
#click.command()
#click.argument('input_file')
def run_files(input_file):
click.echo(input_file)
Then add one option to this:
#click.command()
#click.argument('input_file')
#click.option('--output_file', help='The output file to create')
def run_files(input_file, output_file):
click.echo(input_file, output_file)
I like to set defaults as well, for debugging purposes:
def run_files(input_file='/path/to/input_file',
output_file='/path/to/output_file'):
click.echo(input_file, output_file)

Related

What is the best way to wrap multiple existing CLIs in one CLI using python

I want to wrap multiple CLIs and make one parent CLI that will basically call the other CLIs under the hood.
To give an example, let's say I want to make my_cli that will wrap the git and cmake clis. For instance, when I call my_cli add, it would call git add under the hood and similarly when I can my_cli build, it would call cmake build.
I actually managed to implement an example using the python click library, but for this I need to (re)create the same CLI args and options of git in my_cli. Basically, it was too much copy and paste. I would just check which CLI args and options does git add have and just go and create these using the click library for the add command.
I'm asking if this is the only way to achieve this. It would be great if I can somehow dynamically parse the cli args and give them forward to the click command without having to specify any cli args and options for my click command.
Here is an example from the click docs:
import click
#click.command()
#click.option('--name', prompt='Your name', help='The person to greet.') # is there a way to not know which args and option would my command expect?
def hello(name):
click.echo(f"Hello {name}!")
In click, the command needs to know which args and options to expect at declaration time. Is there a way to make this dynamic and forward the args and options to the command?
Here is a simple expected result:
import click
#click.command()
def add(*args, **kwargs): # args would be cli args and kwargs would be cli options passed by the user.
click.echo(f"git add {args} {kwargs}!")
Ideally, the help text for the add command need to be the same as the help text for git add because if I achieve to pass the args and options dynamically, then I must also update the help text, otherwise my_cli add --help would return nothing useful.
I've achieved this by defining a group/command like this:
import subprocess
import click
#click.group()
def cli():
pass
#cli.command(context_settings=dict(
ignore_unknown_options=True,
allow_extra_args=True,
))
#click.argument('command')
#click.pass_context
def run(ctx, command):
subprocess.run([command, *ctx.args])
if __name__ == '__main__':
cli()
and invoking it from my shell like:
python main.py run git -- --help
python main.py run git -- add --help
python main.py run git -- branch --help
python main.py run ls -- -al
Essentially, I told my click command to passthrough all arguments it receives to the command I have specified.
Hope this answers your question.

Argparse, displaying custom help text without any of the boilerplate argparse text

After looking at about a dozen questions, I can't seem to find an answer.
I have a python CLI i've written using argparse. I have a main command that does nothing but regurgitate help text and then 4 subcommands. My boss wants a very specific output for the help text. He has me write it out as a text file and then we use that text file to display the help text.
However, in some circumstances, it STILL outputs parts of the argparse help text.
For example, if I run my program with no subcommands, it just outputs our help text from the file. But if I use "-h" or "--help" it will output our help text, followed by the list of positional and optional arguments and other argparse stuff. We don't want that.
I could use "add_help=False" but we want the user to be able to type -h and still get our help text. If we set add help to false, it will display our help text followed by the error "-h not recognized".
Also doing this does nothing for when the user uses -h after a subcommand. I set help=None and usage is set to my custom help text for each subcommand, but it still shows the boilerplate argparse info at the end.
This is what I want to happen: user types in the main command with no subcommands prints my custom help text and nothing else. The user types the main command, no subcommand, followed by -h/--help and it prints my custom help text and nothing else. User types in the main command, one of the subcommands, followed by -h/--help and it outputs my help text and nothing else. User types the main command, a subcommand, and then wrong arguments or too many/ too few arguments displays my help text and nothing else. Basically I only ever want it to print nothing, or print just my help text.
how do I do that? here is my main function where the parsers and subparsers are all configured:
def main():
# Import help text from file
p = Path(__file__).with_name("help.txt")
with p.open() as file:
help_text = file.read()
# Configure the top level Parser
parser = argparse.ArgumentParser(prog='myprog', description='My program', usage=help_text)
subparsers = parser.add_subparsers()
# Create Subparsers to give subcommands
parser_one = subparsers.add_parser('subcommandone', prog='subcommandone', usage=help_text, help=None)
parser_one.add_argument('arg1', type=str)
parser_one.add_argument('-o', '--option1', default='mydefault', type=str)
parser_two= subparsers.add_parser('subcommandtwo', usage=help_text, help=None, prog='subcommandtwo')
parser_three= subparsers.add_parser('subcommandthree', usage=help_text, help=None, prog='subcommandthree')
parser_four= subparsers.add_parser('subcommandfour', usage=help_text, help=None, prog='subcommandfour')
# Assign subparsers to their respective functions
parser_one.set_defaults(func=functionone)
parser_two.set_defaults(func=functiontwo)
parser_three.set_defaults(func=functionthree)
parser_four.set_defaults(func=functionfour)
parser.set_defaults(func=base_case)
# Parse the arguments and call appropriate functions
args = parser.parse_args()
if len(sys.argv) == 1:
args.func(args, parser)
else:
args.func(args)
Any thoughts?
You can use sys.exit() after the help text has been displayed, and before the parsing has begun, to avoid problems with "-h not recognized".
So anywhere before the line
# Parse the arguments and call appropriate functions
add
if len(sys.argv) == 1 or '-h' in sys.argv or '--help' in sys.argv:
print(help_text)
sys.exit(1)
In situations where that is not good enough you can subclass argparse.HelpFormatter like so
usage_help_str = 'myscript command [options]'
epilog_str = "More info can be found at https://..."
class Formatter(argparse.HelpFormatter):
# override methods and stuff
def formatter(prog):
return Formatter(prog)
parser = argparse.ArgumentParser(formatter_class=formatter, epilog=epilog_str, usage=usage_help_str, add_help=False)
I tried looking around for documentation on subclassing the helpFormatter, but I couldn't find anything. It looks like people are just looking at the source code to figure out how to subclass it.

running a python program using argparse to setup input In Jupyter Notebook

I have a .py file following the normal code structure
def main( args ):
.......
.......
if __name__ == "__main__":
parser = argparse.ArgumentParser(description = “ forecasting example”)
parser.add_argument("--train-window", default=2160, type=int)
parser.add_argument("--test-window", default=336, type=int)
parser.add_argument("--stride", default=168, type=int)
parser.add_argument("-n", "--num-steps", default=501, type=int)
parser.add_argument("-lr", "--learning-rate", default=0.05, type=float)
parser.add_argument("--dct", action="store_true")
parser.add_argument("--num-samples", default=100, type=int)
parser.add_argument("--log-every", default=50, type=int)
parser.add_argument("--seed", default=1234567890, type=int)
args = parser.parse_args()
main(args)
I was trying to run this program in Jupyter notebook, but it will get errors such as
usage: ipykernel_launcher.py [-h] [--train-window TRAIN_WINDOW]
[--test-window TEST_WINDOW] [--stride STRIDE]
[-n NUM_STEPS] [-lr LEARNING_RATE] [--dct]
[--num-samples NUM_SAMPLES]
[--log-every LOG_EVERY] [--seed SEED]
ipykernel_launcher.py: error: unrecognized arguments: -f C:\Users\AppData\Roaming\jupyter\runtime\kernel-4c744f03-3278-4aaf-af5e-50c96e9e41cd.json
An exception has occurred, use %tb to see the full traceback.
SystemExit: 2
my question is that, what are the right approaches or the modifications I need to make if I want to run a python program, which setup input parameters using argparse type of mechanism, in Jupyter Notebook?
Your code should be indented differently so you can import it into your notebook, or into another Python script. The whole point of the if __name__ == "__main__": block is that it gets executed immediately when Python parses the file; the condition is true only when you run the file directly, not when you import it. But the block needs to be indented differently, so that it's not inside any def or class or other block structure.
The way to use this from a notebook, then, is to call main (or whichever other functions from the imported code you want to run) with your desired parameters.
In this case, main has been designed to expect an Argparse object as its argument, which is quite unfortunate. A better design would simply do the argument parsing inside main, and expose a different function or set of functions for reuse as a library.
Assuming your main function's internals look something like
def main(args):
print(
real_main(args.train_window, args.test_window,
stride=args.stride, num_steps=args.num_steps,
learning_rate=args.learning_rate,
dct=args.dct, num_samples=args.num_samples,
log_every=args.log_every, seed=args.seed))
and supposing you wanted to run the equivalent of
python thatfile.py -n 23 -lr 0.7--dct --num-samples 2300
the equivalent code in your notebook would look like
from thatfile import real_main as that_main
print(that_main(2160, 336, num_steps=23,
learning_rate=0.7, dct=True,
num_samples=2300))
where the first two values are simply copied from the argparse defaults, and I obviously had to speculate a great deal about which parameters are required and which are optional keyword parameters, and whether they are named identically to the argparse field names.

python double tabbing in cli to show compatible files only

Please find the following snippet:
import argparse
parser = argparse.ArgumentParser(
description="Create plot from data",
formatter_class=lambda prog: argparse.HelpFormatter(
prog, max_help_position=27))
action = parser.add_mutually_exclusive_group(required=True)
action.add_argument('--foo', help="Create foo plot") # input is <filename>.foo
action.add_argument('--bar', help="Create bar plot") # Input is <filename>.bar
I run this in linux terminal emulator. Is it possible within python that, in the terminal, double tabiing will show only files with extension foo or bar, depending on argument, and not all the files in PWD?
I have found TAB autocomplete python CLI, but that is a decade old. Is there any option now?
Update #Lenormju:
I have updated the code as:
action.add_argument('--foo', help="Create foo plot", choices=('agr'))
action.add_argument(
'--bar', help="Create bar plot").completer = ChoicesCompleter('.agr')
So, now in terminal,
python ~/bin/code.py --foo [TAB][TAB]
I am expecting this to show files with .agr extensions only. Instead it is still shown all the files present in PWD.
Actually, this should not work, because "choices" means, I have to choose between 'a' 'g' or 'r'. May be I was not clear in the main question, show I have elaborated it.
When you press Tab, your shell expects to receive a list of strings (the possible completions).
Either you register a Bash function to do the completion for your Python script (see this answer) so that your Python script is not called until the command line is finished.
Or else you do it with your Python script (see this answer for argcomplete which mixes well with argparse). In this case, Your Python script is indeed executed, but after computing the possible completions it will exit, and thus not run anything else. In other words, it will be called repeatedly to provide completion suggestions, then once at the end with the full command line to do its actual work.
Showing only the files with certain extensions depending on the argument is "just" a matter of customizing the completer.
Your question is really just a duplicate of the other, but you seem to not see it. If the misunderstanding is actually on my side, please provide detailed explanations.

How to show me output of python argparse script, result of print exits command prompt

I'm still relatively new to Python.
I'm supposed to write a Python script that prints out all files and directories under certain conditions:
You can find the explanation of the exercise below.
The solution to the exercise of my course Scripting-languages doesn't seem to print the actual results and I've searched and tried but I don't see what's wrong with it. After all it's supposed to be the solution.
The supposed solution:
from pathlib import Path
import argparse
from pathlib import Path
import os
import re
def minimum_size(size):
def check(filename):
return os.path.getsize(filename) >= size
parser = argparse.ArgumentParser(prog='find')
parser.add_argument('path', default='.')
parser.add_argument('--minimum-size', dest='minimum_size', default=0, type=int)
parser.add_argument('--maximum-size', dest='maximum_size', default=float('inf'), type=int)
parser.add_argument('--no-directories', dest='no_directories', default=False, action='store_true')
parser.add_argument('--no-files', dest='no_files', default=False, action='store_true')
parser.add_argument('--extension', dest='extension')
parser.add_argument('--contains', dest='contains')
args = parser.parse_args()
path = Path(args.path)
for entry in path.glob('**/*'):
if not os.path.getsize(entry) >= args.minimum_size:
continue
if not os.path.getsize(entry) <= args.maximum_size:
continue
if args.no_directories and os.path.isdir(entry):
continue
if args.no_files and os.path.isfile(entry):
continue
if args.extension and not entry.suffix == args.extension:
continue
if args.contains:
if not os.path.isfile(entry):
continue
with open(entry, 'r') as file:
contents = file.read()
if not re.search(args.contains, contents):
continue
print(entry)
The explanation:
My own comments about this: I think the examples are supposed to include the extension when calling the file and so I do do that when trying to execute the code.
I even include the whole path to the file so that I won't get an 'ObjectNotFound' error.
When I run the solution in Powershell in Python, the command prompt opens and closes immediately.
When I run it directly in Powershell, the command prompt opens, lists all the output and closes immediately when it's done. If you could give me a way to just stop the command prompt from closing so that I can see the result. I'd already be very happy.
Write a script `find.py` that prints out all files and directories
that satisfy certain conditions.
The following functionality must be supported:
* One positional parameter that specifies the directory in which to start looking recursively.
* `--minimum-size=N`: only files whose size is at least `N` must be listed.
* `--maximum-size=N`: only files whose size is at most `N` must be listed.
* `--no-directories`: don't list directories.
* `--no-files`: don't list files.
* `--extension=EXT`: only list entries with extension `EXT`
* `--contains=REGEX`: only list files whose contents match `REGEX`.
To get a better feel for how your script should work, feel free
to experiment with the solution. For example, try
`bash
# List everything starting from two directories up
$ python find ../..
# List all python files in current directory
$ python find --extension=.py .
`
Instead of running the script in Powershell which uses Command prompt to execute the script. Run the script directly in Command prompt so you can see the output.
To change terminal in Visual Studio Code: Click on the current terminal to see a dropdown menu and click "Select Default Shell". Choose "Command Prompt" from the options.

Categories

Resources