Options and subsystems

How to add options to your plugin.

Defining options

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 pants.options.option_types class attributes.
  3. Register the Subsystem with SubsystemRule and register.py.
    • You don't need SubsystemRule 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.option_types import BoolOption
from pants.option.subsystem import Subsystem


class ShellcheckSubsystem(Subsystem):
    options_scope = "shellcheck"
    help = "The Shellcheck linter."
    
    config_discovery = BoolOption(
        "--config-discovery",
        default=True,
        advanced=True,
        help="Whether Pants should...",
    )

        
def rules():
    return [SubsystemRule(ShellcheckSubsystem)]
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.

Option types

These classes correspond to the option types at Options.

Every option type requires that you set the flag name (e.g. -l or --level) and the keyword argument help. Most types require that you set default. You can optionally set advanced=True with every option for it to only show up with help-advanced.

Class name

Notes

StrOption

Must set default to a str or None.

BoolOption

Must set default to a bool or None. TODO

Reminder when choosing a flag name: Pants will recognize the command line argument --no-my-flag-name as equivalent to --my-flag-name=false.

IntOption

Must set default to an int or None.

FloatOption

Must set default to a float or None.

EnumOption

This is like StrOption, but with the valid choices constrained to your enum.

To use, define an enum.Enum. The values of your enum will be what users can type, e.g. 'kale' and 'spinach' below:

class LeafyGreens(Enum):
    KALE = "kale"
    SPINACH = "spinach"
```You must either set `default` to a value from your enum or `None`. If you set `default=None`, you must set `enum_type`.

List options:

  • StrListOption
  • BoolListOption
  • IntListOption
  • FloatListOption
  • EnumListOption

Default is [] if default is not set.

For EnumListOption, you must set the keyword argument enum_type.

DictOption

Default is {} if default is not set.

Currently, Pants does not offer any validation of the dictionary entries, e.g. dict[str, str] vs dict[str, list[str]]. (Although per TOML specs, the key should always be str.) You may want to add eager validation that users are inputting options the correct way.

ArgsListOption

Adds an --args option, e.g. --isort-args. This type is extra useful because it uses a special shell_str that lets users type the arguments as a single string with spaces, which Pants will shlex for them. That is, --args='arg1 arg2' gets converted to ['arg1', 'arg2'].

You must set the keyword argument example, e.g. '--arg1 arg2'. You must also set tool_name: str, e.g. 'Black'.

You can optionally set passthrough=True if the user should be able to use the style ./pants my-goal :: -- --arg1, i.e. arguments after --.

Using options in rules

To use a Subsystem or GoalSubsystem in your rule, request it as a parameter. Then, use the class attributes to access the option value.

from pants.engine.rules import rule
...

@rule
async def demo(shellcheck: Shellcheck) -> LintResults:
    if shellcheck.skip:
        return LintResults()
    config_discovery = shellcheck.config_discovery
    ...

Did this page help you?