-
Notifications
You must be signed in to change notification settings - Fork 127
Load/Unload commands at runtime - Implements #943 #945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
anselor
commented
Jun 15, 2020
- Group commands in CommandSets to be installed/uninstalled as a batch. Commands within a CommandSet can be optionally tagged with at default category. Individual commands may override the default category.
- Install/Uninstall individual command functions at runtime
- Tag command functions for automatic discovery and load
- Automatic discovery of complete_ and help_ functions that are contained in the same module as command functions.
- Automatic discovery and load of CommandSets and registered command functions that can be disabled in the constructor
Codecov Report
@@ Coverage Diff @@
## master #945 +/- ##
==========================================
- Coverage 97.71% 97.66% -0.06%
==========================================
Files 17 18 +1
Lines 4155 4318 +163
==========================================
+ Hits 4060 4217 +157
- Misses 95 101 +6
Continue to review full report at Codecov.
|
|
On my laptop I'm getting the following error trying to build the Sphinx docs using Python 3.8.2 and Sphinx 3.1.1: File "/Users/toddleonhardt/src/cmd2/.venv/lib/python3.8/site-packages/sphinx/util/logging.py", line 421, in filter
raise exc
sphinx.errors.SphinxWarning: /Users/toddleonhardt/src/cmd2/cmd2/cmd2.py:docstring of cmd2.Cmd::py:class reference target not found: cmd2.command_definition.CommandSet |
tleonhardt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a quick look, but I need to take a more in-depth look later.
This is a significant change.
@kmvanbrunt @kotfu I would appreciate feedback from both of you on this one as well.
That's weird. I ran sphinx on mine and didn't have any problems. I'm running Ubuntu 20.04 through WSL with the latest. I'll have to check when I get back on that machine what the versions were. |
tleonhardt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@anselor I started giving this code an in-depth review and have made a number of comments and suggestions for change. I sincerely apologize for not getting to this PR sooner - life has been busy.
Overall this adds a feature that I've thought for a long time we need a good example to show developers how to do it - namely how to define commands in separate files and/or dynamically load commands at runtime.
However, it adds a lot of complexity and does so in a way I fear may be challenging for others to maintain. A few thoughts that passed through my mind while reviewing include the following:
- This could probably be done more simply
- There is more than one way to achieve this and I'm not sure we should pick one way as the "right" way
- This a great excellent example for users with very nice work put into test, a good working example, and documentation ... but would it more appropriately exist as a
cmd2extension akin to cmd2-ext-test than as a core part ofcmd2?
I'm not sure what the right answers are? What are your thoughts?
@kmvanbrunt and @kotfu If either of you are available to review this PR and/or weigh in with your opinion, I would very much appreciate it.
| auto_load_commands: bool = True) -> None: | ||
| """An easy but powerful framework for writing line-oriented command | ||
| interpreters. Extends Python's cmd package. | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is critical that you document these new parameters in the docstring below because that is what populates the Sphinx documentation.
| shortcuts=shortcuts) | ||
|
|
||
| # Load modular commands | ||
| self._installed_functions = [] # type: List[str] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the right data structure? More importantly does it need to exist at all?
The only place this is used is in install_command_functiion() where it is appended to and in uninstall_command() where it is checked to see if the specified command name is in this list?
If we keep it, it should be a set so that there is fast O(1) test for membership.
However, it looks like it is very weakly used and I'm not entirely sure if it is necessary.
|
|
||
| # Load modular commands | ||
| self._installed_functions = [] # type: List[str] | ||
| self._installed_command_sets = [] # type: List[CommandSet] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This too should be a set for the fast membership test since it is only ever used to see if something is in this container.
It is also weakly used and I'm not sure it is strictly necessary.
| def _autoload_commands(self) -> None: | ||
| """ | ||
| Load modular command definitions. | ||
| :return: None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Specifying :return: in docstring is redundant when using type hints and should not be done unless you are providing more context on what is returned.
This comment applies throughout this PR
| @@ -0,0 +1,127 @@ | |||
| #!/usr/bin/env python | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You forgot to make this file executable - make sure to chmod it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's weird. It shows up as executable for me.
examples/modular_commands_main.py
Outdated
|
|
||
|
|
||
| class WithCommandSets(Cmd): | ||
| def __init__(self, *args, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't follow this anti-pattern of just using *args, **kwargs. It makes code harder to read and maintain. Please explicitly list out the args you care about, e.g. command_sets. All arguments to the parent class __init__() are optional so it is safe to pass in the one you care about.
|
|
||
| from cmd2 import Cmd, Cmd2ArgumentParser, CompletionItem, with_argparser | ||
| from cmd2.utils import CompletionError, basic_complete | ||
| from modular_commands.commandset_basic import BasicCompletionCommandSet # noqa: F401 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think documentation at the top of the example of how BasicCompletionCommandSet gets loaded is in order because it probably isn't intuitive to someone just look at this example.
| @@ -0,0 +1,121 @@ | |||
| # coding=utf-8 | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating an empty init.py file in this directory might be a good enough hack to get the examples to run but probably not to work nicely in an IDE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the layout of the examples doesn't really support this very well.
| @@ -23,6 +23,7 @@ | |||
| # Get the current value for argparse_custom.DEFAULT_ARGUMENT_PARSER | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before merging in this would need a CHANGELOG entry for version 1.2.0 or whatever.
Issue #943 New class CommandSet can be used to tag a class as a command class. If the constructor is simple, the object will automatically be instantiated and loaded. New register_command decorator to tag any arbitrary function as a command.
…tation for CommandSet commands. Issue #943
…oad. Added unit tests. Moved installing commands into separate functions that can be called Issue #943
…ding command functions Adds handling of some edge cases. More thorough test coverage.
…e are scenarios where inspect.ismethod() fails for some reason
|
Accepting #961 instead. |