Skip to content

Commit 87a3e5d

Browse files
committed
Change CommandSet._cmd to a read-only property which never returns None.
This addresses issue where CommandSet methods which only run after it is registered have to either check if self._cmd is None or cast it to the CLI class to avoid type checker errors.
1 parent f85e7fc commit 87a3e5d

File tree

2 files changed

+34
-6
lines changed

2 files changed

+34
-6
lines changed

cmd2/command_definition.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,42 @@ class CommandSet(object):
9292
"""
9393

9494
def __init__(self) -> None:
95-
self._cmd: Optional[cmd2.Cmd] = None
95+
# Private reference to the CLI instance in which this CommandSet running.
96+
# This will be set when the CommandSet is registered and it should be
97+
# accessed by child classes using the self._cmd property.
98+
self.__private_cmd: Optional[cmd2.Cmd] = None
99+
96100
self._settables: Dict[str, Settable] = {}
97101
self._settable_prefix = self.__class__.__name__
98102

103+
@property
104+
def _cmd(self) -> 'cmd2.Cmd':
105+
"""
106+
Property for child classes to access self.__private_cmd.
107+
108+
Using this property ensures that self.__private_cmd has been set
109+
and it tells type checkers that it's no longer a None type.
110+
111+
Override this property if you need to change its return type to a
112+
child class of Cmd.
113+
114+
:raises: CommandSetRegistrationError if CommandSet is not registered.
115+
"""
116+
if self.__private_cmd is None:
117+
raise CommandSetRegistrationError('This CommandSet is not registered')
118+
return self.__private_cmd
119+
99120
def on_register(self, cmd: 'cmd2.Cmd') -> None:
100121
"""
101122
Called by cmd2.Cmd as the first step to registering a CommandSet. The commands defined in this class have
102123
not been added to the CLI object at this point. Subclasses can override this to perform any initialization
103124
requiring access to the Cmd object (e.g. configure commands and their parsers based on CLI state data).
104125
105126
:param cmd: The cmd2 main application
127+
:raises: CommandSetRegistrationError if CommandSet is already registered.
106128
"""
107-
if self._cmd is None:
108-
self._cmd = cmd
129+
if self.__private_cmd is None:
130+
self.__private_cmd = cmd
109131
else:
110132
raise CommandSetRegistrationError('This CommandSet has already been registered')
111133

@@ -129,7 +151,7 @@ def on_unregistered(self) -> None:
129151
Called by ``cmd2.Cmd`` after a CommandSet has been unregistered and all its commands removed from the CLI.
130152
Subclasses can override this to perform remaining cleanup steps.
131153
"""
132-
self._cmd = None
154+
self.__private_cmd = None
133155

134156
@property
135157
def settable_prefix(self) -> str:
@@ -145,7 +167,7 @@ def add_settable(self, settable: Settable) -> None:
145167
146168
:param settable: Settable object being added
147169
"""
148-
if self._cmd:
170+
if self.__private_cmd is not None:
149171
if not self._cmd.always_prefix_settables:
150172
if settable.name in self._cmd.settables.keys() and settable.name not in self._settables.keys():
151173
raise KeyError(f'Duplicate settable: {settable.name}')

tests_isolated/test_commandset/test_commandset.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,14 @@ def test_autoload_commands(command_sets_app):
151151

152152

153153
def test_custom_construct_commandsets():
154-
# Verifies that a custom initialized CommandSet loads correctly when passed into the constructor
155154
command_set_b = CommandSetB('foo')
155+
156+
# Verify that _cmd cannot be accessed until CommandSet is registered.
157+
with pytest.raises(CommandSetRegistrationError) as excinfo:
158+
command_set_b._cmd.poutput("test")
159+
assert "is not registered" in str(excinfo.value)
160+
161+
# Verifies that a custom initialized CommandSet loads correctly when passed into the constructor
156162
app = WithCommandSets(command_sets=[command_set_b])
157163

158164
cmds_cats, cmds_doc, cmds_undoc, help_topics = app._build_command_info()

0 commit comments

Comments
 (0)