Options and subsystems
How to add options to your plugin.
As explained in Options, options are partitioned into named scopes, like [test]
and [isort]
. Each of these scopes corresponds to a subsystem.
To add new options:
- Define a subclass of
Subsystem
frompants.subsystem.subsystem
.- Give a description in the docstring.
- Set the class property
options_scope
with the name of the subsystem.- This value will be prepended to all options in the subsystem, e.g.
--skip
will become--shellcheck-skip
.
- This value will be prepended to all options in the subsystem, e.g.
- Add new options through the class method
register_options()
. - Register the
Subsystem
in aregister.py
file.
- pants-plugins/example/shellcheck.py
- pants-plugins/example/register.py
from pants.engine.rules import collect_rules
from pants.option.subystem import Subsystem
class Shellcheck(Subsystem):
"""The Shellcheck linter."""
options_scope = "shellcheck"
@classmethod
def register_options(cls, register):
super().register_options(register)
register(
"--skip",
type=bool,
default=False,
help="Don't use Shellcheck when running `./pants lint`."
)
def rules():
return collect_rules()
from example import shellcheck
def rules():
return [*shellcheck.rules()]
The subsystem should now show up when you run ./pants help shellcheck
.
GoalSubsystem
As explained in Goal rules, goals use a subclass of Subsystem
: GoalSubsystem
from pants.engine.goal
.
GoalSubsystem
behaves the same way as a normal subsystem, except that you set the class property name
rather than options_scope
. The name
will auto-populate the options_scope
.
register()
parameters
Flag name
The first argument to register()
should be a string with the option name, like, --example-option
.
When users use a command-line flag, the option will get prefixed with the options_scope
, e.g. --shellcheck-skip
. With config files, every -
will be replaced by _
, e.g. example_option
.
You may also specify a short flag, like -e
. You may pass multiple values, so you may pass -e, --example-option
.
metavar
metavar
changes what users see in ./pants help
as possible values for the flag. If unspecified, Pants will use a metavar
based on the type
you specify.
default
default
will be used if the user does not specify the value.
If you leave this off, the default
will depend on the type
you specify.
type
Refer to Options for how a user specifies these different types.
type | Default (if not overridden) | Other notes |
---|---|---|
str | None | |
int | None | Will error if a user passes a float . |
float | None | Will convert an int into a float . |
bool | False Set default=UnsetBool from pants.option.custom_types to represent a trinary state. If the user does not specify a value, the option will return None . | |
Enums | None | Create an enum.Enum , with the value for each member as a string.See below for an example. |
list | [] (empty list) | You must also set member_type to one of str , int , float , dict , dir_option , file_option , target_option , shell_str , or an Enum . |
dict | {} (empty dictionary) | |
shell_str | [] (empty list) | You must set type=list and member_type=shell_str .Pants will "shlex" the string passed by the user, i.e. to split on each space. For example, ./pants --docformatter-args='--wrap-summaries 100 --wrap-descriptions 100' will get converted into ["--wrap_summaries", "100", "--wrap-descriptions", "100"] .The type is from pants.option.custom_types . |
target_option | None | This behaves like str , but changes the metavar so that ./pants help is more useful.The type is from pants.option.custom_types . |
file_option | None | This behaves like str , but changes the metavar so that ./pants help is more useful. It will also validate that the file exists.The type is from pants.option.custom_types . |
dir_option | None | This behaves like str , but changes the metavar so that ./pants help is more useful. It will also validate that the directory exists.The type is from pants.option.custom_types . |
An example of an Enum
:
from enum import Enum
class LeafyGreens(Enum):
KALE = "kale"
SPINACH = "spinach"
register(
"--vegetable",
type=LeafyGreens,
default=LeafyGreens.KALE,
help="Which veggie Pants should use.",
)
passthrough
Set passthrough=True
if the option should work with the -- <args>
syntax, e.g. ./pants test app.py -- -v -k demo
.
advanced
If you set advanced=True
, the option will only show up in help-advanced
, and not help
.
You should generally set this value if the option will primarily be used by codebase administrators, such as setting up a config file.
help
This help message will show up when running ./pants help
and ./pants help-advanced
.
Using options in rules
To use a Subsystem
or GoalSubsystem
in your rule, request it as a parameter. Then, use the property .options
, followed by the name of the option. Replace any -
with _
, e.g. --my-option
becomes my_option
.
from pants.engine.rules import rule
...
@rule
async def demo(shellcheck: Shellcheck) -> LintResults:
if shellcheck.options.skip:
return LintResults()
extra_args = shellcheck.options.args
...
MyPy does not understand the property .options
.
Instead, you can create a @property
for each option and use typing.cast()
to teach MyPy what each value is.
from typing import cast
class Shellcheck(Subsystem):
"""The Shellcheck linter."""
options_scope = "shellcheck"
@classmethod
def register_options(cls, register):
super().register_options(register)
register(
"--skip",
type=bool,
default=False,
help="Don't use Shellcheck when running `./pants lint`.
)
@property
def skip(self) -> bool:
return cast(bool, self.options.skip)
This is optional. It can make your call sites simpler and safer thanks to MyPy, but it requires some boilerplate.