-
Notifications
You must be signed in to change notification settings - Fork 128
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
Organize subcommands #1002
Organize subcommands #1002
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1002 +/- ##
==========================================
+ Coverage 59.24% 59.64% +0.40%
==========================================
Files 50 53 +3
Lines 6267 6317 +50
Branches 1588 1586 -2
==========================================
+ Hits 3713 3768 +55
+ Misses 2292 2286 -6
- Partials 262 263 +1 ☔ View full report in Codecov by Sentry. |
9e1cce9
to
1b2d1e2
Compare
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.
+1 for the general pattern of separate command modules with subpackages. A couple notes, both relatively minor and non-blocking.
Instead of just having each command to add their own arguments, allow them add their own subparser. This gives each command complete control of its own parser and their subparsers to customize them as needed. This allows each command to define its own command name instead of parsing the command name from the module name, which is important for the following commit to rename the `import` module. This commit also introduces the behavior to print a command's help when called if it does not have its own `run` function. Note that for a majority of the commands, `register_arguments` was updated to `register_parser`. However, for `align`, `filter` and `mask`, I kept the `register_arguments` functions since they are used in unit testing. This change came from discussion of how to extract the boilerplate for registering subparsers so that commands can use the same function to register their own subcommands.¹ ¹ #1002 (comment)
Since `import` is a Python keyword, the best practice is to name the module as `import_` according to PEP-8 guidelines.¹ This should not affect the usage of the `augur import` command since the command name remains unchanged. Since the module only has `register_parser` and `run` functions, there is unlikely to be any external uses of `augur.import`. This change came from suggestion by @tsibley in review.² ¹ https://peps.python.org/pep-0008/#descriptive-naming-styles ² #1002 (comment)
1b2d1e2
to
615a8ae
Compare
Tests failed at the setup-miniconda step, so I manually re-ran the |
|
||
if not subparser.description and command.__doc__: | ||
subparser.description = command.__doc__ | ||
|
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.
Ideally, I would like to add the subparser's help message here instead of requiring each module to import utils.first_line
. However, I have not found a way to add help
outside of the add_parser
call.
When I tried:
if not subparser.help and command.__doc__:
subparser.help = first_line(command.__doc__)
I get an error AttributeError: 'ArgumentParser' object has no attribute 'help'
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.
Ah, argparse
uses private methods to add the help message behind the scenes.
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. help=
becomes a property of a choices action on the parser's subparsers object, not the subparser itself.
It's retrievable, e.g.
name = next(name for name, sp in subparsers.choices.items() if sp is subparser)
choice = next(c for c in s._choices_actions if c.dest == name)
choice.help = …
but may not want to do that here!
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.
Hmm, seems a bit hacky to do this...
# Add help message for subparser if it doesn't already exist
name = next(name for name, sp in subparsers.choices.items() if sp is subparser)
if not any(c.dest == name for c in subparsers._choices_actions) and command.__doc__:
aliases = [alias for alias, sp in subparsers._name_parser_map.items() if alias != name and sp is subparser]
choice_action = subparsers._ChoicesPseudoAction(name, aliases, help=first_line(command.__doc__))
subparsers._choices_actions.append(choice_action)
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, agreed!
This `argparse_` module is expected to house custom helpers for interacting/extending the `argparse` standard library. Move `add_default_command()` to a shared `argparse_` module so that it can be used by commands without having circular import issues. I named the module `argparse_` (with an underscore) to avoid overwriting the `argparse` standard library within `augur/__init__.py`. @tsibley found the relevant docs¹ to explain this behavior. ¹ https://docs.python.org/3/reference/import.html#submodules
Instead of just having each command to add their own arguments, allow them add their own subparser. This gives each command complete control of its own parser and their subparsers to customize them as needed. This allows each command to define its own command name instead of parsing the command name from the module name, which is important for the following commit to rename the `import` module. This commit also introduces the behavior to print a command's help when called if it does not have its own `run` function. Note that for a majority of the commands, `register_arguments` was updated to `register_parser`. However, for `align`, `filter` and `mask`, I kept the `register_arguments` functions since they are used in unit testing. This change came from discussion of how to extract the boilerplate for registering subparsers so that commands can use the same function to register their own subcommands.¹ ¹ #1002 (comment)
Since `import` is a Python keyword, the best practice is to name the module as `import_` according to PEP-8 guidelines.¹ This should not affect the usage of the `augur import` command since the command name remains unchanged. Since the module only has `register_parser` and `run` functions, there is unlikely to be any external uses of `augur.import`. This change came from suggestion by @tsibley in review.² ¹ https://peps.python.org/pep-0008/#descriptive-naming-styles ² #1002 (comment)
This is a custom argparse.Action so it belongs with the other argparse helpers. This was added with the augur measurements command so it's highly unlikely to be used externally.
Moves the measurements module to its own package where the subcommands are split out into their own modules. The subcommand subparsers are added with the new `add_command_subparsers` function. This is an effort to reorganize augur commands with subcommands as packages instead of continuously adding subcommands as modules. Starting with the measurements command since it is a new command that is unlikely to already be used by anyone outside of the Nextstrain. In the future, we may want to consider refactoring export and validate in the same way. Putting that off for now since reorganizing those modules may result in breaking outside uses of their APIs.
Moves the import_ and import_beast modules to an import_ package and uses the new `add_command_subparsers` function to add the beast subparser. Search via cs.github.com¹ shows that there are no external uses of the `augur.import` or `augur.import_beast` APIs, so this should be safe to move to a package. ¹ https://cs.github.com/?scopeName=All+repos&scope=&q=%22augur.import%22
Simplifies the `register_parser` function and removes the now unneeded `run` function in export.py Ideally, export and it subcommands would be moved to its own package, but keeping these files in place since moving them can potentially break external uses of their APIs.
615a8ae
to
7de1fcd
Compare
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.
Much cleaner and understandable this way! Just a few minor suggestions.
These were carried over from when the two command subparsers were created within the same parent parser. Now that they are separated, the argument group variables can be renamed to remove redundant words.
`utils.first_line` is useful for the brief help message for `augur/distance.py` since it has a long multiline docstring, but seems like an overkill for other modules. Keep things simple by removing `utils.first_line` from other modules and just use their docstrings directly as their help message.
Keep the export modules consistent with the other modules in Augur and create help messages from the module docstrings.
Description of proposed changes
While starting on the new
augur curate
command, I was thinking of how to organize its suite of subcommands.This is my proof-of-concept, reorganizing the existing measurements command as an example.
Testing
Functional test for the measurements command passed locally.