Exemple #1
0
 def create(cls, target_type: Type[Target], *,
            union_membership: UnionMembership) -> "VerboseTargetInfo":
     return cls(
         alias=target_type.alias,
         description=get_docstring(target_type),
         fields=[
             FieldInfo.create(field)
             for field in target_type.class_field_types(
                 union_membership=union_membership)
         ],
     )
Exemple #2
0
 def create(cls, target_type: Type[Target], *,
            union_membership: UnionMembership) -> "VerboseTargetInfo":
     return cls(
         alias=target_type.alias,
         description=get_docstring(target_type),
         fields=[
             FieldInfo.create(field)
             for field in target_type.class_field_types(
                 union_membership=union_membership)
             if not field.alias.startswith("_")
             and field.deprecated_removal_version is None
         ],
     )
 def create(cls, target_type: Type[Target], *,
            union_membership: UnionMembership) -> "TargetTypeHelpInfo":
     return cls(
         alias=target_type.alias,
         summary=get_docstring_summary(target_type),
         description=get_docstring(target_type),
         fields=tuple(
             TargetFieldHelpInfo.create(field)
             for field in target_type.class_field_types(
                 union_membership=union_membership)
             if not field.alias.startswith("_")
             and field.deprecated_removal_version is None),
     )
Exemple #4
0
def test_get_docstring_fallback_to_parents() -> None:
    class Grandparent:
        """Grandparent."""

    class ParentWithDocstring(Grandparent):
        """Parent."""

    class ParentWithoutDocstring(Grandparent):
        pass

    class ChildWithParentDocstring(ParentWithDocstring):
        pass

    class ChildWithGrandparentDocstring(ParentWithoutDocstring):
        pass

    class ChildWithDocstring(ParentWithDocstring):
        """Child."""

    assert get_docstring(ChildWithParentDocstring,
                         fallback_to_ancestors=True) == "Parent."
    assert (get_docstring(ChildWithGrandparentDocstring,
                          fallback_to_ancestors=True) == "Grandparent.")
    assert get_docstring(ChildWithDocstring,
                         fallback_to_ancestors=True) == "Child."

    # `object` is the "cosmic" superclass.
    class FallbackToObject:
        pass

    assert (get_docstring(FallbackToObject,
                          fallback_to_ancestors=True,
                          ignored_ancestors=[object]) is None)
    object_docstring = object.__doc__
    assert object_docstring is not None
    assert (get_docstring(FallbackToObject,
                          fallback_to_ancestors=True,
                          ignored_ancestors=[]) == object_docstring.rstrip())
 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,
         fields=tuple(
             TargetFieldHelpInfo.create(field)
             for field in target_type.class_field_types(union_membership=union_membership)
             if not field.alias.startswith("_") and field.deprecated_removal_version is None
         ),
     )
Exemple #6
0
    def create(cls, field: Type[Field]) -> "FieldInfo":
        # 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 `Field` author did not define docstring, then this means we
        # would typically fall back to the docstring for `AsyncField`, `PrimitiveField`, or a
        # helper class like `StringField`. This is a quirk of this heuristic and it's not
        # intentional since these core `Field` types have documentation oriented to the custom
        # `Field` author and not the end user filling in fields in a BUILD file target.
        description = (
            get_docstring(
                field,
                flatten=True,
                fallback_to_ancestors=True,
                ignored_ancestors={
                    *Field.mro(),
                    AsyncField,
                    PrimitiveField,
                    BoolField,
                    DictStringToStringField,
                    DictStringToStringSequenceField,
                    FloatField,
                    Generic,  # type: ignore[arg-type]
                    IntField,
                    ScalarField,
                    SequenceField,
                    StringField,
                    StringOrStringSequenceField,
                    StringSequenceField,
                },
            ) or "")
        if issubclass(field, PrimitiveField):
            raw_value_type = get_type_hints(field.compute_value)["raw_value"]
        elif issubclass(field, AsyncField):
            raw_value_type = get_type_hints(
                field.sanitize_raw_value)["raw_value"]
        else:
            raw_value_type = get_type_hints(field.__init__)["raw_value"]
        type_hint = pretty_print_type_hint(raw_value_type)

        # Check if the field only allows for certain choices.
        if issubclass(field, StringField) and field.valid_choices is not None:
            valid_choices = sorted(field.valid_choices if isinstance(
                field.valid_choices, tuple) else (
                    choice.value for choice in field.valid_choices))
            type_hint = " | ".join([*(repr(c) for c in valid_choices), "None"])

        if field.required:
            # We hackily remove `None` as a valid option for the field when it's required. This
            # greatly simplifies Field definitions because it means that they don't need to
            # override the type hints for `PrimitiveField.compute_value()` and
            # `AsyncField.sanitize_raw_value()` to indicate that `None` is an invalid type.
            type_hint = type_hint.replace(" | None", "")

        return cls(
            alias=field.alias,
            description=description,
            type_hint=type_hint,
            required=field.required,
            default=repr(field.default) if not field.required else None,
        )
Exemple #7
0
def test_get_docstring() -> None:
    class SingleLineDocstring:
        """Hello."""

    assert get_docstring_summary(SingleLineDocstring) == "Hello."
    assert get_docstring(SingleLineDocstring) == "Hello."
    assert get_docstring(SingleLineDocstring, flatten=True) == "Hello."

    class MultilineDocstring:
        """Hello.

        Extra description.
        """

    assert get_docstring_summary(MultilineDocstring) == "Hello."
    assert get_docstring(MultilineDocstring) == dedent("""\
        Hello.

        Extra description.""")
    assert get_docstring(MultilineDocstring,
                         flatten=True) == "Hello. Extra description."

    class NoDocstring:
        pass

    assert get_docstring_summary(NoDocstring) is None
    assert get_docstring(NoDocstring) is None
    assert get_docstring(NoDocstring, flatten=True) is None

    long_summary = (
        "This is all one sentence, it's just really really really long so it stretches to a "
        "whole new line.")

    class MultilineSummary:
        """This is all one sentence, it's just really really really long so it stretches to a whole
        new line."""

    assert get_docstring_summary(MultilineSummary) == long_summary
    assert get_docstring(MultilineSummary) == dedent("""\
        This is all one sentence, it's just really really really long so it stretches to a whole
        new line.""")
    assert get_docstring(MultilineSummary, flatten=True) == long_summary

    class MultilineSummaryWithDetails:
        """This is all one sentence, it's just really really really long so it stretches to a whole
        new line.

        We also have some extra detail.

            * l1
            * l2
        """

    assert get_docstring_summary(MultilineSummaryWithDetails) == long_summary
    assert get_docstring(MultilineSummaryWithDetails) == dedent("""\
        This is all one sentence, it's just really really really long so it stretches to a whole
        new line.

        We also have some extra detail.

            * l1
            * l2""")
    assert (get_docstring(MultilineSummaryWithDetails, flatten=True) ==
            f"{long_summary} We also have some extra detail. * l1 * l2")

    class SneakyDocstring:
        """Hello 😀!\n\nSneaky."""

    assert get_docstring_summary(SneakyDocstring) == "Hello 😀!"
    assert get_docstring(SneakyDocstring) == dedent("""\
        Hello 😀!

        Sneaky.""")
    assert get_docstring(SneakyDocstring,
                         flatten=True) == "Hello 😀! Sneaky."