Skip to content

Commit

Permalink
[internal] Refactor the flag info in option_types (#14498)
Browse files Browse the repository at this point in the history
[internal] Refactor the flag info in option_types

[ci skip-rust]
[ci skip-build-wheels]
  • Loading branch information
thejcannon authored Feb 17, 2022
1 parent 30f9b53 commit 1f314a2
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 56 deletions.
126 changes: 73 additions & 53 deletions src/python/pants/option/option_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast, overload
from typing import Any, Generic, TypeVar, cast, overload

from pants.option import custom_types

if TYPE_CHECKING:
pass

_PropType = TypeVar("_PropType")
_EnumT = TypeVar("_EnumT", bound=Enum)
_ValueT = TypeVar("_ValueT")
# NB: We don't provide constraints, as our `XListOption` types act like a set of contraints
_ListMemberType = TypeVar("_ListMemberType")


@dataclass(frozen=True)
class OptionsInfo:
flag_names: tuple[str, ...]
flag_options: dict[str, Any]


class _OptionBase(Generic[_PropType]):
"""Descriptor for subsystem options.
Expand All @@ -37,8 +41,9 @@ class _OptionBase(Generic[_PropType]):
"""

_flag_names: tuple[str, ...]
_kwargs: dict
option_type: Any # NB: This should be some kind of callable that returns _PropType
_default: Any
_help: str
_extra_kwargs: dict[str, Any]

# NB: Due to https://github.com/python/mypy/issues/5146, we try to keep the parameter list as
# short as possible to avoid having to repeat the param in each of the 3 `__new__` specs that
Expand All @@ -51,33 +56,29 @@ def __new__(
cls,
*flag_names: str,
default: Any,
option_type: type | None = None,
help: str,
):
self = super().__new__(cls)
self._flag_names = flag_names
if option_type is None:
option_type = cls.option_type

self._kwargs = dict(
type=option_type,
default=default,
help=help,
)
self._default = default
self._help = help
self._extra_kwargs = {}
return self

@property
def flag_names(self) -> tuple[str, ...]:
"""Returns the flag names."""
return self._flag_names
# Override if necessary
def get_option_type(self):
return type(self).option_type

@property
def flag_options(self) -> dict:
"""Returns a shallow-copy of the the internal flag options."""
return self._kwargs.copy()
def get_flag_options(self) -> dict:
return dict(
help=self._help,
default=self._default,
type=self.get_option_type(),
**self._extra_kwargs,
)

@overload
def __get__(self, obj: None, objtype: Any) -> _OptionBase[_PropType]:
def __get__(self, obj: None, objtype: Any) -> OptionsInfo:
...

@overload
Expand All @@ -86,8 +87,8 @@ def __get__(self, obj: object, objtype: Any) -> _PropType:

def __get__(self, obj, objtype):
if obj is None:
return self
long_name = self.flag_names[-1]
return OptionsInfo(self._flag_names, self.get_flag_options())
long_name = self._flag_names[-1]
option_value = getattr(obj.options, long_name[2:].replace("-", "_"))
if option_value is None:
return None
Expand All @@ -98,36 +99,36 @@ def _convert_(self, val: Any) -> _PropType:
return cast("_PropType", val)

def advanced(self) -> _OptionBase[_PropType]:
self._kwargs["advanced"] = True
self._extra_kwargs["advanced"] = True
return self

def from_file(self) -> _OptionBase[_PropType]:
self._kwargs["fromfile"] = True
self._extra_kwargs["fromfile"] = True
return self

def metavar(self, metavar: str) -> _OptionBase[_PropType]:
self._kwargs["metavar"] = metavar
self._extra_kwargs["metavar"] = metavar
return self

def mutually_exclusive_group(self, mutually_exclusive_group: str) -> _OptionBase[_PropType]:
self._kwargs["mutually_exclusive_group"] = mutually_exclusive_group
self._extra_kwargs["mutually_exclusive_group"] = mutually_exclusive_group
return self

def default_help_repr(self, default_help_repr: str) -> _OptionBase[_PropType]:
self._kwargs["default_help_repr"] = default_help_repr
self._extra_kwargs["default_help_repr"] = default_help_repr
return self

def deprecated(self, *, removal_version: str, hint: str) -> _OptionBase[_PropType]:
self._kwargs["removal_version"] = removal_version
self._kwargs["removal_hint"] = hint
self._extra_kwargs["removal_version"] = removal_version
self._extra_kwargs["removal_hint"] = hint
return self

def daemoned(self) -> _OptionBase[_PropType]:
self._kwargs["daemon"] = True
self._extra_kwargs["daemon"] = True
return self

def non_fingerprinted(self) -> _OptionBase[_PropType]:
self._kwargs["fingerprint"] = False
self._extra_kwargs["fingerprint"] = False
return self


Expand All @@ -153,19 +154,25 @@ def __new__(
default: list[_ListMemberType] | None = None,
help: str,
):
if member_type is None:
member_type = cls.member_type

default = default or []
instance = super().__new__(
cls, # type: ignore[arg-type]
*flag_names,
default=default,
help=help,
)
instance._kwargs["member_type"] = member_type
return instance

def get_flag_options(self) -> dict[str, Any]:
return dict(
member_type=self.get_member_type(),
**super().get_flag_options(),
)

# Override if necessary
def get_member_type(self):
return type(self).member_type

def _convert_(self, value: list[Any]) -> tuple[_ListMemberType]:
return cast("tuple[_ListMemberType]", tuple(value))

Expand Down Expand Up @@ -278,14 +285,20 @@ def __new__(
default=None,
help,
):
instance = super().__new__(cls, *flag_names, default=default, help=help)
instance._option_type = option_type
return instance

def get_option_type(self):
option_type = self._option_type
default = self._default
if option_type is None:
if default is None:
raise ValueError("`option_type` must be provided if `default` isn't provided.")
option_type = type(default)

return super().__new__(
cls, *flag_names, default=default, option_type=option_type, help=help
)
raise ValueError(
"`enum_type` must be provided to the constructor if `default` isn't provided."
)
return type(default)
return option_type


class DictOption(_OptionBase["dict[str, _ValueT]"], Generic[_ValueT]):
Expand Down Expand Up @@ -418,14 +431,21 @@ def __new__(
default=None,
help,
):
if member_type is None:
if default is None:
raise ValueError("`member_type` must be provided if `default` isn't provided.")
member_type = type(default[0])
instance = super().__new__(cls, *flag_names, default=default, help=help)
instance._member_type = member_type
return instance

return super().__new__(
cls, *flag_names, member_type=member_type, default=default, help=help
)
def get_member_type(self):
member_type = self._member_type
default = self._default
if member_type is None:
if not default:
raise ValueError(
"`enum_type` must be provided to the constructor if `default` isn't provided "
"or is empty."
)
return type(default[0])
return member_type


class TargetListOption(StrListOption):
Expand Down Expand Up @@ -477,5 +497,5 @@ def passthrough(self) -> "ArgsListOption":
This should be used when callers can alternatively use "--" followed by the arguments,
instead of having to provide "--[scope]-args='--arg1 --arg2'".
"""
self._kwargs["passthrough"] = True
self._extra_kwargs["passthrough"] = True
return self
8 changes: 5 additions & 3 deletions src/python/pants/option/subsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from pants.engine.internals.selectors import AwaitableConstraints, Get
from pants.option.errors import OptionsError
from pants.option.option_types import _OptionBase
from pants.option.option_types import OptionsInfo
from pants.option.option_value_container import OptionValueContainer
from pants.option.scope import Scope, ScopedOptions, ScopeInfo, normalize_scope

Expand Down Expand Up @@ -104,8 +104,10 @@ def register_options(cls, register):
# NB: Since registration ordering matters (it impacts `help` output), we register these in
# class attribute order, starting from the base class down.
for class_ in reversed(inspect.getmro(cls)):
for attr in class_.__dict__.values():
if isinstance(attr, _OptionBase):
for attrname in class_.__dict__.keys():
# NB: We use attrname and getattr to trigger descriptors
attr = getattr(cls, attrname)
if isinstance(attr, OptionsInfo):
register(*attr.flag_names, **attr.flag_options)

@classmethod
Expand Down

0 comments on commit 1f314a2

Please sign in to comment.