Skip to content

Commit

Permalink
refactor: Refactor parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
pawamoy committed Jun 1, 2020
1 parent 7bec6c4 commit 3caefba
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 250 deletions.
70 changes: 48 additions & 22 deletions src/pytkdocs/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from typing import Any, List, Optional, Set, Union

from pytkdocs.objects import Attribute, Class, Function, Method, Module, Object, Source
from pytkdocs.parsers.attributes import get_attributes
from pytkdocs.parsers.attributes import get_class_attributes, get_instance_attributes, get_module_attributes
from pytkdocs.parsers.docstrings import PARSERS
from pytkdocs.properties import RE_SPECIAL

Expand Down Expand Up @@ -222,8 +222,6 @@ def get_object_documentation(self, dotted_path: str, members: Optional[Union[Set
root_object: Object
leaf = get_object_tree(dotted_path)

attributes = get_attributes(leaf.root.obj)

if leaf.is_module():
root_object = self.get_module_documentation(leaf, members)
elif leaf.is_class():
Expand All @@ -239,20 +237,7 @@ def get_object_documentation(self, dotted_path: str, members: Optional[Union[Set
elif leaf.is_property():
root_object = self.get_property_documentation(leaf)
else:
for attribute in attributes:
if attribute.path == dotted_path:
return attribute
raise ValueError(f"{dotted_path}: {type(leaf.obj)} not yet supported")

if members is not False:
filtered = []
for attribute in attributes:
if attribute.parent_path == root_object.path:
if self.select(attribute.name, members): # type: ignore
filtered.append(attribute)
elif self.select(attribute.name, set()):
filtered.append(attribute)
root_object.dispatch_attributes(filtered)
root_object = self.get_attribute_documentation(leaf)

root_object.parse_all_docstrings(self.docstring_parser)

Expand Down Expand Up @@ -298,13 +283,18 @@ def get_module_documentation(self, node: ObjectNode, members=None) -> Module:
# type_hints = get_type_hints(module)
members = members or set()

for member_name, member in inspect.getmembers(module, lambda m: node.root.obj is inspect.getmodule(m)):
attributes_data = get_module_attributes(module)
root_object.parse_docstring(self.docstring_parser, attributes=attributes_data)

for member_name, member in inspect.getmembers(module):
if self.select(member_name, members): # type: ignore
child_node = ObjectNode(member, member_name, parent=node)
if child_node.is_class():
if child_node.is_class() and node.root.obj is inspect.getmodule(member):
root_object.add_child(self.get_class_documentation(child_node))
elif child_node.is_function():
elif child_node.is_function() and node.root.obj is inspect.getmodule(member):
root_object.add_child(self.get_function_documentation(child_node))
elif member_name in attributes_data:
root_object.add_child(self.get_attribute_documentation(child_node, attributes_data[member_name]))

try:
package_path = module.__path__
Expand Down Expand Up @@ -338,6 +328,13 @@ def get_class_documentation(self, node: ObjectNode, members=None) -> Class:

members = members or set()

attributes_data = get_class_attributes(class_)
context = {"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)

for member_name, member in class_.__dict__.items():
if member is type or member is object:
continue
Expand All @@ -356,6 +353,8 @@ def get_class_documentation(self, node: ObjectNode, members=None) -> Class:
root_object.add_child(self.get_regular_method_documentation(child_node))
elif child_node.is_property():
root_object.add_child(self.get_property_documentation(child_node))
elif member_name in attributes_data:
root_object.add_child(self.get_attribute_documentation(child_node, attributes_data[member_name]))

# First check if this is Pydantic compatible
if "__fields__" in class_.__dict__:
Expand Down Expand Up @@ -450,7 +449,8 @@ def get_property_documentation(self, node: ObjectNode) -> Attribute:
source=source,
)

def get_pydantic_field_documentation(self, node: ObjectNode) -> Attribute:
@staticmethod
def get_pydantic_field_documentation(node: ObjectNode) -> Attribute:
"""
Get the documentation for a Pydantic Field.
Expand All @@ -475,7 +475,8 @@ def get_pydantic_field_documentation(self, node: ObjectNode) -> Attribute:
properties=properties,
)

def get_annotated_dataclass_field(self, node: ObjectNode) -> Attribute:
@staticmethod
def get_annotated_dataclass_field(node: ObjectNode) -> Attribute:
"""
Get the documentation for an dataclass annotation.
Expand Down Expand Up @@ -580,6 +581,31 @@ def get_method_documentation(self, node: ObjectNode, properties: Optional[List[s
source=source,
)

@staticmethod
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:
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 select(self, name: str, names: Set[str]) -> bool:
"""
Tells whether we should select an object or not, given its name.
Expand Down
32 changes: 8 additions & 24 deletions src/pytkdocs/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def relative_file_path(self) -> str:
If the relative file path cannot be determined, the value returned is `""` (empty string).
"""
parts = self.path.split(".")
namespaces = [".".join(parts[:l]) for l in range(1, len(parts) + 1)]
namespaces = [".".join(parts[:length]) for length in range(1, len(parts) + 1)]
# Iterate through all sub namespaces including the last in case it is a module
for namespace in namespaces:
try:
Expand Down Expand Up @@ -269,25 +269,16 @@ def add_children(self, children: List["Object"]) -> None:
for child in children:
self.add_child(child)

def dispatch_attributes(self, attributes: List["Attribute"]) -> None:
def parse_docstring(self, parser: Parser, **context) -> None:
"""
Dispatch attributes as children of an object and its children given their path.
If an attribute's path does not correspond to the object or any of its children,
the attribute is not attached.
Parse the docstring of this object.
Arguments:
attributes: The list of attributes to dispatch.
parser: A parser to parse the docstrings.
"""
for attribute in attributes:
try:
attach_to = self._path_map[attribute.parent_path]
except KeyError:
pass
else:
attach_to.attributes.append(attribute)
attach_to.children.append(attribute)
attribute.parent = attach_to
if self.docstring and not self._parsed:
self.docstring_sections, self.docstring_errors = parser.parse(self.docstring, {"obj": self, **context})
self._parsed = True

def parse_all_docstrings(self, parser: Parser) -> None:
"""
Expand All @@ -296,14 +287,7 @@ def parse_all_docstrings(self, parser: Parser) -> None:
Arguments:
parser: A parser to parse the docstrings.
"""
if self.docstring and not self._parsed:
self.docstring_sections, self.docstring_errors = parser.parse(
self.docstring,
object_path=self.path,
object_signature=getattr(self, "signature", None),
object_type=getattr(self, "type", None),
)
self._parsed = True
self.parse_docstring(parser)
for child in self.children:
child.parse_all_docstrings(parser)

Expand Down
Loading

0 comments on commit 3caefba

Please sign in to comment.