diff options
author | David Lord <davidism@gmail.com> | 2021-10-25 08:46:34 -0700 |
---|---|---|
committer | David Lord <davidism@gmail.com> | 2021-10-25 08:46:34 -0700 |
commit | 0c85d80b07742b9f9ed4d583d694c37fce1d1292 (patch) | |
tree | 0696c73b5d1abfe4d5eb1f6ef1328985d2a710e9 | |
parent | 65eceb08e392e74dcc761be2090e951274ccbe36 (diff) | |
parent | e415d3a811fda506c8d7917e615b97ae9dcd1194 (diff) | |
download | click-0c85d80b07742b9f9ed4d583d694c37fce1d1292.tar.gz |
Merge branch '8.0.x'
-rw-r--r-- | CHANGES.rst | 9 | ||||
-rw-r--r-- | src/click/_compat.py | 6 | ||||
-rw-r--r-- | src/click/types.py | 37 | ||||
-rw-r--r-- | src/click/utils.py | 39 | ||||
-rw-r--r-- | tests/test_arguments.py | 4 | ||||
-rw-r--r-- | tests/test_utils.py | 16 |
6 files changed, 71 insertions, 40 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 251b2e4..fe07098 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,15 @@ Unreleased the default if it is ``True``. :issue:`1971`: +Version 8.0.4 +------------- + +Unreleased + +- ``open_file`` recognizes ``Path("-")`` as a standard stream, the + same as the string ``"-"``. :issue:`2106` + + Version 8.0.3 ------------- diff --git a/src/click/_compat.py b/src/click/_compat.py index b9e1f0d..7154dfd 100644 --- a/src/click/_compat.py +++ b/src/click/_compat.py @@ -388,9 +388,9 @@ def open_stream( ) -> t.Tuple[t.IO, bool]: binary = "b" in mode - # Standard streams first. These are simple because they don't need - # special handling for the atomic flag. It's entirely ignored. - if filename == "-": + # Standard streams first. These are simple because they ignore the + # atomic flag. Use fsdecode to handle Path("-"). + if os.fsdecode(filename) == "-": if any(m in mode for m in ["w", "a", "x"]): if binary: return get_binary_stdout(), False diff --git a/src/click/types.py b/src/click/types.py index 103d218..198c723 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -744,26 +744,23 @@ class File(ParamType): class Path(ParamType): - """The path type is similar to the :class:`File` type but it performs - different checks. First of all, instead of returning an open file - handle it returns just the filename. Secondly, it can perform various - basic checks about what the file or directory should be. - - :param exists: if set to true, the file or directory needs to exist for - this value to be valid. If this is not required and a - file does indeed not exist, then all further checks are - silently skipped. - :param file_okay: controls if a file is a possible value. - :param dir_okay: controls if a directory is a possible value. - :param writable: if true, a writable check is performed. - :param readable: if true, a readable check is performed. - :param resolve_path: if this is true, then the path is fully resolved - before the value is passed onwards. This means - that it's absolute and symlinks are resolved. It - will not expand a tilde-prefix, as this is - supposed to be done by the shell only. - :param allow_dash: If this is set to `True`, a single dash to indicate - standard streams is permitted. + """The ``Path`` type is similar to the :class:`File` type, but + returns the filename instead of an open file. Various checks can be + enabled to validate the type of file and permissions. + + :param exists: The file or directory needs to exist for the value to + be valid. If this is not set to ``True``, and the file does not + exist, then all further checks are silently skipped. + :param file_okay: Allow a file as a value value. + :param dir_okay: Allow a directory as a value. + :param writable: The file or directory must be writable. + :param readable: The file or directory must be readable. + :param resolve_path: Make the value absolute and resolve any + symlinks. A ``~`` is not expanded, as this is supposed to be + done by the shell only. + :param allow_dash: Allow a single dash as a value, which indicates + a standard stream (but does not open it). Use + :func:`~click.open_file` to handle opening this value. :param path_type: Convert the incoming path value to this type. If ``None``, keep Python's default, which is ``str``. Useful to convert to :class:`pathlib.Path`. diff --git a/src/click/utils.py b/src/click/utils.py index 16033d6..051cf70 100644 --- a/src/click/utils.py +++ b/src/click/utils.py @@ -340,33 +340,42 @@ def open_file( lazy: bool = False, atomic: bool = False, ) -> t.IO: - """This is similar to how the :class:`File` works but for manual - usage. Files are opened non lazy by default. This can open regular - files as well as stdin/stdout if ``'-'`` is passed. + """Open a file, with extra behavior to handle ``'-'`` to indicate + a standard stream, lazy open on write, and atomic write. Similar to + the behavior of the :class:`~click.File` param type. - If stdin/stdout is returned the stream is wrapped so that the context - manager will not close the stream accidentally. This makes it possible - to always use the function like this without having to worry to - accidentally close a standard stream:: + If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is + wrapped so that using it in a context manager will not close it. + This makes it possible to use the function without accidentally + closing a standard stream: + + .. code-block:: python with open_file(filename) as f: ... - .. versionadded:: 3.0 + :param filename: The name of the file to open, or ``'-'`` for + ``stdin``/``stdout``. + :param mode: The mode in which to open the file. + :param encoding: The encoding to decode or encode a file opened in + text mode. + :param errors: The error handling mode. + :param lazy: Wait to open the file until it is accessed. For read + mode, the file is temporarily opened to raise access errors + early, then closed until it is read again. + :param atomic: Write to a temporary file and replace the given file + on close. - :param filename: the name of the file to open (or ``'-'`` for stdin/stdout). - :param mode: the mode in which to open the file. - :param encoding: the encoding to use. - :param errors: the error handling for this file. - :param lazy: can be flipped to true to open the file lazily. - :param atomic: in atomic mode writes go into a temporary file and it's - moved on close. + .. versionadded:: 3.0 """ if lazy: return t.cast(t.IO, LazyFile(filename, mode, encoding, errors, atomic=atomic)) + f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) + if not should_close: f = t.cast(t.IO, KeepOpenFile(f)) + return f diff --git a/tests/test_arguments.py b/tests/test_arguments.py index f4d7afd..b4719d6 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -120,9 +120,9 @@ def test_file_args(runner): assert result.exit_code == 0 -def test_path_args(runner): +def test_path_allow_dash(runner): @click.command() - @click.argument("input", type=click.Path(dir_okay=False, allow_dash=True)) + @click.argument("input", type=click.Path(allow_dash=True)) def foo(input): click.echo(input) diff --git a/tests/test_utils.py b/tests/test_utils.py index 271177d..519b1a6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -320,6 +320,22 @@ def test_open_file(runner): assert result.output == "foobar\nmeep\n" +def test_open_file_pathlib_dash(runner): + @click.command() + @click.argument( + "filename", type=click.Path(allow_dash=True, path_type=pathlib.Path) + ) + def cli(filename): + click.echo(str(type(filename))) + + with click.open_file(filename) as f: + click.echo(f.read()) + + result = runner.invoke(cli, ["-"], input="value") + assert result.exception is None + assert result.output == "pathlib.Path\nvalue\n" + + def test_open_file_ignore_errors_stdin(runner): @click.command() @click.argument("filename") |