def get_attribute_documentation( node: ObjectNode, attribute_data: Optional[dict] = None) -> Attribute: """ Get the documentation for an attribute. Arguments: node: The node representing the method and its parents. attribute_data: Docstring and annotation for this attribute. Returns: The documented attribute object. """ if attribute_data is None: if node.parent_is_class(): attribute_data = get_class_attributes(node.parent.obj).get( node.name, {}) # type: ignore else: attribute_data = get_module_attributes(node.root.obj).get( node.name, {}) return Attribute( name=node.name, path=node.dotted_path, file_path=node.file_path, docstring=attribute_data.get("docstring", ""), attr_type=attribute_data.get("annotation", None), )
def get_annotated_dataclass_field( node: ObjectNode, attribute_data: Optional[dict] = None) -> Attribute: """ Get the documentation for a dataclass field. Arguments: node: The node representing the annotation and its parents. attribute_data: Docstring and annotation for this attribute. Returns: The documented attribute object. """ if attribute_data is None: if node.parent_is_class(): attribute_data = get_class_attributes(node.parent.obj).get( node.name, {}) # type: ignore else: attribute_data = get_module_attributes(node.root.obj).get( node.name, {}) return Attribute( name=node.name, path=node.dotted_path, file_path=node.file_path, docstring=attribute_data["docstring"], attr_type=attribute_data["annotation"], properties=["dataclass-field"], )
def get_class_documentation(self, node: ObjectNode, select_members=None) -> Class: """ Get the documentation for a class and its children. Arguments: node: The node representing the class and its parents. select_members: Explicit members to select. Returns: The documented class object. """ class_ = node.obj docstring = inspect.cleandoc(class_.__doc__ or "") root_object = Class(name=node.name, path=node.dotted_path, file_path=node.file_path, docstring=docstring) # Even if we don't select members, we want to correctly parse the docstring attributes_data: Dict[str, Dict[str, Any]] = {} for parent_class in reversed(class_.__mro__[:-1]): merge(attributes_data, get_class_attributes(parent_class)) context: Dict[str, Any] = {"attributes": attributes_data} if "__init__" in class_.__dict__: try: attributes_data.update(get_instance_attributes( class_.__init__)) context["signature"] = inspect.signature(class_.__init__) except (TypeError, ValueError): pass root_object.parse_docstring(self.docstring_parser, attributes=attributes_data) if select_members is False: return root_object select_members = select_members or set() # Build the list of members members = {} inherited = set() direct_members = class_.__dict__ all_members = dict(inspect.getmembers(class_)) for member_name, member in all_members.items(): if member is class_: continue if not (member is type or member is object) and self.select( member_name, select_members): if member_name not in direct_members: if self.select_inherited_members: members[member_name] = member inherited.add(member_name) else: members[member_name] = member # Iterate on the selected members child: Object for member_name, member in members.items(): child_node = ObjectNode(member, member_name, parent=node) if child_node.is_class(): child = self.get_class_documentation(child_node) elif child_node.is_classmethod(): child = self.get_classmethod_documentation(child_node) elif child_node.is_staticmethod(): child = self.get_staticmethod_documentation(child_node) elif child_node.is_method(): child = self.get_regular_method_documentation(child_node) elif child_node.is_property(): child = self.get_property_documentation(child_node) elif member_name in attributes_data: child = self.get_attribute_documentation( child_node, attributes_data[member_name]) else: continue if member_name in inherited: child.properties.append("inherited") root_object.add_child(child) for attr_name, properties, add_method in ( ("__fields__", ["pydantic-model"], self.get_pydantic_field_documentation), ("_declared_fields", ["marshmallow-model"], self.get_marshmallow_field_documentation), ("__dataclass_fields__", ["dataclass"], self.get_annotated_dataclass_field), ): if self.detect_field_model(attr_name, direct_members, all_members): root_object.properties.extend(properties) self.add_fields( node, root_object, attr_name, all_members, select_members, class_, add_method, ) break return root_object
def setup(self): """Setup reusable attributes.""" self.attributes = get_class_attributes(attr_module.E)
def setup(self): """Setup reusable attributes.""" self.attributes = get_class_attributes(attr_module.MarshmallowSchema)
def get_class_documentation(self, node: ObjectNode, select_members=None) -> Class: """ Get the documentation for a class and its children. Arguments: node: The node representing the class and its parents. select_members: Explicit members to select. Return: The documented class object. """ class_ = node.obj docstring = textwrap.dedent(class_.__doc__ or "") root_object = Class(name=node.name, path=node.dotted_path, file_path=node.file_path, docstring=docstring) # Even if we don't select members, we want to correctly parse the docstring attributes_data: Dict[str, Dict[str, Any]] = {} for cls in reversed(class_.__mro__[:-1]): merge(attributes_data, get_class_attributes(cls)) context: Dict[str, Any] = {"attributes": attributes_data} if "__init__" in class_.__dict__: attributes_data.update(get_instance_attributes(class_.__init__)) context["signature"] = inspect.signature(class_.__init__) root_object.parse_docstring(self.docstring_parser, attributes=attributes_data) if select_members is False: return root_object select_members = select_members or set() # Build the list of members members = {} inherited = set() direct_members = class_.__dict__ all_members = dict(inspect.getmembers(class_)) for member_name, member in all_members.items(): if not (member is type or member is object) and self.select( member_name, select_members): if member_name not in direct_members: if self.select_inherited_members: members[member_name] = member inherited.add(member_name) else: members[member_name] = member # Iterate on the selected members child: Object for member_name, member in members.items(): child_node = ObjectNode(member, member_name, parent=node) if child_node.is_class(): child = self.get_class_documentation(child_node) elif child_node.is_classmethod(): child = self.get_classmethod_documentation(child_node) elif child_node.is_staticmethod(): child = self.get_staticmethod_documentation(child_node) elif child_node.is_method(): child = self.get_regular_method_documentation(child_node) elif child_node.is_property(): child = self.get_property_documentation(child_node) elif member_name in attributes_data: child = self.get_attribute_documentation( child_node, attributes_data[member_name]) else: continue if member_name in inherited: child.properties.append("inherited") root_object.add_child(child) # First check if this is Pydantic compatible if "__fields__" in direct_members or (self.select_inherited_members and "__fields__" in all_members): root_object.properties = ["pydantic-model"] for field_name, model_field in all_members["__fields__"].items(): if self.select( field_name, select_members ) and ( # type: ignore self.select_inherited_members # When we don't select inherited members, one way to tell if a field was inherited # is to check if it exists in parent classes __fields__ attributes. # We don't check the current class, nor the top one (object), hence __mro__[1:-1] or field_name not in chain(*(getattr(cls, "__fields__", {}).keys() for cls in class_.__mro__[1:-1]))): child_node = ObjectNode(obj=model_field, name=field_name, parent=node) root_object.add_child( self.get_pydantic_field_documentation(child_node)) # Handle dataclasses elif "__dataclass_fields__" in direct_members or ( self.select_inherited_members and "__fields__" in all_members): root_object.properties = ["dataclass"] for field_name, annotation in all_members["__annotations__"].items( ): if self.select( field_name, select_members) and ( # type: ignore self.select_inherited_members # Same comment as for Pydantic models or field_name not in chain(*(getattr(cls, "__dataclass_fields__", {}).keys() for cls in class_.__mro__[1:-1]))): child_node = ObjectNode(obj=annotation, name=field_name, parent=node) root_object.add_child( self.get_annotated_dataclass_field(child_node)) return root_object