I have a Python program and am using Click to process the command line. I have a single command program, as in the example below. On Windows I would like to pass in a valid glob expression like *.py
to Click 8.x and have the literal string "*.py"
show up in the filepattern
variable.
Click 8.x expands the glob patterns by default (this is different than 7.x). On Bash, if you quote the glob pattern, the shell doesn't do the expansion, and Click passes in the string. On Windows, the shell doesn't do the expansion and passes the string into Python. In this case Click expands them.
So the question here is how do I get the string "*.py" passed in as a string on Windows (if there are OR are not any files that match)? I only want to allow 1 argument (so nargs=1). This all works fine in Click 7.x.
The following example saved as mycommand.py
:
import click
@click.command("mycommand")
@click.argument("filepattern", nargs=1, type=str)
def mycommand(filepattern):
print(filepattern)
if __name__ == "__main__":
mycommand()
If I have a directory full of, say Python files, if I invoke this as python mycommand.py somefile.py
, it will succeed as one value gets passed into filepattern
and it will echo somefile.py
.
If I invoke as python mycommand.py *.py
it fails with an error like:
Usage: mycommand.py [OPTIONS] FILEPATTERN
Try 'mycommand.py --help' for help.
Error: Got unexpected extra arguments (scratch.py mycommand.py ...)
I know there is an argument windows_expand_args
for a click.group
, but I can't puzzle out how to get it to work for a single command program.
A search for windows_expand_args
in Click's GitHub repository gave the clue.
From click changelog, Version 8.0.1, released 2021-05-19:
- Pass
windows_expand_args=False
when calling the main command to disable pattern expansion on Windows. There is no way to escape patterns in CMD, so if the program needs to pass them on as-is then expansion must be disabled. :issue:1901
Someone asked same question in the respective PR 1918:
How is this used? I tried:
import click @click.group(windows_expand_args=False) @click.pass_context def cli(ctx): ...
and got this answer from the maintainer:
It's an argument to the main program.
cli(windows_expand_args=False)
windows_expand_args=False
As CrazyChucky already answered, invoke the @click.command
decorated function with windows_expand_args
as extra keyword argument (e.g. in your main like shown below):
import click
@click.command("mycommand")
@click.argument("filepattern", nargs=1, type=str)
def mycommand(filepattern):
print(filepattern)
if __name__ == "__main__":
import os
print(f"Click {click.__version__} on {os.name}")
mycommand(windows_expand_args = False)
Could only test on Linux: failed, as expected by David Campbell's comment to the question 😉️.
(a) with 1 quoted argument containing a glob like *
python3 click_glob.py "*.py"
Click 8.0.4 on posix
*.py
(b) with 1 unquoted argument containing a glob like *
python3 click_glob.py *.py
Click 8.0.4 on posix
Usage: click_glob.py [OPTIONS] FILEPATTERN
Try 'click_glob.py --help' for help.
Error: Got unexpected extra argument (pdfminer_figures.py)
(c) with 2 quoted arguments
python3 click_glob.py "*.py" "invalid"
Click 8.0.4 on posix
Usage: click_glob.py [OPTIONS] FILEPATTERN
Try 'click_glob.py --help' for help.
Error: Got unexpected extra argument (invalid)
Could not verify, since I have no Windows available. Maybe someone else can test on their Windows and edit this answer with results.
(a) with 1 quoted argument containing a glob like *
(b) with 1 unquoted argument containing a glob like *
(c) with 2 quoted arguments