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) ], )
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), )
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 ), )
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, )
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."