Example script: https://gist.github.com/thingsiplay/ae9a26322cd5830e52b036ab411afd1f
Hi all. I just wanted to share a way to handle a so called advanced help menu, where additional options are listed that are otherwise hidden with regular help. Hidden options should still function. This is just to have less clutter in normal view.
I’ve researched the web to see how people does it, and this is the way I like most so far. If you think this is problematic, please share your thoughts. This is for a commandline terminal application, that could also be automated through a script.
How it works on a high level
Before the ArgumentParser()
is called, we check the sys.argv
for the trigger option --advanced-help
. Depending on this we set a variable to true or false. Then with the setup of the parser after the ArgumenParser()
call, we add the --advanced-help
option to the list of regular help.
advanced_help = False
for arg in sys.argv:
if arg == "--":
break
if arg == "--advanced-help":
advanced_help = True
parser = argparse.ArgumentParser()
Continue setting up your options as usual. But for the help description of those you want to exclude when using just regular -h
, add an inline if else statement (ternary statement). This statement will put the help description only if advanced_help
variable is true, otherwise it puts argparse.SUPPRESS
to hide the option. Do this with all the options you want to hide.
parser.add_argument(
"-c",
"--count",
action="store_true",
default=False,
help="print only a count of matching items per list, output file unaffected"
if advanced_help
else argparse.SUPPRESS,
)
At last we need to actually parse what you just setup. For this we need to assign our custom list, that is based on the sys.argv
, plus the regular --help
option. This way we can use --advanced-help
without the need for -h
or --help
in addition to show any help message.
if advanced_help:
args = parser.parse_args(sys.argv[0:0] + ["--help"] + sys.argv[1:])
else:
args = parser.parse_args()
Run following program once with ./thing.py -h
and ./thing.py --advanced-help
.
why click is based on optparse and not argparse
Applies only to optional args long help
Applies only to subcommands short help
Special mention to how to document positional args. The docs explains the intentional lack of help kwarg for positional args.
./thing.py -h ./thing.py subcommand --help
Lists all the subcommands with one line short description for each subcommand.
Lists detailed docs of one subcommand
My opinion having used both argparse and click, click is simpler cleaner and less time consuming.
Nice. There is a reason why the standard library of Python does not get rid of optparse. optparse was I think marked to be removed in the future, but its no longer deprecated and will stay in the library.
Click looks good and I would have looked in more detail. The problem to me is, it is a dependency. I usually write and try to write Python programs without external library dependency, unless it is absolutely necessary (like GUI or BeautifulSoup). Click “might” be a better alternative, but it is “only” something that does it a little bit better. This is not a reason for me to use an external library.
What is the root basis of your external package reluctance? Please explain cuz that’s really where the juicy story lies.
As technologists things change and advance and we have to adapt (or not) with the times. Maintaining the universe by ourselves is impossible, instead almost all of our tech choices are from what’s available. And only if/when that is insufficient do we roll up our sleeves.
More and more packages are using
click
. So there is a good chance if you look at your requirements .lock file that it’s already a transitive dependency from another dependency.Or said another way, show me 5 popular packages that use argparse and not click and use dataclasses and not attrs
I use external packages for compiled languages. For scripting languages like Python, it makes it ab it harder to use. Because people are forced to install dependencies. I don’t like that (unless its a shell script, but that is by its nature a dependency hell).
And for Python, I usually deliver the script as a single .py file, without requirements or special instructions, no typical file structure as well. And your argument just because other popular packages use a specific library does not mean I have to use it too. I don’t care what libraries other popular packages use, at least in context to my program / script.
people are forced to install dependencies
This ^^.
If possible, Python dependency management is a burden would prefer to avoid. Until can’t, then be skilled at it!
disclosure: i use/wrote wreck for Python dependency management.
Compiled languages should really live within containers. At all cost, would like to avoid time consuming system updates! I can no longer install C programs cuz on OS partition ran out of hard disk space. Whereas Python packages can be installed on data storage partitions.
for Python, I usually deliver the script as a single .py file I’m sure you are already aware of this. So forgive me if this is just being Captain Obvious.
Even if the deliverable is a single .py file, there is support for specifying dependencies within module level comment block. (i forget the PEP #).
I don’t like that (unless its a shell script, but that is by its nature a dependency hell) You and i could bond over a hatefest on shell scripts, but lets leave this as outside the discussion scope
And your argument As the complexity of a .py script grows, very quickly, comes to a point the deliverable becoming a Python package. With the exceptions being projects which are: external language, low level, or simple. This .py script nonsense does not scale and is exceedingly rare to encounter. May be an indication of a old/dated or unmaintained project.
From a random venv, installed scripts:
_black_version.py appdirs.py cfgv.py distutils-precedence.pth mccabe.py mypy_extensions.py nodeenv.py packaging_legacy_version.py pip_requirements_parser.py py.py pycodestyle.py pyi.py six.py typing_extensions.py
BTW, I’m not saying the way I am going and doing this is the right one. In the end I use additional packages when needed, BeautifulSoup as an example to make life really easier. But that is something specific to that script. Stuff like click and argparse is more than a single script, because when I switch to click then all my future scripts would depend on it. And I feel more comfortable with a solution that is already built-in (stdlib) if its not too bad.
I don’t see click as a required package that I really need. And that is also true for many other packages, such as requests one.
There is an expression,
Linux isn't free it costs you your time
. Which might be a counter argument against always using only what is built in.I’m super guilty of reinventing the wheel. But writing overly verbose code isn’t fun either. Never seem to get very far.
There is an expression, Linux isn’t free it costs you your time.
Just because someone said it does not make it true and certainly, I don’t have to live after that expression. It kind of is a catch all phrase to justify (or not to justify) everything. It could also be used as an argument for “Vibe Coding” (I hate that term…).
I mean this argument about Linux does not apply to every single application (you apply it right now here to some random Python script). In example Windows, MacOS, Android, nothing is free and costs you your time. The question is, how you want spent your time. And I enjoy writing programs and scripts for various reasons.
I personally think reinventing the wheel is great. Why? It makes you learn and do it. It makes you less dependent. The end result might not be the most polished one, but also if nobody reinvents the wheel, then we have no competition. Sure you should not reinvent everything, there is a balance act to make. And this balance is different for everyone else.
BTW, you can also have a short description instead suppressing the option. In example:
parser.add_argument( "-s", "--sort", action="append", help="sort all game items by value of chosen key, before filter, *multi" if advanced_help else "sort all items", )
instead hiding the option, with
--help
the short description"sort all items"
is listed, while the advanced help would then show the long help. Just an additional thing with the above technique is possible.