Skip to main content
Version: 2.3 (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 in a register.py file.
from pants.engine.rules import collect_rules
from pants.option.subystem import Subsystem


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`."
)


def rules():
return collect_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.

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.