-
Notifications
You must be signed in to change notification settings - Fork 1
Extensions
Hashbang uses extensions to extend the functionality of your commands. An extension is specified as a parameter of @command
and modifies the given HashbangCommand
to extend the behavior. A commonly used extension is Argument
, which changes how the arguments are applied.
Extensions are listed as parameters of @command
. For example, suppose we want to use the Argument
extension, and our hypothetical Version
extension, the code will look like this:
@command(
Argument('trailing_newline', aliases=('n',)),
Version('1.0.1'))
def echo(*message, trailing_newline=True):
print(' '.join(message), end=('\n' if trailing_newline else ''))
To see how extensions can be implemented, we can look at the implementation of Argument
.
class Argument:
def __init__(self, name, *, **kwargs):
self.name = name
[...]
def apply_hashbang_extension(self, cmd):
cmd.arguments[self.name] = (cmd.signature.parameters[self.name], self)
As you can see, an extension is just a regular object implementing the apply_hashbang_extension
method. In that method the HashbangCommand
class is passed in, and the extension would modify the command object to achieve the desired behavior. In the case of Argument
, it replaces itself in cmd.arguments
with the extension itself, thus allowing additional attributes to be defined.
A common use case for extensions is to extend Argument
. Since Argument
is responsible for adding arguments to the parser, extending Argument
allows it to add additional arguments or modify behavior of an existing argument. In our Version
example above, we can add a version
argument so that users can see the version number using --version
.
class VersionArgument(Argument):
def __init__(self, version):
super().__init__()
self.version = version
def add_argument(self, cmd, arg_container, param):
arg_container.add_argument(
'--version',
action='version',
version=self.version)
By overriding add_argument
and adding a --version
argument, any invocation with --version
will print the version number and exit immediately. The arg_container
argument passed to the add_argument
function is a parser-like object from the argparse
module. To plug this custom Argument
into the extension, we will implement Version
as follows:
class Version:
def __init__(self, version):
self.version_arg = VersionArgument(version)
def apply_hashbang_extension(self, cmd):
cmd.arguments['version'] = (None, self.version_arg)
This would add the VersionArgument
into the parser. HashbangCommand.arguments
, is a dictionary of {name: (param, argument)}
. param
is the parameter definition from inspect.signature()
. But since this argument doesn't map to any parameter in the decorated function, we can just pass None
. argument
is the argument object, which we will use the VersionArgument
class we implemented above.
In fact, VersionArgument
and Version
can be combined into the same class, so it would just pass self
in the arguments
dict. You can see a complete version of that below, or at tests/extension/version.py
Complete Version
extension
class Version(Argument):
def __init__(self, version):
super().__init__()
self.version = version
def apply_hashbang_extension(self, cmd):
cmd.arguments['version'] = (None, self)
def add_argument(self, cmd, arg_container, param):
arg_container.add_argument(
'--version',
action='version',
version=self.version)