Docopt extreemly unintuitive opions usage - python

Docopt loves to write lots of documentation, by I can't seem to find a single actual command line call inside their many pages of how to write the comments section. I have this very simple file:
"""Main.py
Usage:
main.py controller
main.py model
main.py form
main.py -h | --help
main.py --version
Options:
-h --help Show this screen.
--version Show version.
--outfile Output file.
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__, version='Main.py 1.0')
print(arguments)
I get the first part:
adams-mbp:Aut adam$ python main.py model
{'--help': 0,
'--outfile': None,
'--version': 0,
'Options:': False,
'Show': 0,
'controller': False,
'file.': False,
'form': False,
'model': True,
'screen.': False,
'this': False,
'version.': False}
But I can't seem to figure out how to pass an --outfile parameter. Here's what I've tried:
adams-mbp:Aut adam$ python main.py main.py --outfile thing
Usage:
main.py controller
main.py model
main.py form
main.py -h | --help
main.py --version
Options:
-h --help Show this screen.
--version Show version.
--outfile Output file.
adams-mbp:Aut adam$ python main.py main.py --outfile=thing
Usage:
main.py controller
main.py model
main.py form
main.py -h | --help
main.py --version
Options:
-h --help Show this screen.
--version Show version.
--outfile Output file.
adams-mbp:Aut adam$ python --outfile thing main.py main.py
Unknown option: --
usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...
Try `python -h' for more information.
adams-mbp:Aut adam$ python --outfile=thing main.py main.py
Unknown option: --
usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...
Try `python -h' for more information.
adams-mbp:Aut adam$ python main.py main.py -outfile thing
Usage:
main.py controller
main.py model
main.py form
main.py -h | --help
main.py --version
Options:
-h --help Show this screen.
--version Show version.
--outfile Output file.
adams-mbp:Aut adam$ python main.py main.py -outfile=thing
Naval Fate.
Usage:
main.py controller
main.py model
main.py form
main.py -h | --help
main.py --version
Options:
-h --help Show this screen.
--version Show version.
--outfile Output file.
adams-mbp:Aut adam$ python main.py main.py outfile=thing
Usage:
main.py controller
main.py model
main.py form
main.py -h | --help
main.py --version
Options:
-h --help Show this screen.
--version Show version.
--outfile Output file.
Seriously?

First of all, why are you calling your script twice?
python main.py main.py --outfile thing # Will not work
python main.py --outfile thing # This will be enough
Secondly, the usage section of docopt show you the different way to call your script.
Usage:
main.py controller
main.py model
main.py form
main.py -h | --help
main.py --version
main.py model work because it's described here. But --outfile appears nowhere in this Usage section so docopt consider it as a false input. And that's why the script always return the help.
If you want to use this option you have to describe it in "Usage".
You can do this like that :
Usage:
main.py controller
main.py model [--outfile]
The bracket tell that the option is not mandatory, use () for mandatory options.
Another point, even you first call show errors, options should not appear in the dictionnary, maybe you should leave a blank before in the documentation. Neither screen, or this. an so on...
Try something like that :
"""Main.py
Usage:
main.py controller [--outfile]
main.py model [--outfile]
main.py form [--outfile]
main.py (-h | --help)
main.py --version
Options:
-h, --help Show this screen.
--version Show version.
--outfile Output file.
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__, version='Main.py 1.0')
print(arguments)

Related

How can I get a --version to the root of a typer.Typer application?

