diff options
author | David Lord <davidism@gmail.com> | 2021-10-10 11:08:13 -0700 |
---|---|---|
committer | David Lord <davidism@gmail.com> | 2021-10-10 11:08:13 -0700 |
commit | 65eceb08e392e74dcc761be2090e951274ccbe36 (patch) | |
tree | 32d29455afd15ba6239ba3399f23c56e18abdb85 | |
parent | 0a81393fdf41edb0ab9d2f527eccdc8ce38d7d42 (diff) | |
parent | 41f5b7a7967bb65910e8837bd4e8542a18feec6c (diff) | |
download | click-65eceb08e392e74dcc761be2090e951274ccbe36.tar.gz |
Merge branch '8.0.x'
-rw-r--r-- | CHANGES.rst | 13 | ||||
-rw-r--r-- | src/click/core.py | 4 | ||||
-rw-r--r-- | src/click/termui.py | 6 | ||||
-rw-r--r-- | src/click/types.py | 19 | ||||
-rw-r--r-- | tests/conftest.py | 32 | ||||
-rw-r--r-- | tests/test_commands.py | 16 | ||||
-rw-r--r-- | tests/test_types.py | 58 | ||||
-rw-r--r-- | tests/test_utils.py | 4 |
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: " ") |