Repository: python-cmd2/cmd2

Issue:
Argparse choices can't be CompletionItems
ArgparseCompleter is converting CompletionItems to normal strings when they appear in a choices list.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/1069?src=pr&el=h1) Report
> Merging [#1069](https://codecov.io/gh/python-cmd2/cmd2/pull/1069?src=pr&el=desc) (38c9a4e) into [master](https://codecov.io/gh/python-cmd2/cmd2/commit/cc9d96a7c5309bec64856e4dd9554d2cee235d23?el=desc) (cc9d96a) will **decrease** coverage by `1.26%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/python-cmd2/cmd2/pull/1069/graphs/tree.svg?width=650&height=150&src=pr&token=eks2PUwa6A)](https://codecov.io/gh/python-cmd2/cmd2/pull/1069?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1069      +/-   ##
==========================================
- Coverage   98.22%   96.96%   -1.27%     
==========================================
  Files          22       22              
  Lines        4622     4614       -8     
==========================================
- Hits         4540     4474      -66     
- Misses         82      140      +58     
```


| [Impacted Files](https://codecov.io/gh/python-cmd2/cmd2/pull/1069?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [cmd2/argparse\_completer.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1069/diff?src=pr&el=tree#diff-Y21kMi9hcmdwYXJzZV9jb21wbGV0ZXIucHk=) | `100.00% <100.00%> (ø)` | |
| [cmd2/rl\_utils.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1069/diff?src=pr&el=tree#diff-Y21kMi9ybF91dGlscy5weQ==) | `58.53% <0.00%> (-41.47%)` | :arrow_down: |
| [cmd2/clipboard.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1069/diff?src=pr&el=tree#diff-Y21kMi9jbGlwYm9hcmQucHk=) | `69.23% <0.00%> (-30.77%)` | :arrow_down: |
| [cmd2/\_\_init\_\_.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1069/diff?src=pr&el=tree#diff-Y21kMi9fX2luaXRfXy5weQ==) | `91.66% <0.00%> (-8.34%)` | :arrow_down: |
| [cmd2/cmd2.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1069/diff?src=pr&el=tree#diff-Y21kMi9jbWQyLnB5) | `96.57% <0.00%> (-1.37%)` | :arrow_down: |
| [cmd2/transcript.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1069/diff?src=pr&el=tree#diff-Y21kMi90cmFuc2NyaXB0LnB5) | `91.89% <0.00%> (-1.03%)` | :arrow_down: |
| [cmd2/parsing.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1069/diff?src=pr&el=tree#diff-Y21kMi9wYXJzaW5nLnB5) | `99.28% <0.00%> (-0.72%)` | :arrow_down: |
| [cmd2/utils.py](https://codecov.io/gh/python-cmd2/cmd2/pull/1069/diff?src=pr&el=tree#diff-Y21kMi91dGlscy5weQ==) | `96.97% <0.00%> (-0.71%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/1069?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/python-cmd2/cmd2/pull/1069?src=pr&el=footer). Last update [cc9d96a...38c9a4e](https://codecov.io/gh/python-cmd2/cmd2/pull/1069?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index 21007289..ef35eabc 100644
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -641,8 +641,11 @@ class ArgparseCompleter:
                 arg_choices.sort()
                 self._cmd2_app.matches_sorted = True
 
-            # Since choices can be various types, convert them all to strings
-            arg_choices = [str(x) for x in arg_choices]
+            # Since choices can be various types, make sure they are all strings
+            for index, choice in enumerate(arg_choices):
+                # Prevent converting anything that is already a str (i.e. CompletionItem)
+                if not isinstance(choice, str):
+                    arg_choices[index] = str(choice)
         else:
             arg_choices = getattr(arg_state.action, ATTR_CHOICES_CALLABLE, None)
             if arg_choices is None:


Test patch:
diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py
index 75f24b3e..6002a856 100644
--- a/tests/test_argparse_completer.py
+++ b/tests/test_argparse_completer.py
@@ -107,6 +107,7 @@ class ArgparseCompleterTester(cmd2.Cmd):
     int_choices = [-1, 1, -2, 2, 0, -12]
     static_choices_list = ['static', 'choices', 'stop', 'here']
     choices_from_provider = ['choices', 'provider', 'probably', 'improved']
+    completion_item_choices = [CompletionItem('choice_1', 'A description'), CompletionItem('choice_2', 'Another description')]
 
     def choices_provider(self) -> List[str]:
         """Method that provides choices"""
@@ -150,6 +151,7 @@ class ArgparseCompleterTester(cmd2.Cmd):
         nargs=argparse.ONE_OR_MORE,
     )
     choices_parser.add_argument('-i', '--int', type=int, help='a flag with an int type', choices=int_choices)
+    choices_parser.add_argument('--completion_items', help='choices are CompletionItems', choices=completion_item_choices)
 
     # Positional args for choices command
     choices_parser.add_argument("list_pos", help="a positional populated with a choices list", choices=static_choices_list)
@@ -729,6 +731,23 @@ def test_completion_items(ac_app, num_aliases, show_description):
         assert 'help' in first_result_line
 
 
+def test_completion_item_choices(ac_app):
+    text = ''
+    line = 'choices --completion_items {}'.format(text)
+    endidx = len(line)
+    begidx = endidx - len(text)
+
+    first_match = complete_tester(text, line, begidx, endidx, ac_app)
+    assert first_match is not None
+    assert len(ac_app.completion_matches) == len(ac_app.completion_item_choices)
+    assert len(ac_app.display_matches) == len(ac_app.completion_item_choices)
+
+    # Make sure a completion table was created
+    first_result_line = normalize(ac_app.formatted_completions)[1]
+    assert 'choice_1' in first_result_line
+    assert 'A description' in first_result_line
+
+
 @pytest.mark.parametrize(
     'args, completions',
     [