My CLI applications typically have subcommands. I want to have the --version flag at the root of my CLI applications, but with Typer I've only seen ways to put it to a command. I want to add it to the typer.Typer object (the root) itself. How can I do that?
What I've tried
import typer
from typing import Optional
__version__ = "0.1.0"
def version_callback(value: bool):
if value:
typer.echo(f"Awesome CLI Version: {__version__}")
raise typer.Exit()
app = typer.Typer(
add_completion=False,
)
#app.command()
def main(
version: Optional[bool] = typer.Option(
None, "--version", callback=version_callback
),
) -> None:
pass
#app.command()
def foo() -> None:
pass
if __name__ == "__main__":
app()
This gives
$ python cli.py --help
Usage: cli.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
foo
main
$ python cli.py main --help
Usage: cli.py main [OPTIONS]
Options:
--version
--help Show this message and exit.
What I wanted:
$ python cli.py --help
Usage: cli.py [OPTIONS] COMMAND [ARGS]...
Options:
--version
--help Show this message and exit.
Commands:
foo
This is addressed in the documentation:
But as those CLI parameters are handled by each of those commands, they don't allow us to create CLI parameters for the main CLI application itself.
But we can use #app.callback() for that.
It's very similar to #app.command(), but it declares the CLI parameters for the main CLI application (before the commands):
To do what you want, you could write something like this:
import typer
from typing import Optional
__version__ = "0.1.0"
def version_callback(value: bool):
if value:
typer.echo(f"Awesome CLI Version: {__version__}")
raise typer.Exit()
app = typer.Typer(
add_completion=False,
)
#app.callback()
def common(
ctx: typer.Context,
version: bool = typer.Option(None, "--version", callback=version_callback),
):
pass
#app.command()
def main() -> None:
pass
#app.command()
def foo() -> None:
pass
if __name__ == "__main__":
app()
Which gives us:
$ python typertest.py --help
Usage: typertest.py [OPTIONS] COMMAND [ARGS]...
Options:
--version
--help Show this message and exit.
Commands:
foo
main

How require options for CLI app based on Python and Click

