Repository: pallets/click (python)

Issue:
show_default and default_map don't play nice together
```python
#!/usr/bin/env python
import click

@click.command(context_settings={
    "show_default": True, "default_map": {"a": "b"}
})
@click.option("-a", default="c")
def func(a):
    print(a)

if __name__ == "__main__":
    func()
```
```console
$ # This is expected
$ ./cli.py
b
$ # This is not (default should be b)
$ ./cli.py --help
Usage: cli.py [OPTIONS]

Options:
  -a TEXT  [default: c]
  --help   Show this message and exit.  [default: False]
```

### Expected Behavior

I expect the default shown to be identical to the one being used.

### Actual Behavior

The default shown was the value provided as `default`, without taking the `default_map` into account

### Environment

* Python version: 3.7
* Click version: ``click==7.1.2``


Pull request description:
show_default looks at default_map
Closes #1548 

It's my first PR on this project, so I may have missed things.

- Not sure if a ``versionchanged`` marker is needed to fix bugs.
- If a callable is passed as default, it will not be evaluated, but if it's passed as default map, it will. Is it a problem ?
- I've included a single-character typo fix in a related entry of the changelog. Is it ok ?

Also, I know how feedback is always nice so:
- Contributing doc was 👌 
- I have already worked with pytest-click, so nothing new here
- A bit confusing whether the fix was supposed to go to "commands", "options" or "defaults", so I made a choice :)
- Overall, very easy to contribute. Thanks and congrats.

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 95d16b7..04bf4c2 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -134,6 +134,9 @@ Unreleased
 -   Handle empty env vars as though the option were not passed. This
     extends the change introduced in 7.1 to be consistent in more cases.
     :issue:`1285`
+-   ``Parameter.get_default()`` checks ``Context.default_map`` to
+    handle overrides consistently in help text, ``invoke()``, and
+    prompts. :issue:`1548`
 
 
 Version 7.1.2
@@ -182,7 +185,7 @@ Released 2020-03-09
     :issue:`1277`, :pr:`1318`
 -   Add ``no_args_is_help`` option to ``click.Command``, defaults to
     False :pr:`1167`
--   Add ``show_defaults`` parameter to ``Context`` to enable showing
+-   Add ``show_default`` parameter to ``Context`` to enable showing
     defaults globally. :issue:`1018`
 -   Handle ``env MYPATH=''`` as though the option were not passed.
     :issue:`1196`
diff --git a/src/click/core.py b/src/click/core.py
index 53b175b..b62a591 100644
--- a/src/click/core.py
+++ b/src/click/core.py
@@ -1,6 +1,5 @@
 import enum
 import errno
-import inspect
 import os
 import sys
 from contextlib import contextmanager
@@ -613,15 +612,23 @@ class Context:
             self.obj = rv = object_type()
         return rv
 
-    def lookup_default(self, name):
-        """Looks up the default for a parameter name.  This by default
-        looks into the :attr:`default_map` if available.
+    def lookup_default(self, name, call=True):
+        """Get the default for a parameter from :attr:`default_map`.
+
+        :param name: Name of the parameter.
+        :param call: If the default is a callable, call it. Disable to
+            return the callable instead.
+
+        .. versionchanged:: 8.0
+            Added the ``call`` parameter.
         """
         if self.default_map is not None:
-            rv = self.default_map.get(name)
-            if callable(rv):
-                rv = rv()
-            return rv
+            value = self.default_map.get(name)
+
+            if call and callable(value):
+                return value()
+
+            return value
 
     def fail(self, message):
         """Aborts the execution of the program with a specific error
@@ -1920,14 +1927,33 @@ class Parameter:
             metavar += "..."
         return metavar
 
-    def get_default(self, ctx):
-        """Given a context variable this calculates the default value."""
-        # Otherwise go with the regular default.
-        if callable(self.default):
-            rv = self.default()
-        else:
-            rv = self.default
-        return self.type_cast_value(ctx, rv)
+    def get_default(self, ctx, call=True):
+        """Get the default for the parameter. Tries
+        :meth:`Context.lookup_value` first, then the local default.
+
+        :param ctx: Current context.
+        :param call: If the default is a callable, call it. Disable to
+            return the callable instead.
+
+        .. versionchanged:: 8.0
+            Looks at ``ctx.default_map`` first.
+
+        .. versionchanged:: 8.0
+            Added the ``call`` parameter.
+        """
+        value = ctx.lookup_default(self.name, call=False)
+
+        if value is None:
+            value = self.default
+
+        if callable(value):
+            if not call:
+                # Don't type cast the callable.
+                return value
+
+            value = value()
+
+        return self.type_cast_value(ctx, value)
 
     def add_to_parser(self, parser, ctx):
         pass
@@ -2355,12 +2381,15 @@ class Option(Parameter):
                     else envvar
                 )
                 extra.append(f"env var: {var_str}")
-        if self.default is not None and (self.show_default or ctx.show_default):
+
+        default_value = self.get_default(ctx, call=False)
+
+        if default_value is not None and (self.show_default or ctx.show_default):
             if isinstance(self.show_default, str):
                 default_string = f"({self.show_default})"
-            elif isinstance(self.default, (list, tuple)):
-                default_string = ", ".join(str(d) for d in self.default)
-            elif inspect.isfunction(self.default):
+            elif isinstance(default_value, (list, tuple)):
+                default_string = ", ".join(str(d) for d in default_value)
+            elif callable(default_value):
                 default_string = "(dynamic)"
             elif self.is_bool_flag and self.secondary_opts:
                 # For boolean flags that have distinct True/False opts,
@@ -2369,7 +2398,8 @@ class Option(Parameter):
                     (self.opts if self.default else self.secondary_opts)[0]
                 )[1]
             else:
-                default_string = self.default
+                default_string = default_value
+
             extra.append(f"default: {default_string}")
 
         if isinstance(self.type, _NumberRangeBase):
@@ -2386,7 +2416,7 @@ class Option(Parameter):
 
         return ("; " if any_prefix_is_slash else " / ").join(rv), help
 
-    def get_default(self, ctx):
+    def get_default(self, ctx, call=True):
         # If we're a non boolean flag our default is more complex because
         # we need to look at all flags in the same group to figure out
         # if we're the the default one in which case we return the flag
@@ -2395,9 +2425,10 @@ class Option(Parameter):
             for param in ctx.command.params:
                 if param.name == self.name and param.default:
                     return param.flag_value
+
             return None
 
-        return super().get_default(ctx)
+        return super().get_default(ctx, call=call)
 
     def prompt_for_value(self, ctx):
         """This is an alternative flow that can be activated in the full


Test patch:
diff --git a/tests/test_options.py b/tests/test_options.py
index 2e50594..eea68c1 100644
--- a/tests/test_options.py
+++ b/tests/test_options.py
@@ -188,6 +188,18 @@ def test_multiple_default_help(runner):
     assert "1, 2" in result.output
 
 
+def test_show_default_default_map(runner):
+    @click.command()
+    @click.option("--arg", default="a", show_default=True)
+    def cmd(arg):
+        click.echo(arg)
+
+    result = runner.invoke(cmd, ["--help"], default_map={"arg": "b"})
+
+    assert not result.exception
+    assert "[default: b]" in result.output
+
+
 def test_multiple_default_type():
     opt = click.Option(["-a"], multiple=True, default=(1, 2))
     assert opt.nargs == 1