Repository: cookiecutter/cookiecutter

Issue:
Add support for click 8.0.0
### Description:

We should add support to click 8.0.0, there are some breaking changes like #1558 


Discussion:
ssbarnea: @cjolowicz can you please rebase this? Basically dependencies moved to setup.cfg. That is indeed a change that we really want in next release.
cjolowicz: @ssbarnea done :)

Patch:
diff --git a/cookiecutter/prompt.py b/cookiecutter/prompt.py
index dfb8f32..4b8b2fb 100644
--- a/cookiecutter/prompt.py
+++ b/cookiecutter/prompt.py
@@ -1,4 +1,5 @@
 """Functions for prompting the user for project info."""
+import functools
 import json
 from collections import OrderedDict
 
@@ -78,11 +79,18 @@ def read_user_choice(var_name, options):
     return choice_map[user_choice]
 
 
-def process_json(user_value):
+DEFAULT_DISPLAY = 'default'
+
+
+def process_json(user_value, default_value=None):
     """Load user-supplied value as a JSON dict.
 
     :param str user_value: User-supplied value to load as a JSON dict
     """
+    if user_value == DEFAULT_DISPLAY:
+        # Return the given default w/o any processing
+        return default_value
+
     try:
         user_dict = json.loads(user_value, object_pairs_hook=OrderedDict)
     except Exception:
@@ -107,15 +115,16 @@ def read_user_dict(var_name, default_value):
     if not isinstance(default_value, dict):
         raise TypeError
 
-    default_display = 'default'
-
     user_value = click.prompt(
-        var_name, default=default_display, type=click.STRING, value_proc=process_json
+        var_name,
+        default=DEFAULT_DISPLAY,
+        type=click.STRING,
+        value_proc=functools.partial(process_json, default_value=default_value),
     )
 
-    if user_value == default_display:
-        # Return the given default w/o any processing
-        return default_value
+    if click.__version__.startswith("7.") and user_value == DEFAULT_DISPLAY:
+        # click 7.x does not invoke value_proc on the default value.
+        return default_value  # pragma: no cover
     return user_value
 
 
diff --git a/setup.cfg b/setup.cfg
index 609c2dc..b9f2cf7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -62,7 +62,7 @@ setup_requires =
 install_requires =
     binaryornot>=0.4.4
     Jinja2>=2.7,<4.0.0
-    click>=7.0,<8.0.0
+    click>=7.0,<9.0.0
     pyyaml>=5.3.1
     jinja2-time>=0.2.0
     python-slugify>=4.0.0


Test patch:
diff --git a/tests/test_read_user_dict.py b/tests/test_read_user_dict.py
index ccf6322..0ce50ef 100644
--- a/tests/test_read_user_dict.py
+++ b/tests/test_read_user_dict.py
@@ -92,35 +92,35 @@ def test_should_call_prompt_with_process_json(mocker):
 
     read_user_dict('name', {'project_slug': 'pytest-plugin'})
 
-    assert mock_prompt.call_args == mocker.call(
-        'name', type=click.STRING, default='default', value_proc=process_json,
-    )
+    args, kwargs = mock_prompt.call_args
+
+    assert args == ('name',)
+    assert kwargs['type'] == click.STRING
+    assert kwargs['default'] == 'default'
+    assert kwargs['value_proc'].func == process_json
 
 
-def test_should_not_call_process_json_default_value(mocker, monkeypatch):
-    """Make sure that `process_json` is not called when using default value."""
-    mock_process_json = mocker.patch('cookiecutter.prompt.process_json', autospec=True)
+def test_should_not_load_json_from_sentinel(mocker):
+    """Make sure that `json.loads` is not called when using default value."""
+    mock_json_loads = mocker.patch(
+        'cookiecutter.prompt.json.loads', autospec=True, return_value={}
+    )
 
     runner = click.testing.CliRunner()
     with runner.isolation(input="\n"):
         read_user_dict('name', {'project_slug': 'pytest-plugin'})
 
-    mock_process_json.assert_not_called()
+    mock_json_loads.assert_not_called()
 
 
-def test_read_user_dict_default_value(mocker):
+@pytest.mark.parametrize("input", ["\n", "default\n"])
+def test_read_user_dict_default_value(mocker, input):
     """Make sure that `read_user_dict` returns the default value.
 
     Verify return of a dict variable rather than the display value.
     """
-    mock_prompt = mocker.patch(
-        'cookiecutter.prompt.click.prompt', autospec=True, return_value='default',
-    )
-
-    val = read_user_dict('name', {'project_slug': 'pytest-plugin'})
-
-    assert mock_prompt.call_args == mocker.call(
-        'name', type=click.STRING, default='default', value_proc=process_json,
-    )
+    runner = click.testing.CliRunner()
+    with runner.isolation(input=input):
+        val = read_user_dict('name', {'project_slug': 'pytest-plugin'})
 
     assert val == {'project_slug': 'pytest-plugin'}