summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2021-10-10 11:08:13 -0700
committerDavid Lord <davidism@gmail.com>2021-10-10 11:08:13 -0700
commit65eceb08e392e74dcc761be2090e951274ccbe36 (patch)
tree32d29455afd15ba6239ba3399f23c56e18abdb85
parent0a81393fdf41edb0ab9d2f527eccdc8ce38d7d42 (diff)
parent41f5b7a7967bb65910e8837bd4e8542a18feec6c (diff)
downloadclick-65eceb08e392e74dcc761be2090e951274ccbe36.tar.gz
Merge branch '8.0.x'
-rw-r--r--CHANGES.rst13
-rw-r--r--src/click/core.py4
-rw-r--r--src/click/termui.py6
-rw-r--r--src/click/types.py19
-rw-r--r--tests/conftest.py32
-rw-r--r--tests/test_commands.py16
-rw-r--r--tests/test_types.py58
-rw-r--r--tests/test_utils.py4
8 files changed, 74 insertions, 78 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 28d4e23..251b2e4 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -9,6 +9,19 @@ Unreleased
the default if it is ``True``. :issue:`1971`:
+Version 8.0.3
+-------------
+
+Released 2021-10-10
+
+- Fix issue with ``Path(resolve_path=True)`` type creating invalid
+ paths. :issue:`2088`
+- Importing ``readline`` does not cause the ``confirm()`` prompt to
+ disappear when pressing backspace. :issue:`2092`
+- Any default values injected by ``invoke()`` are cast to the
+ corresponding parameter's type. :issue:`2089, 2090`
+
+
Version 8.0.2
-------------
diff --git a/src/click/core.py b/src/click/core.py
index dae3e21..5a4129c 100644
--- a/src/click/core.py
+++ b/src/click/core.py
@@ -739,7 +739,9 @@ class Context:
for param in other_cmd.params:
if param.name not in kwargs and param.expose_value:
- kwargs[param.name] = param.get_default(ctx) # type: ignore
+ kwargs[param.name] = param.type_cast_value( # type: ignore
+ ctx, param.get_default(ctx)
+ )
# Track all kwargs as params, so that forward() will pass
# them on in subsequent calls.
diff --git a/src/click/termui.py b/src/click/termui.py
index a023f42..cf8d5f1 100644
--- a/src/click/termui.py
+++ b/src/click/termui.py
@@ -231,8 +231,10 @@ def confirm(
try:
# Write the prompt separately so that we get nice
# coloring through colorama on Windows
- echo(prompt, nl=False, err=err)
- value = visible_prompt_func("").lower().strip()
+ echo(prompt.rstrip(" "), nl=False, err=err)
+ # Echo a space to stdout to work around an issue where
+ # readline causes backspace to clear the whole line.
+ value = visible_prompt_func(" ").lower().strip()
except (KeyboardInterrupt, EOFError):
raise Abort() from None
if value in ("y", "yes"):
diff --git a/src/click/types.py b/src/click/types.py
index a7de43b..103d218 100644
--- a/src/click/types.py
+++ b/src/click/types.py
@@ -836,20 +836,11 @@ class Path(ParamType):
if not is_dash:
if self.resolve_path:
- # Get the absolute directory containing the path.
- dir_ = os.path.dirname(os.path.abspath(rv))
-
- # Resolve a symlink. realpath on Windows Python < 3.9
- # doesn't resolve symlinks. This might return a relative
- # path even if the path to the link is absolute.
- if os.path.islink(rv):
- rv = os.readlink(rv)
-
- # Join dir_ with the resolved symlink if the resolved
- # path is relative. This will make it relative to the
- # original containing directory.
- if not os.path.isabs(rv):
- rv = os.path.join(dir_, rv)
+ # os.path.realpath doesn't resolve symlinks on Windows
+ # until Python 3.8. Use pathlib for now.
+ import pathlib
+
+ rv = os.fsdecode(pathlib.Path(rv).resolve())
try:
st = os.stat(rv)
diff --git a/tests/conftest.py b/tests/conftest.py
index d33df11..a71db7d 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,5 +1,4 @@
import os
-import shutil
import tempfile
import pytest
@@ -12,20 +11,17 @@ def runner(request):
return CliRunner()
-def check_symlink_impl():
- """This function checks if using symlinks is allowed
- on the host machine"""
- tempdir = tempfile.mkdtemp(prefix="click-")
- test_pth = os.path.join(tempdir, "check_sym_impl")
- sym_pth = os.path.join(tempdir, "link")
- open(test_pth, "w").close()
- rv = True
- try:
- os.symlink(test_pth, sym_pth)
- except (NotImplementedError, OSError):
- # Creating symlinks on Windows require elevated access.
- # OSError is thrown if the function is called without it.
- rv = False
- finally:
- shutil.rmtree(tempdir, ignore_errors=True)
- return rv
+def _check_symlinks_supported():
+ with tempfile.TemporaryDirectory(prefix="click-pytest-") as tempdir:
+ target = os.path.join(tempdir, "target")
+ open(target, "w").close()
+ link = os.path.join(tempdir, "link")
+
+ try:
+ os.symlink(target, link)
+ return True
+ except OSError:
+ return False
+
+
+symlinks_supported = _check_symlinks_supported()
diff --git a/tests/test_commands.py b/tests/test_commands.py
index 9ebf612..788398c 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -246,15 +246,17 @@ def test_other_command_invoke_with_defaults(runner):
return ctx.invoke(other_cmd)
@click.command()
- @click.option("--foo", type=click.INT, default=42)
+ @click.option("-a", type=click.INT, default=42)
+ @click.option("-b", type=click.INT, default="15")
+ @click.option("-c", multiple=True)
@click.pass_context
- def other_cmd(ctx, foo):
- assert ctx.info_name == "other-cmd"
- click.echo(foo)
+ def other_cmd(ctx, a, b, c):
+ return ctx.info_name, a, b, c
- result = runner.invoke(cli, [])
- assert not result.exception
- assert result.output == "42\n"
+ result = runner.invoke(cli, standalone_mode=False)
+ # invoke should type cast default values, str becomes int, empty
+ # multiple should be empty tuple instead of None
+ assert result.return_value == ("other-cmd", 42, 15, ())
def test_invoked_subcommand(runner):
diff --git a/tests/test_types.py b/tests/test_types.py
index dff8172..df44d8a 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -2,7 +2,7 @@ import os.path
import pathlib
import pytest
-from conftest import check_symlink_impl
+from conftest import symlinks_supported
import click
@@ -104,37 +104,27 @@ def test_path_type(runner, cls, expect):
assert result.return_value == expect
-@pytest.mark.skipif(not check_symlink_impl(), reason="symlink not allowed on device")
-@pytest.mark.parametrize(
- ("sym_file", "abs_fun"),
- [
- (("relative_symlink",), os.path.basename),
- (("test", "absolute_symlink"), lambda x: x),
- ],
+@pytest.mark.skipif(
+ not symlinks_supported, reason="The current OS or FS doesn't support symlinks."
)
-def test_symlink_resolution(tmpdir, sym_file, abs_fun):
- """This test ensures symlinks are properly resolved by click"""
- tempdir = str(tmpdir)
- real_path = os.path.join(tempdir, "test_file")
- sym_path = os.path.join(tempdir, *sym_file)
-
- # create dirs and files
- os.makedirs(os.path.join(tempdir, "test"), exist_ok=True)
- open(real_path, "w").close()
- os.symlink(abs_fun(real_path), sym_path)
-
- # test
- ctx = click.Context(click.Command("do_stuff"))
- rv = click.Path(resolve_path=True).convert(sym_path, None, ctx)
-
- # os.readlink prepends path prefixes to absolute
- # links in windows.
- # https://docs.microsoft.com/en-us/windows/win32/
- # ... fileio/naming-a-file#win32-file-namespaces
- #
- # Here we strip win32 path prefix from the resolved path
- rv_drive, rv_path = os.path.splitdrive(rv)
- stripped_rv_drive = rv_drive.split(os.path.sep)[-1]
- rv = os.path.join(stripped_rv_drive, rv_path)
-
- assert rv == real_path
+def test_path_resolve_symlink(tmp_path, runner):
+ test_file = tmp_path / "file"
+ test_file_str = os.fsdecode(test_file)
+ test_file.write_text("")
+
+ path_type = click.Path(resolve_path=True)
+ param = click.Argument(["a"], type=path_type)
+ ctx = click.Context(click.Command("cli", params=[param]))
+
+ test_dir = tmp_path / "dir"
+ test_dir.mkdir()
+
+ abs_link = test_dir / "abs"
+ abs_link.symlink_to(test_file)
+ abs_rv = path_type.convert(os.fsdecode(abs_link), param, ctx)
+ assert abs_rv == test_file_str
+
+ rel_link = test_dir / "rel"
+ rel_link.symlink_to(pathlib.Path("..") / "file")
+ rel_rv = path_type.convert(os.fsdecode(rel_link), param, ctx)
+ assert rel_rv == test_file_str
diff --git a/tests/test_utils.py b/tests/test_utils.py
index d21b246..271177d 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -275,8 +275,8 @@ def test_echo_writing_to_standard_error(capfd, monkeypatch):
emulate_input("y\n")
click.confirm("Prompt to stderr", err=True)
out, err = capfd.readouterr()
- assert out == ""
- assert err == "Prompt to stderr [y/N]: "
+ assert out == " "
+ assert err == "Prompt to stderr [y/N]:"
monkeypatch.setattr(click.termui, "isatty", lambda x: True)
monkeypatch.setattr(click.termui, "getchar", lambda: " ")