Skip to content

Commit 14d91a7

Browse files
authored
Made CompletionItem deepcopy-safe. (#1642)
1 parent 66a6a57 commit 14d91a7

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

cmd2/completion.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Provides classes and functions related to command-line completion."""
22

3+
import copy
34
import re
45
import sys
56
from collections.abc import (
@@ -124,6 +125,19 @@ def __post_init__(self) -> None:
124125
ru.prepare_objects_for_rendering(*renderable_data),
125126
)
126127

128+
def __deepcopy__(self, memo: dict[int, Any]) -> "CompletionItem":
129+
"""Return a shallow copy of this CompletionItem during a deepcopy operation.
130+
131+
This is necessary because cmd2 deepcopies argument parsers to keep them unique
132+
across command instances. This override prevents the deepcopying of
133+
CompletionItems stored within a parser's 'choices' list.
134+
135+
Since the 'value' and 'table_data' fields may contain complex objects which
136+
should not be deep copied, a shallow copy ensures the original object
137+
references are preserved.
138+
"""
139+
return copy.copy(self)
140+
127141
def __str__(self) -> str:
128142
"""Return the completion text."""
129143
return self.text

tests/test_completion.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import argparse
8+
import copy
89
import dataclasses
910
import enum
1011
import os
@@ -919,6 +920,36 @@ def test_remove_duplicates() -> None:
919920
assert new_meta in completions
920921

921922

923+
def test_completion_item_deepcopy() -> None:
924+
"""Test that deepcopy of a CompletionItem preserves identity of its members."""
925+
926+
class ComplexValue:
927+
pass
928+
929+
value = ComplexValue()
930+
table_data = (ComplexValue(),)
931+
orig_item = CompletionItem(
932+
value=value,
933+
text="my_text",
934+
display="my_display",
935+
display_meta="my_meta",
936+
table_data=table_data,
937+
)
938+
939+
# Perform deepcopy
940+
copied_item = copy.deepcopy(orig_item)
941+
942+
# We should have a new object
943+
assert copied_item is not orig_item
944+
945+
# But its member should be the same objects
946+
assert copied_item.value is orig_item.value
947+
assert copied_item.text is orig_item.text
948+
assert copied_item.display is orig_item.display
949+
assert copied_item.display_meta is orig_item.display_meta
950+
assert copied_item.table_data is orig_item.table_data
951+
952+
922953
def test_plain_fields() -> None:
923954
"""Test the plain text fields in CompletionItem."""
924955
display = "\x1b[31mApple\x1b[0m"

0 commit comments

Comments
 (0)