Skip to content

Commit

Permalink
Stop falling back to docstring for help messages for Targets and `F…
Browse files Browse the repository at this point in the history
…ield`s (pantsbuild#12003)

This was meant to be removed after the deprecation from pantsbuild#11380 was done.

[ci skip-rust]
[ci skip-build-wheels]
  • Loading branch information
Eric-Arellano authored May 3, 2021
1 parent 31465e5 commit 4ec94f2
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 315 deletions.
86 changes: 22 additions & 64 deletions src/python/pants/help/help_info_extracter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,15 @@
import json
from dataclasses import dataclass
from enum import Enum
from typing import Any, Callable, Dict, Generic, List, Optional, Tuple, Type, cast, get_type_hints
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union, cast, get_type_hints

from pants.base import deprecated
from pants.engine.goal import GoalSubsystem
from pants.engine.target import (
AsyncFieldMixin,
BoolField,
DictStringToStringField,
DictStringToStringSequenceField,
Field,
FloatField,
IntField,
RegisteredTargetTypes,
ScalarField,
SequenceField,
StringField,
StringSequenceField,
Target,
)
from pants.engine.target import Field, RegisteredTargetTypes, StringField, Target
from pants.engine.unions import UnionMembership
from pants.option.option_util import is_dict_option, is_list_option
from pants.option.options import Options
from pants.option.parser import OptionValueHistory, Parser
from pants.util.objects import get_docstring, get_docstring_summary, pretty_print_type_hint
from pants.util.strutil import first_paragraph


Expand Down Expand Up @@ -144,51 +129,32 @@ class GoalHelpInfo:
consumed_scopes: Tuple[str, ...] # The scopes of subsystems consumed by this goal.


def pretty_print_type_hint(hint: Any) -> str:
if getattr(hint, "__origin__", None) == Union:
union_members = hint.__args__
hint_str = " | ".join(pretty_print_type_hint(member) for member in union_members)
# NB: Checking for GenericMeta is only for Python 3.6 because some `typing` classes like
# `typing.Iterable` have its type, whereas Python 3.7+ removes it. Remove this check
# once we drop support for Python 3.6.
elif isinstance(hint, type) and not str(type(hint)) == "<class 'typing.GenericMeta'>":
hint_str = hint.__name__
else:
hint_str = str(hint)
return hint_str.replace("typing.", "").replace("NoneType", "None")


@dataclass(frozen=True)
class TargetFieldHelpInfo:
"""A container for help information for a field in a target type."""

alias: str
description: Optional[str]
description: str
type_hint: str
required: bool
default: Optional[str]

@classmethod
def create(cls, field: Type[Field]) -> TargetFieldHelpInfo:
description: Optional[str]
if hasattr(field, "help"):
description = field.help
else:
# NB: It is very common (and encouraged) to subclass Fields to give custom behavior, e.g.
# `PythonSources` subclassing `Sources`. Here, we set `fallback_to_ancestors=True` so that
# we can still generate meaningful documentation for all these custom fields without
# requiring the Field author to rewrite the docstring.
#
# However, if the original plugin author did not define docstring, then this means we
# would typically fall back to the docstring for `Field` or a template like `StringField`.
# This is a an awkward edge of our heuristic and it's not intentional since these core
# `Field` types have documentation oriented to the plugin author and not the end user
# filling in fields in a BUILD file.
description = get_docstring(
field,
flatten=True,
fallback_to_ancestors=True,
ignored_ancestors={
*Field.mro(),
AsyncFieldMixin,
BoolField,
DictStringToStringField,
DictStringToStringSequenceField,
FloatField,
Generic, # type: ignore[arg-type]
IntField,
ScalarField,
SequenceField,
StringField,
StringSequenceField,
},
)
raw_value_type = get_type_hints(field.compute_value)["raw_value"]
type_hint = pretty_print_type_hint(raw_value_type)

Expand All @@ -210,7 +176,7 @@ def create(cls, field: Type[Field]) -> TargetFieldHelpInfo:

return cls(
alias=field.alias,
description=description,
description=field.help,
type_hint=type_hint,
required=field.required,
default=(
Expand All @@ -224,26 +190,18 @@ class TargetTypeHelpInfo:
"""A container for help information for a target type."""

alias: str
summary: Optional[str]
description: Optional[str]
summary: str
description: str
fields: Tuple[TargetFieldHelpInfo, ...]

@classmethod
def create(
cls, target_type: Type[Target], *, union_membership: UnionMembership
) -> TargetTypeHelpInfo:
description: Optional[str]
summary: Optional[str]
if hasattr(target_type, "help"):
description = target_type.help
summary = first_paragraph(description)
else:
description = get_docstring(target_type)
summary = get_docstring_summary(target_type)
return cls(
alias=target_type.alias,
summary=summary,
description=description,
summary=first_paragraph(target_type.help),
description=target_type.help,
fields=tuple(
TargetFieldHelpInfo.create(field)
for field in target_type.class_field_types(union_membership=union_membership)
Expand Down
28 changes: 26 additions & 2 deletions src/python/pants/help/help_info_extracter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

import dataclasses
from enum import Enum
from typing import Any, Optional, Tuple
from typing import Any, Iterable, List, Optional, Tuple, Union

from pants.engine.goal import GoalSubsystem
from pants.engine.target import IntField, RegisteredTargetTypes, StringField, Target
from pants.engine.unions import UnionMembership
from pants.help.help_info_extracter import HelpInfoExtracter, to_help_str
from pants.help.help_info_extracter import HelpInfoExtracter, pretty_print_type_hint, to_help_str
from pants.option.config import Config
from pants.option.global_options import GlobalOptions
from pants.option.options import Options
Expand Down Expand Up @@ -413,3 +413,27 @@ def fake_consumed_scopes_mapper(scope: str) -> Tuple[str, ...]:
},
}
assert expected_all_help_info_dict == all_help_info_dict


def test_pretty_print_type_hint() -> None:
assert pretty_print_type_hint(str) == "str"
assert pretty_print_type_hint(int) == "int"
assert pretty_print_type_hint(None) == "None"

class ExampleCls:
pass

assert pretty_print_type_hint(ExampleCls) == "ExampleCls"

# Transform Unions to use `|`
assert pretty_print_type_hint(Union[int, float]) == "int | float"
assert pretty_print_type_hint(Optional[int]) == "int | None"
# NB: `Iterable[List[ExampleCls]]` will use the full module name for `ExampleCls`. We can't
# easily control that because it comes from the __repr__ implementation for `typing.Iterable`.
example_cls_repr = (
f"{__name__}.{test_pretty_print_type_hint.__name__}.<locals>.{ExampleCls.__name__}"
)
assert (
pretty_print_type_hint(Union[Iterable[List[ExampleCls]], Optional[float], Any])
== f"Iterable[List[{example_cls_repr}]] | float | None | Any"
)
2 changes: 1 addition & 1 deletion src/python/pants/help/help_printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def _print_all_targets(self) -> None:
):
alias_str = self.maybe_cyan(f"{alias}".ljust(chars_before_description))
summary = self._format_summary_description(
target_type_info.summary or "<no description>", chars_before_description
target_type_info.summary, chars_before_description
)
print(f"{alias_str}{summary}\n")
specific_help_cmd = f"{self._bin_name} help $target_type"
Expand Down
5 changes: 0 additions & 5 deletions src/python/pants/option/optionable.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from pants.option.errors import OptionsError
from pants.option.scope import Scope, ScopedOptions, ScopeInfo
from pants.util.meta import classproperty
from pants.util.objects import get_docstring_summary


async def _construct_optionable(optionable_factory):
Expand Down Expand Up @@ -126,10 +125,6 @@ def get_options_scope_equivalent_flag_component(cls):
"""
return re.sub(r"\.", "-", cls.options_scope)

@classmethod
def get_description(cls) -> Optional[str]:
return get_docstring_summary(cls)

@classmethod
def register_options(cls, register):
"""Register options for this optionable.
Expand Down
85 changes: 0 additions & 85 deletions src/python/pants/util/objects.py

This file was deleted.

Loading

0 comments on commit 4ec94f2

Please sign in to comment.