I am building a CLI app with Python and the Click library.
How do I achieve the following use case:
First I only want the subcommand to be followed by an argument no options are required:
$ myapp subcommand argument
This is straight forward.
But how can I write the code that if argument2 is set that also some options are required?
$ myapp subcommand argument2 -o1 abc -o2 def
For example:
no options are required:
$ ./myapp.py install basic
options are required:
$ ./myapp.py install custom -o1 abc -o2 def
Furthermore I do not know how to make choice for arguments, that means that the user must choose between "basic" or "custom". In case he chooses "custom", he needs to add some options.
I have achieved this successfully by making your argument2 be a click.Command.
Running through the code below, my main way of interacting with the CLI application is via the cli group. That cli group has another group, install, added as a command. So we have a CLI with nested groups.
install has 2 commands, basic and custom, as in your example.
basic takes no parameters, while custom takes 2 required Options.
Calls would look like this:
❯ myapp install custom -o1 arg1 -o2 def
This is a custom install with option1: arg1 and option2: def
❯ myapp install basic
Executing a basic install
You can see the nested group install acts as a command inside the help message:
❯ myapp
Usage: myapp [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
install
And if you were to invoke install, this is the help output you'd get.
❯ myapp install
Usage: myapp install [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
basic
custom
This is the code:
import click
#click.group()
def cli():
pass
#click.group()
def install():
pass
#install.command()
def basic():
print('Executing a basic install')
#install.command()
#click.option("-o1", "--option1", required=True)
#click.option("-o2", "--option2", required=True)
def custom(option1, option2):
print(f'This is a custom install with option1: {option1} and option2: {option2}')
def main():
cli.add_command(install)
cli()
if __name__ == '__main__':
main()

Behaviour of the docopt module (argument parser)

I currently use the docopt lib for the first time so I surely do something wrong
My script is :
"""prog
Usage:
prog.py (-h | --help)
prog.py (--version)
prog.py -s TAG [-t NB_NUC]
Options:
-h, --help help
--version version
-s TAG Some TAG I want.
-t NB_NUC A number of nuc.
"""
If I write:
python prog.py -s SMT
I get:
{'--help': False,
'--version': False,
'-h': False,
'-s': True,
'-t': True,
'NB_NUC': None,
'TAG': 'SMT'}
And it seems to be correct, but if I write :
python prog.py -s -t 10 -> TAG contain 10 (instead of None)
python prog.py -t 10 -s SMT -> TAG contain always 10 (instead of SMT) and NB_NUC contain SMT (instead of 10)
python prog.py -s SMT -t -> TAG contain SMT and NB_NUC contain None (and its what I expected on this way)
So, I tried lot a combination, but I don't understand how this is supposed to word...
What I want is TAG always contains the values which correspond with the -s argument, with None or an error if nothing is given after -s, and I don't understand why it's not the case..
Thanks for your help !
Your are almost there, just need the "<...>" around the arguments:
"""prog
Usage:
prog.py (-h | --help)
prog.py (--version)
prog.py -s TAG [-t NB_NUC]
Options:
-h, --help help
--version version
-s TAG Some TAG I want.
-t NB_NUC A number of nuc.
"""
The problem came from the fact that previous version of docopt didn't work with tabulated indentation.
Actual version does, and the PEP8 recommend usage of spaces anyway.
And for the formating the easiest way is to only write
Usage:
prog.py (-h | --help)
prog.py (-v | --version)
prog.py [options] <mandatory_file>
And to put the differents options and their descriptions in the Options part.

How to pass command line arguments to ipython

Is there any way that I pass arguments to my python script through command line while using ipython? Ideally I want to call my script as:
ipython -i script.py --argument blah
and I want to be able to have --argument and blah listed in my sys.argv.
You can use one -- more option before that:
ipython script.py -- --argument blah
Help of Ipython:
ipython [subcommand] [options] [-c cmd | -m mod | file] [--] [arg] ...
If invoked with no options, it executes the file and exits, passing the
remaining arguments to the script, just as if you had specified the same
command with python. You may need to specify `--` before args to be passed
to the script, to prevent IPython from attempting to parse them. If you
specify the option `-i` before the filename, it will enter an interactive
IPython session after running the script, rather than exiting.
Demo:
$ cat script.py
import sys
print(sys.argv)
$ ipython script.py -- --argument blah
['script.py', '--argument', 'blah']
$ ipython script.py -- arg1 arg2
['script.py', 'arg1', 'arg2']

ipython not showing argparse help message

In my python script myscript.py I use argparse to pass command-line arguments. When I want to display the help information about the input arguments, I just do:
$ python myscript.py --help
If instead I want to use ipython to run my script, the help message won't be displayed. Ipython will display its own help information:
$ ipython -- myscript.py -h
=========
IPython
=========
Tools for Interactive Computing in Python
=========================================
A Python shell with automatic history (input and output), dynamic object
introspection, easier configuration, command completion, access to the
system shell and more. IPython can also be embedded in running programs.
Usage
ipython [subcommand] [options] [files]
It's not so annoying, but is there a way around it?
You need to run your .py script inside the ipython. Something like that:
%run script.py -h
This is an IPython bug, corrected in https://github.com/ipython/ipython/pull/2663.
My 0.13 has this error; it is corrected in 0.13.2. The fix is in IPthyon/config/application.py Application.parse_command_line. This function looks for help and version flags (-h,-V) in sys.argv before passing things on to parse_known_args (hence the custom help formatting). In the corrected release, it checks sys.argv only up to the first --. Before it looked in the whole array.
earlier:
A fix for earlier releases is to define an alternate help flag in the script:
simple.py script:
import argparse, sys
print(sys.argv)
p = argparse.ArgumentParser(add_help=False) # turn off the regular -h
p.add_argument('-t')
p.add_argument('-a','--ayuda',action=argparse._HelpAction,help='alternate help')
print(p.parse_args())
Invoke with:
$ ./ipython3 -- simple.py -a
['/home/paul/mypy/argdev/simple.py', '-a']
usage: simple.py [-t T] [-a]
optional arguments:
-t T
-a, --ayuda alternate help
$ ./ipython3 -- simple.py -t test
['/home/paul/mypy/argdev/simple.py', '-t', 'test']
Namespace(t='test')

Categories

Resources