def test_name_properties_on_attribute(): assert not Attribute(name="b", path="a.b", file_path="a.py").name_properties assert "private" in Attribute(name="_b", path="a._b", file_path="a.py").name_properties assert "class-private" in Attribute(name="__b", path="a.__b", file_path="a.py").name_properties assert "special" in Attribute(name="__b__", path="a.__b__", file_path="a.py").name_properties
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 test_add_children(): root = Object(name="o", path="o", file_path="o.py") class_ = Class(name="c", path="o.c", file_path="o.py") attribute = Attribute(name="a", path="o.c.a", file_path="o.py") class_.add_child(attribute) root.add_children( [ # class has wrong path Class(name="w", path="wrong.path.w", file_path="/wrong/path/w.py"), # class OK class_, # not a direct child, attribute, # function OK Function(name="f", path="o.f", file_path="o.py"), # not a direct child, not even a child of known child Method(name="missing_node", path="o.mn.missing_node", file_path="o.py"), ] ) assert len(root.children) == 2 assert root.classes and root.classes[0] is class_ assert root.functions and root.functions[0].name == "f"
def get_django_field_documentation(node: ObjectNode) -> Attribute: """ Get the documentation for a Django Field. Arguments: node: The node representing the Field and its parents. Returns: The documented attribute object. """ prop = node.obj path = node.dotted_path properties = ["django-field"] if prop.null: properties.append("nullable") if prop.blank: properties.append("blank") return Attribute( name=node.name, path=path, file_path=node.file_path, docstring=prop.verbose_name, attr_type=prop.__class__, properties=properties, )
def test_do_not_add_child_if_parent_is_not_self(): """Don't add a child the parent is not the right one.""" parent = Module(name="my_module", path="my.dotted.path", file_path="/my/absolute/path.py") child = Attribute(name="my_attribute", path="my.other.path.my_attribute", file_path="/my/absolute/path.py") parent.add_child(child) assert not parent.children assert not parent.attributes
def test_add_child(): """Add a child.""" parent = Module(name="my_module", path="my.dotted.path", file_path="/my/absolute/path.py") child = Attribute(name="my_attribute", path="my.dotted.path.my_attribute", file_path="/my/absolute/path.py") parent.add_child(child) assert parent.children[0] is child assert parent.attributes[0] is child
def get_property_documentation(self, node: ObjectNode) -> Attribute: """ Get the documentation for a property. Arguments: node: The node representing the property and its parents. Returns: The documented attribute object (properties are considered attributes for now). """ prop = node.obj path = node.dotted_path properties = ["property"] if node.is_cached_property(): # cached_property is always writable, see the docs properties.extend(["writable", "cached"]) sig_source_func = prop.func else: properties.append("readonly" if prop.fset is None else "writable") sig_source_func = prop.fget source: Optional[Source] try: signature = inspect.signature(sig_source_func) except (TypeError, ValueError) as error: self.errors.append(f"Couldn't get signature for '{path}': {error}") attr_type = None else: attr_type = signature.return_annotation try: source = Source(*inspect.getsourcelines(sig_source_func)) except (OSError, TypeError) as error: self.errors.append(f"Couldn't get source for '{path}': {error}") source = None return Attribute( name=node.name, path=path, file_path=node.file_path, docstring=inspect.getdoc(prop), attr_type=attr_type, properties=properties, source=source, )
def _get_attributes( ast_body: Iterable, name_prefix: str, file_path: str, properties: Optional[List[str]] = None ) -> List[Attribute]: if not properties: properties = [] documented_attributes = [] previous_node = None body: Any for node in ast_body: try: attr_info = get_attribute_info(previous_node, node) except ValueError: if isinstance(node, RECURSIVE_NODES): documented_attributes.extend(_get_attributes(node.body, name_prefix, file_path, properties)) # type: ignore if isinstance(node, ast.Try): for body in [node.handlers, node.orelse, node.finalbody]: documented_attributes.extend(_get_attributes(body, name_prefix, file_path, properties)) elif isinstance(node, ast.If): documented_attributes.extend(_get_attributes(node.orelse, name_prefix, file_path, properties)) elif isinstance(node, ast.FunctionDef) and node.name == "__init__": documented_attributes.extend(_get_attributes(node.body, name_prefix, file_path)) elif isinstance(node, ast.ClassDef): documented_attributes.extend( _get_attributes(node.body, f"{name_prefix}.{node.name}", file_path, properties=["class-attribute"]) ) else: for name in attr_info["names"]: documented_attributes.append( Attribute( name=name, path=f"{name_prefix}.{name}", file_path=file_path, docstring=dedent(attr_info["docstring"]), properties=properties, attr_type=attr_info["type"], ) ) previous_node = node return documented_attributes
def get_annotated_dataclass_field(self, node: ObjectNode) -> Attribute: """ Get the documentation for an dataclass annotation. Arguments: node: The node representing the annotation and its parents. Return: The documented attribute object. """ annotation: type = node.obj path = node.dotted_path properties = ["field"] return Attribute(name=node.name, path=path, file_path=node.file_path, attr_type=annotation, properties=properties)
def get_property_documentation(self, node: ObjectNode) -> Attribute: """ Get the documentation for an attribute. Arguments: node: The node representing the attribute and its parents. Returns: The documented attribute object. """ prop = node.obj path = node.dotted_path properties = [ "property", "readonly" if prop.fset is None else "writable" ] source: Optional[Source] try: signature = inspect.signature(prop.fget) except (TypeError, ValueError) as error: self.errors.append(f"Couldn't get signature for '{path}': {error}") attr_type = None else: attr_type = signature.return_annotation try: source = Source(*inspect.getsourcelines(prop.fget)) except (OSError, TypeError) as error: self.errors.append(f"Couldn't get source for '{path}': {error}") source = None return Attribute( name=node.name, path=path, file_path=node.file_path, docstring=inspect.getdoc(prop.fget), attr_type=attr_type, properties=properties, source=source, )
def get_marshmallow_field_documentation(node: ObjectNode) -> Attribute: """ Get the documentation for a Marshmallow Field. Arguments: node: The node representing the Field and its parents. Returns: The documented attribute object. """ prop = node.obj path = node.dotted_path properties = ["marshmallow-field"] if prop.required: properties.append("required") return Attribute( name=node.name, path=path, file_path=node.file_path, docstring=prop.metadata.get("description"), attr_type=type(prop), properties=properties, )
def get_pydantic_field_documentation(node: ObjectNode) -> Attribute: """ Get the documentation for a Pydantic Field. Arguments: node: The node representing the Field and its parents. Returns: The documented attribute object. """ prop = node.obj path = node.dotted_path properties = ["pydantic-field"] if prop.required: properties.append("required") return Attribute( name=node.name, path=path, file_path=node.file_path, docstring=prop.field_info.description, attr_type=prop.type_, properties=properties, )
def test_creating_attribute(): assert Attribute(name="my_object", path="my.dotted.path", file_path="/my/absolute/path.py")