Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ plugins/* @anselor

# Unit and Integration Tests
tests/* @kmvanbrunt @tleonhardt
tests_isolated/* @anselor

# Top-level project stuff
.coveragerc @tleonhardt
Expand Down
7 changes: 3 additions & 4 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,10 +346,9 @@ This bit is up to you!

The cmd2 project directory structure is pretty simple and straightforward. All actual code for cmd2
is located underneath the `cmd2` directory. The code to generate the documentation is in the `docs`
directory. Unit tests are in the `tests` directory. Integration tests are in the `tests_isolated`
directory. The `examples` directory contains examples of how to use cmd2. There are various other
files in the root directory, but these are primarily related to continuous integration and release
deployment.
directory. Unit and integration tests are in the `tests` directory. The `examples` directory
contains examples of how to use cmd2. There are various other files in the root directory, but these
are primarily related to continuous integration and release deployment.

#### Changes to the documentation files

Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ jobs:
- name: Run tests
run: uv run python -Xutf8 -m pytest --cov --cov-config=pyproject.toml --cov-report=xml tests

- name: Run isolated tests
run:
uv run python -Xutf8 -m pytest --cov --cov-config=pyproject.toml --cov-report=xml
tests_isolated

- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ time reading the [rich documentation](https://rich.readthedocs.io/).
`value` property
- Removed redundant setting of a parser's `prog` value in the `with_argparser()` decorator, as
this is now handled centrally in `Cmd._build_parser()`
- The `auto_load_commands` argument to `cmd2.Cmd.__init__` now defaults to `False`

- Enhancements
- Enhanced all print methods (`poutput()`, `perror()`, `ppaged()`, etc.) to natively render
Expand Down
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ typecheck: ## Perform type checking
test: ## Test the code with pytest.
@echo "🚀 Testing code: Running pytest"
@uv run python -Xutf8 -m pytest --cov --cov-config=pyproject.toml --cov-report=xml tests
@uv run python -Xutf8 -m pytest --cov --cov-config=pyproject.toml --cov-report=xml tests_isolated

.PHONY: docs-test
docs-test: ## Test if documentation can be built without warnings or errors
Expand Down
2 changes: 1 addition & 1 deletion cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ def __init__(
terminators: list[str] | None = None,
shortcuts: dict[str, str] | None = None,
command_sets: Iterable[CommandSet] | None = None,
auto_load_commands: bool = True,
auto_load_commands: bool = False,
allow_clipboard: bool = True,
suggest_similar_command: bool = False,
) -> None:
Expand Down
1 change: 0 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@ component_management:
ignore:
- "examples" # ignore example code folder
- "tests" # ignore unit test code folder
- "tests_isolated" # ignore integration test code folder
20 changes: 11 additions & 9 deletions docs/features/modular_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@

Cmd2 also enables developers to modularize their command definitions into
[CommandSet][cmd2.CommandSet] objects. CommandSets represent a logical grouping of commands within a
`cmd2` application. By default, all `CommandSet` objects will be discovered and loaded automatically
when the [cmd2.Cmd][] class is instantiated with this mixin. This also enables the developer to
dynamically add/remove commands from the cmd2 application. This could be useful for loadable plugins
that add additional capabilities. Additionally, it allows for object-oriented encapsulation and
garbage collection of state that is specific to a CommandSet.
`cmd2` application. By default, `CommandSet` objects need to be manually registered. However, it is
possible for all `CommandSet` objects to be discovered and loaded automatically when the
[cmd2.Cmd][] class is instantiated with this mixin by setting `auto_load_commands=True`. This also
enables the developer to dynamically add/remove commands from the `cmd2` application. This could be
useful for loadable plugins that add additional capabilities. Additionally, it allows for
object-oriented encapsulation and garbage collection of state that is specific to a CommandSet.

### Features

- Modular Command Sets - Commands can be broken into separate modules rather than in one god class
holding all commands.
- Automatic Command Discovery - In your application, merely defining and importing a CommandSet is
sufficient for `cmd2` to discover and load your command. No manual registration is necessary.
sufficient for `cmd2` to discover and load your command if you set `auto_load_commands=True`. No
manual registration is necessary.
- Dynamically Loadable/Unloadable Commands - Command functions and CommandSets can both be loaded
and unloaded dynamically during application execution. This can enable features such as
dynamically loaded modules that add additional commands.
Expand Down Expand Up @@ -71,7 +73,7 @@ class ExampleApp(cmd2.Cmd):
CommandSets are automatically loaded. Nothing needs to be done.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
super().__init__(*args, auto_load_commands=True, **kwargs)

def do_something(self, arg):
self.poutput('this is the something command')
Expand Down Expand Up @@ -106,7 +108,7 @@ class ExampleApp(cmd2.Cmd):
"""
def __init__(self, *args, **kwargs):
# gotta have this or neither the plugin or cmd2 will initialize
super().__init__(*args, **kwargs)
super().__init__(*args, auto_load_commands=True, **kwargs)

def do_something(self, arg):
self.last_result = 5
Expand Down Expand Up @@ -289,7 +291,7 @@ class LoadableVegetables(CommandSet):

class ExampleApp(cmd2.Cmd):
"""
CommandSets are automatically loaded. Nothing needs to be done.
CommandSets are loaded dynamically at runtime via other commands.
"""

def __init__(self, *args, **kwargs):
Expand Down
8 changes: 8 additions & 0 deletions docs/upgrades.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,11 @@ however it now inherits from `argparse.HelpFormatter`. If you want RawText behav
The benefit is that your `cmd2` applications now have more aesthetically pleasing help which
includes color to make it quicker and easier to visually parse help text. This works for all
supported versions of Python.

### Other Changes

- The `auto_load_commands` argument to `cmd2.Cmd.__init__` now defaults to `False`
- Replaced `Settable.get_value()` and `Settable.set_value()` methods with a more Pythonic `value`
property
- Removed redundant setting of a parser's `prog` value in the `with_argparser()` decorator, as this
is now handled centrally in `Cmd._build_parser()`
19 changes: 9 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,16 @@ disallow_untyped_defs = true
exclude = [
"^.git/",
"^.venv/",
"^build/", # .build directory
"^docs/", # docs directory
"^build/", # .build directory
"^docs/", # docs directory
"^dist/",
"^examples/", # examples directory
"^plugins/*", # plugins directory
"^noxfile\\.py$", # nox config file
"setup\\.py$", # any files named setup.py
"^examples/", # examples directory
"^plugins/*", # plugins directory
"^noxfile\\.py$", # nox config file
"setup\\.py$", # any files named setup.py
"^site/",
"^tasks\\.py$", # tasks.py invoke config file
"^tests/", # tests directory
"^tests_isolated/", # tests_isolated directory
"^tasks\\.py$", # tasks.py invoke config file
"^tests/", # tests directory
]
files = ['.']
show_column_numbers = true
Expand Down Expand Up @@ -270,7 +269,7 @@ mccabe.max-complexity = 49
"plugins/*.py" = ["INP001"] # Module is part of an implicit namespace

# Ingore various rulesets in test and plugins directories
"{plugins,tests,tests_isolated}/*.py" = [
"{plugins,tests}/*.py" = [
"ANN", # Ignore all type annotation rules in test folders
"ARG", # Ignore all unused argument warnings in test folders
"D", # Ignore all pydocstyle rules in test folders
Expand Down
16 changes: 3 additions & 13 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,18 @@ def rmrf(items: str | list[str] | set[str], verbose: bool = True) -> None:


@invoke.task()
def pytest(context: Context, junit: bool = False, pty: bool = True, base: bool = False, isolated: bool = False) -> None:
def pytest(context: Context, junit: bool = False, pty: bool = True) -> None:
"""Run tests and code coverage using pytest."""
with context.cd(TASK_ROOT_STR):
command_str = 'pytest '
command_str += ' --cov=cmd2 '
command_str += ' --cov-append --cov-report=term --cov-report=html '

if not base and not isolated:
base = True
isolated = True

if junit:
command_str += ' --junitxml=junit/test-results.xml '

if base:
tests_cmd = command_str + ' tests'
context.run(tests_cmd, pty=pty)
if isolated:
for _root, dirnames, _ in os.walk(str(TASK_ROOT / 'tests_isolated')):
for dir_name in dirnames:
if dir_name.startswith('test_'):
context.run(command_str + ' tests_isolated/' + dir_name)
tests_cmd = command_str + ' tests'
context.run(tests_cmd, pty=pty)


namespace.add_task(pytest)
Expand Down
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,13 @@ class WithCommandSets(ExternalTestMixin, cmd2.Cmd):

def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)


@pytest.fixture
def autoload_command_sets_app():
return WithCommandSets(auto_load_commands=True)


@pytest.fixture
def manual_command_sets_app():
return WithCommandSets(auto_load_commands=False)
Loading
Loading