summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2021-10-25 08:46:34 -0700
committerDavid Lord <davidism@gmail.com>2021-10-25 08:46:34 -0700
commit0c85d80b07742b9f9ed4d583d694c37fce1d1292 (patch)
tree0696c73b5d1abfe4d5eb1f6ef1328985d2a710e9
parent65eceb08e392e74dcc761be2090e951274ccbe36 (diff)
parente415d3a811fda506c8d7917e615b97ae9dcd1194 (diff)
downloadclick-0c85d80b07742b9f9ed4d583d694c37fce1d1292.tar.gz
Merge branch '8.0.x'
-rw-r--r--CHANGES.rst9
-rw-r--r--src/click/_compat.py6
-rw-r--r--src/click/types.py37
-rw-r--r--src/click/utils.py39
-rw-r--r--tests/test_arguments.py4
-rw-r--r--tests/test_utils.py16
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")