Skip to main content
Version: 2.10 (deprecated)

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:

  1. Define a subclass of Subsystem from pants.subsystem.subsystem.
    1. 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.
    2. Set the class property help, which is used by ./pants help.
  2. Add new options through the class method register_options().
  3. Register the Subsystem with SubsystemRule and register.py.
    • You don't need this if the Subsystem is used in an @rule because collect_rules() will recognize it. It doesn't hurt to keep this around, though.
from pants.engine.rules import SubsystemRule
from pants.option.subsystem import Subsystem


class ShellcheckSubsystem(Subsystem):
options_scope = "shellcheck"
help = "The Shellcheck linter."

@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 [SubsystemRule(ShellcheckSubsystem)]

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.

typeDefault (if not overridden)Other notes
strNone
intNoneWill error if a user passes a float.
floatNoneWill convert an int into a float.
boolFalse

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.
EnumsNoneCreate 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_optionNoneThis behaves like str, but changes the metavar so that ./pants help is more useful.

The type is from pants.option.custom_types.
file_optionNoneThis 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_optionNoneThis 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
...
Tip: teach MyPy about your subsystem

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):
options_scope = "shellcheck"
help = "The Shellcheck linter."

@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.