Skip to content

Commit 090995e

Browse files
committed
Merge branch 'master' into 3.0.0
2 parents b911a0b + 84a7bba commit 090995e

File tree

7 files changed

+63
-58
lines changed

7 files changed

+63
-58
lines changed

.github/workflows/build.yml

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,36 @@
22
# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
33
name: "build"
44

5-
on: [ push, pull_request ]
5+
on: [push, pull_request]
66

77
jobs:
88
build:
99
strategy:
1010
fail-fast: false
1111
matrix:
12-
os: [ ubuntu-latest, macos-latest, windows-latest ]
13-
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13" ]
12+
os: [ubuntu-latest, macos-latest, windows-latest]
13+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
1414
runs-on: ${{ matrix.os }}
1515
steps:
16-
- uses: actions/checkout@v4 # https://github.com/actions/checkout
16+
- uses: actions/checkout@v4 # https://github.com/actions/checkout
1717
with:
1818
# Only a single commit is fetched by default, for the ref/SHA that triggered the workflow.
1919
# Set fetch-depth: 0 to fetch all history for all branches and tags.
2020
fetch-depth: 0 # Needed for setuptools_scm to work correctly
21-
- uses: actions/setup-python@v5 # https://github.com/actions/setup-python
21+
- name: Install uv
22+
uses: astral-sh/setup-uv@v3
23+
24+
- name: Set up Python ${{ matrix.python-version }}
25+
uses: actions/setup-python@v5
2226
with:
2327
python-version: ${{ matrix.python-version }}
2428
allow-prereleases: true
25-
- name: Install dependencies
26-
run: python -m pip install --upgrade pip setuptools setuptools-scm nox
27-
- name: Run tests and post coverage results
28-
env:
29-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30-
run: python -m nox --non-interactive --session tests-${{ matrix.python-version }} # Run nox for a single version of Python
29+
30+
- name: Install the project
31+
run: uv sync --all-extras --dev
32+
33+
- name: Run tests
34+
run: uv run inv pytest --junit --no-pty --base
35+
36+
- name: Run isolated tests
37+
run: uv run inv pytest --junit --no-pty --isolated

CHANGELOG.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@
66
See [custom_parser.py](https://github.com/python-cmd2/cmd2/blob/master/examples/custom_parser.py)
77
example for more details.
88

9-
# 2.5.2 (November 3, 2024)
9+
## 2.5.3 (November 5, 2024)
10+
* Enhancements
11+
* Changed `CommandSet._cmd` to a read-only property which never returns `None` because it
12+
is meant to be called after the `CommandSet` is registered. This addresses type checker
13+
errors that occurred if `CommandSet._cmd` wasn't cast or checked if `None` before use.
14+
15+
## 2.5.2 (November 3, 2024)
1016
* Bug Fixes
1117
* Fixed default `pytest` execution when not using cmd2's custom `invoke` command via `inv pytest`
12-
13-
# 2.5.1 (November 2, 2024)
18+
19+
## 2.5.1 (November 2, 2024)
1420
* Bug Fixes
1521
* Fixed readline bug when using `ipy` command with `gnureadline` and Python 3.13
1622

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.__cmd_internal: 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.__cmd_internal.
107+
108+
Using this property ensures that self.__cmd_internal 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.__cmd_internal is None:
117+
raise CommandSetRegistrationError('This CommandSet is not registered')
118+
return self.__cmd_internal
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.__cmd_internal is None:
130+
self.__cmd_internal = 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.__cmd_internal = 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.__cmd_internal 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}')

noxfile.py

Lines changed: 0 additions & 25 deletions
This file was deleted.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ dev = [
5353
"doc8",
5454
"invoke",
5555
"mypy",
56-
"nox",
5756
"pytest",
5857
"pytest-cov",
5958
"pytest-mock",
@@ -312,6 +311,7 @@ packages = ["cmd2"]
312311

313312
[tool.uv]
314313
dev-dependencies = [
314+
"build",
315315
"cmd2-ext-test",
316316
"codecov",
317317
"doc8",

tasks.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def rmrf(items, verbose=True):
4949

5050
#####
5151
#
52-
# pytest, nox, pylint, and codecov
52+
# pytest, pylint, and codecov
5353
#
5454
#####
5555

@@ -117,17 +117,6 @@ def mypy_clean(context):
117117
namespace_clean.add_task(mypy_clean, 'mypy')
118118

119119

120-
@invoke.task
121-
def nox_clean(context):
122-
"""Remove nox virtualenvs and logs"""
123-
# pylint: disable=unused-argument
124-
with context.cd(TASK_ROOT_STR):
125-
rmrf('.nox')
126-
127-
128-
namespace_clean.add_task(nox_clean, 'nox')
129-
130-
131120
#####
132121
#
133122
# documentation

tests_isolated/test_commandset/test_commandset.py

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

151151

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

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

0 commit comments

Comments
 (0)