loader
This module is responsible for loading the documentation from Python objects.
It uses inspect
for introspecting objects,
iterating over their members, etc.
Loader
¤
This class contains the object documentation loading mechanisms.
Any error that occurred during collection of the objects and their documentation is stored in the errors
list.
Source code in pytkdocs/loader.py
class Loader:
"""
This class contains the object documentation loading mechanisms.
Any error that occurred during collection of the objects and their documentation is stored in the `errors` list.
"""
def __init__(
self,
filters: Optional[List[str]] = None,
docstring_style: str = "google",
docstring_options: Optional[dict] = None,
inherited_members: bool = False,
new_path_syntax: bool = False,
) -> None:
"""
Initialize the object.
Arguments:
filters: A list of regular expressions to fine-grain select members. It is applied recursively.
docstring_style: The style to use when parsing docstrings.
docstring_options: The options to pass to the docstrings parser.
inherited_members: Whether to select inherited members for classes.
new_path_syntax: Whether to use the "colon" syntax for the path.
"""
if not filters:
filters = []
self.filters = [(filtr, re.compile(filtr.lstrip("!"))) for filtr in filters]
self.docstring_parser = PARSERS[docstring_style](**(docstring_options or {})) # type: ignore
self.errors: List[str] = []
self.select_inherited_members = inherited_members
self.new_path_syntax = new_path_syntax
def get_object_documentation(self, dotted_path: str, members: Optional[Union[Set[str], bool]] = None) -> Object:
"""
Get the documentation for an object and its children.
Arguments:
dotted_path: The Python dotted path to the desired object.
members: `True` to select members and filter them, `False` to select no members,
or a list of names to explicitly select the members with these names.
It is applied only on the root object.
Returns:
The documented object.
"""
if members is True:
members = set()
root_object: Object
leaf = get_object_tree(dotted_path, self.new_path_syntax)
if leaf.is_module():
root_object = self.get_module_documentation(leaf, members)
elif leaf.is_class():
root_object = self.get_class_documentation(leaf, members)
elif leaf.is_staticmethod():
root_object = self.get_staticmethod_documentation(leaf)
elif leaf.is_classmethod():
root_object = self.get_classmethod_documentation(leaf)
elif leaf.is_method_descriptor():
root_object = self.get_regular_method_documentation(leaf)
elif leaf.is_method():
root_object = self.get_regular_method_documentation(leaf)
elif leaf.is_function():
root_object = self.get_function_documentation(leaf)
elif leaf.is_property():
root_object = self.get_property_documentation(leaf)
else:
root_object = self.get_attribute_documentation(leaf)
root_object.parse_all_docstrings(self.docstring_parser)
return root_object
def get_module_documentation(self, node: ObjectNode, select_members=None) -> Module:
"""
Get the documentation for a module and its children.
Arguments:
node: The node representing the module and its parents.
select_members: Explicit members to select.
Returns:
The documented module object.
"""
module = node.obj
path = node.dotted_path
name = path.split(".")[-1]
source: Optional[Source]
try:
source = Source(inspect.getsource(module), 1)
except OSError as error:
try:
code = Path(node.file_path).read_text()
except (OSError, UnicodeDecodeError):
source = None
else:
source = Source(code, 1) if code else None
root_object = Module(
name=name,
path=path,
file_path=node.file_path,
docstring=inspect.getdoc(module),
source=source,
)
if select_members is False:
return root_object
select_members = select_members or set()
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, select_members):
child_node = ObjectNode(member, member_name, parent=node)
if child_node.is_class() and node.root.obj is inspect.getmodule(child_node.obj):
root_object.add_child(self.get_class_documentation(child_node))
elif child_node.is_function() and node.root.obj is inspect.getmodule(child_node.obj):
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]))
if hasattr(module, "__path__"): # noqa: WPS421 (hasattr)
for _, modname, _ in pkgutil.iter_modules(module.__path__):
if self.select(modname, select_members):
leaf = get_object_tree(f"{path}.{modname}")
root_object.add_child(self.get_module_documentation(leaf))
return root_object
@staticmethod
def _class_path(cls):
mod = cls.__module__
qname = cls.__qualname__
if mod == "builtins":
return qname
else:
return f"{mod}.{qname}"
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 "")
bases = [self._class_path(b) for b in class_.__bases__]
source: Optional[Source]
try:
source = Source(*inspect.getsourcelines(node.obj))
except (OSError, TypeError) as error:
source = None
root_object = Class(
name=node.name,
path=node.dotted_path,
file_path=node.file_path,
docstring=docstring,
bases=bases,
source=source,
)
# 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, **context)
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),
("_meta.get_fields", ["django-model"], self.get_django_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 detect_field_model(self, attr_name: str, direct_members, all_members) -> bool:
"""
Detect if an attribute is present in members.
Arguments:
attr_name: The name of the attribute to detect, can contain dots.
direct_members: The direct members of the class.
all_members: All members of the class.
Returns:
Whether the attribute is present.
"""
first_order_attr_name, remainder = split_attr_name(attr_name)
if not (
first_order_attr_name in direct_members
or (self.select_inherited_members and first_order_attr_name in all_members)
):
return False
if remainder and not attrgetter(remainder)(all_members[first_order_attr_name]):
return False
return True
def add_fields(
self,
node: ObjectNode,
root_object: Object,
attr_name: str,
members,
select_members,
base_class,
add_method,
) -> None:
"""
Add detected fields to the current object.
Arguments:
node: The current object node.
root_object: The current object.
attr_name: The fields attribute name.
members: The members to pick the fields attribute in.
select_members: The members to select.
base_class: The class declaring the fields.
add_method: The method to add the children object.
"""
fields = get_fields(attr_name, members=members)
for field_name, field in fields.items():
select_field = self.select(field_name, select_members)
is_inherited = field_is_inherited(field_name, attr_name, base_class)
if select_field and (self.select_inherited_members or not is_inherited):
child_node = ObjectNode(obj=field, name=field_name, parent=node)
root_object.add_child(add_method(child_node))
def get_function_documentation(self, node: ObjectNode) -> Function:
"""
Get the documentation for a function.
Arguments:
node: The node representing the function and its parents.
Returns:
The documented function object.
"""
function = node.obj
path = node.dotted_path
source: Optional[Source]
signature: Optional[inspect.Signature]
try:
signature = inspect.signature(function)
except TypeError as error:
signature = None
try:
source = Source(*inspect.getsourcelines(function))
except OSError as error:
source = None
properties: List[str] = []
if node.is_coroutine_function():
properties.append("async")
return Function(
name=node.name,
path=node.dotted_path,
file_path=node.file_path,
docstring=inspect.getdoc(function),
signature=signature,
source=source,
properties=properties,
)
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:
attr_type = None
else:
attr_type = signature.return_annotation
try:
source = Source(*inspect.getsourcelines(sig_source_func))
except (OSError, TypeError) as 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,
)
@staticmethod
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.outer_type_,
properties=properties,
)
@staticmethod
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")
# set correct docstring based on verbose_name and help_text
# both should be converted to str type in case lazy translation
# is being used, which is common scenario in django
if prop.help_text:
docstring = f"{prop.verbose_name}: {prop.help_text}"
else:
docstring = str(prop.verbose_name)
return Attribute(
name=node.name,
path=path,
file_path=node.file_path,
docstring=docstring,
attr_type=prop.__class__,
properties=properties,
)
@staticmethod
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,
)
@staticmethod
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_classmethod_documentation(self, node: ObjectNode) -> Method:
"""
Get the documentation for a class-method.
Arguments:
node: The node representing the class-method and its parents.
Returns:
The documented method object.
"""
return self.get_method_documentation(node, ["classmethod"])
def get_staticmethod_documentation(self, node: ObjectNode) -> Method:
"""
Get the documentation for a static-method.
Arguments:
node: The node representing the static-method and its parents.
Returns:
The documented method object.
"""
return self.get_method_documentation(node, ["staticmethod"])
def get_regular_method_documentation(self, node: ObjectNode) -> Method:
"""
Get the documentation for a regular method (not class- nor static-method).
We do extra processing in this method to discard docstrings of `__init__` methods
that were inherited from parent classes.
Arguments:
node: The node representing the method and its parents.
Returns:
The documented method object.
"""
method = self.get_method_documentation(node)
if node.parent:
class_ = node.parent.obj
if RE_SPECIAL.match(node.name):
docstring = method.docstring
parent_classes = class_.__mro__[1:]
for parent_class in parent_classes:
try:
parent_method = getattr(parent_class, node.name)
except AttributeError:
continue
else:
if docstring == inspect.getdoc(parent_method):
method.docstring = ""
break
return method
def get_method_documentation(self, node: ObjectNode, properties: Optional[List[str]] = None) -> Method:
"""
Get the documentation for a method or method descriptor.
Arguments:
node: The node representing the method and its parents.
properties: A list of properties to apply to the method.
Returns:
The documented method object.
"""
method = node.obj
path = node.dotted_path
signature: Optional[inspect.Signature]
source: Optional[Source]
try:
source = Source(*inspect.getsourcelines(method))
except OSError as error:
source = None
except TypeError:
source = None
if node.is_coroutine_function():
if properties is None:
properties = ["async"]
else:
properties.append("async")
try:
# for "built-in" functions, e.g. those implemented in C,
# inspect.signature() uses the __text_signature__ attribute, which
# provides a limited but still useful amount of signature information.
# "built-in" functions with no __text_signature__ will
# raise a ValueError().
signature = inspect.signature(method)
except ValueError as error:
signature = None
return Method(
name=node.name,
path=path,
file_path=node.file_path,
docstring=inspect.getdoc(method),
signature=signature,
properties=properties or [],
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: 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 select(self, name: str, names: Set[str]) -> bool:
"""
Tells whether we should select an object or not, given its name.
If the set of names is not empty, we check against it, otherwise we check against filters.
Arguments:
name: The name of the object to select or not.
names: An explicit list of names to select.
Returns:
Yes or no.
"""
if names:
return name in names
return not self.filter_name_out(name)
@lru_cache(maxsize=None)
def filter_name_out(self, name: str) -> bool:
"""
Filter a name based on the loader's filters.
Arguments:
name: The name to filter.
Returns:
True if the name was filtered out, False otherwise.
"""
if not self.filters:
return False
keep = True
for fltr, regex in self.filters:
is_matching = bool(regex.search(name))
if is_matching:
if str(fltr).startswith("!"):
is_matching = not is_matching
keep = is_matching
return not keep
__init__(self, filters=None, docstring_style='google', docstring_options=None, inherited_members=False, new_path_syntax=False)
special
¤
Initialize the object.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
filters |
Optional[List[str]] |
A list of regular expressions to fine-grain select members. It is applied recursively. |
None |
docstring_style |
str |
The style to use when parsing docstrings. |
'google' |
docstring_options |
Optional[dict] |
The options to pass to the docstrings parser. |
None |
inherited_members |
bool |
Whether to select inherited members for classes. |
False |
new_path_syntax |
bool |
Whether to use the "colon" syntax for the path. |
False |
Source code in pytkdocs/loader.py
def __init__(
self,
filters: Optional[List[str]] = None,
docstring_style: str = "google",
docstring_options: Optional[dict] = None,
inherited_members: bool = False,
new_path_syntax: bool = False,
) -> None:
"""
Initialize the object.
Arguments:
filters: A list of regular expressions to fine-grain select members. It is applied recursively.
docstring_style: The style to use when parsing docstrings.
docstring_options: The options to pass to the docstrings parser.
inherited_members: Whether to select inherited members for classes.
new_path_syntax: Whether to use the "colon" syntax for the path.
"""
if not filters:
filters = []
self.filters = [(filtr, re.compile(filtr.lstrip("!"))) for filtr in filters]
self.docstring_parser = PARSERS[docstring_style](**(docstring_options or {})) # type: ignore
self.errors: List[str] = []
self.select_inherited_members = inherited_members
self.new_path_syntax = new_path_syntax
add_fields(self, node, root_object, attr_name, members, select_members, base_class, add_method)
¤
Add detected fields to the current object.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The current object node. |
required |
root_object |
Object |
The current object. |
required |
attr_name |
str |
The fields attribute name. |
required |
members |
The members to pick the fields attribute in. |
required | |
select_members |
The members to select. |
required | |
base_class |
The class declaring the fields. |
required | |
add_method |
The method to add the children object. |
required |
Source code in pytkdocs/loader.py
def add_fields(
self,
node: ObjectNode,
root_object: Object,
attr_name: str,
members,
select_members,
base_class,
add_method,
) -> None:
"""
Add detected fields to the current object.
Arguments:
node: The current object node.
root_object: The current object.
attr_name: The fields attribute name.
members: The members to pick the fields attribute in.
select_members: The members to select.
base_class: The class declaring the fields.
add_method: The method to add the children object.
"""
fields = get_fields(attr_name, members=members)
for field_name, field in fields.items():
select_field = self.select(field_name, select_members)
is_inherited = field_is_inherited(field_name, attr_name, base_class)
if select_field and (self.select_inherited_members or not is_inherited):
child_node = ObjectNode(obj=field, name=field_name, parent=node)
root_object.add_child(add_method(child_node))
detect_field_model(self, attr_name, direct_members, all_members)
¤
Detect if an attribute is present in members.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
attr_name |
str |
The name of the attribute to detect, can contain dots. |
required |
direct_members |
The direct members of the class. |
required | |
all_members |
All members of the class. |
required |
Returns:
Type | Description |
---|---|
bool |
Whether the attribute is present. |
Source code in pytkdocs/loader.py
def detect_field_model(self, attr_name: str, direct_members, all_members) -> bool:
"""
Detect if an attribute is present in members.
Arguments:
attr_name: The name of the attribute to detect, can contain dots.
direct_members: The direct members of the class.
all_members: All members of the class.
Returns:
Whether the attribute is present.
"""
first_order_attr_name, remainder = split_attr_name(attr_name)
if not (
first_order_attr_name in direct_members
or (self.select_inherited_members and first_order_attr_name in all_members)
):
return False
if remainder and not attrgetter(remainder)(all_members[first_order_attr_name]):
return False
return True
filter_name_out(self, name)
¤
Filter a name based on the loader's filters.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
The name to filter. |
required |
Returns:
Type | Description |
---|---|
bool |
True if the name was filtered out, False otherwise. |
Source code in pytkdocs/loader.py
@lru_cache(maxsize=None)
def filter_name_out(self, name: str) -> bool:
"""
Filter a name based on the loader's filters.
Arguments:
name: The name to filter.
Returns:
True if the name was filtered out, False otherwise.
"""
if not self.filters:
return False
keep = True
for fltr, regex in self.filters:
is_matching = bool(regex.search(name))
if is_matching:
if str(fltr).startswith("!"):
is_matching = not is_matching
keep = is_matching
return not keep
get_annotated_dataclass_field(node, attribute_data=None)
staticmethod
¤
Get the documentation for a dataclass field.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the annotation and its parents. |
required |
attribute_data |
Optional[dict] |
Docstring and annotation for this attribute. |
None |
Returns:
Type | Description |
---|---|
Attribute |
The documented attribute object. |
Source code in pytkdocs/loader.py
@staticmethod
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"],
)
get_attribute_documentation(node, attribute_data=None)
staticmethod
¤
Get the documentation for an attribute.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the method and its parents. |
required |
attribute_data |
Optional[dict] |
Docstring and annotation for this attribute. |
None |
Returns:
Type | Description |
---|---|
Attribute |
The documented attribute object. |
Source code in pytkdocs/loader.py
@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: 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),
)
get_class_documentation(self, node, select_members=None)
¤
Get the documentation for a class and its children.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the class and its parents. |
required |
select_members |
Explicit members to select. |
None |
Returns:
Type | Description |
---|---|
Class |
The documented class object. |
Source code in pytkdocs/loader.py
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 "")
bases = [self._class_path(b) for b in class_.__bases__]
source: Optional[Source]
try:
source = Source(*inspect.getsourcelines(node.obj))
except (OSError, TypeError) as error:
source = None
root_object = Class(
name=node.name,
path=node.dotted_path,
file_path=node.file_path,
docstring=docstring,
bases=bases,
source=source,
)
# 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, **context)
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),
("_meta.get_fields", ["django-model"], self.get_django_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
get_classmethod_documentation(self, node)
¤
Get the documentation for a class-method.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the class-method and its parents. |
required |
Returns:
Type | Description |
---|---|
Method |
The documented method object. |
Source code in pytkdocs/loader.py
def get_classmethod_documentation(self, node: ObjectNode) -> Method:
"""
Get the documentation for a class-method.
Arguments:
node: The node representing the class-method and its parents.
Returns:
The documented method object.
"""
return self.get_method_documentation(node, ["classmethod"])
get_django_field_documentation(node)
staticmethod
¤
Get the documentation for a Django Field.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the Field and its parents. |
required |
Returns:
Type | Description |
---|---|
Attribute |
The documented attribute object. |
Source code in pytkdocs/loader.py
@staticmethod
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")
# set correct docstring based on verbose_name and help_text
# both should be converted to str type in case lazy translation
# is being used, which is common scenario in django
if prop.help_text:
docstring = f"{prop.verbose_name}: {prop.help_text}"
else:
docstring = str(prop.verbose_name)
return Attribute(
name=node.name,
path=path,
file_path=node.file_path,
docstring=docstring,
attr_type=prop.__class__,
properties=properties,
)
get_function_documentation(self, node)
¤
Get the documentation for a function.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the function and its parents. |
required |
Returns:
Type | Description |
---|---|
Function |
The documented function object. |
Source code in pytkdocs/loader.py
def get_function_documentation(self, node: ObjectNode) -> Function:
"""
Get the documentation for a function.
Arguments:
node: The node representing the function and its parents.
Returns:
The documented function object.
"""
function = node.obj
path = node.dotted_path
source: Optional[Source]
signature: Optional[inspect.Signature]
try:
signature = inspect.signature(function)
except TypeError as error:
signature = None
try:
source = Source(*inspect.getsourcelines(function))
except OSError as error:
source = None
properties: List[str] = []
if node.is_coroutine_function():
properties.append("async")
return Function(
name=node.name,
path=node.dotted_path,
file_path=node.file_path,
docstring=inspect.getdoc(function),
signature=signature,
source=source,
properties=properties,
)
get_marshmallow_field_documentation(node)
staticmethod
¤
Get the documentation for a Marshmallow Field.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the Field and its parents. |
required |
Returns:
Type | Description |
---|---|
Attribute |
The documented attribute object. |
Source code in pytkdocs/loader.py
@staticmethod
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,
)
get_method_documentation(self, node, properties=None)
¤
Get the documentation for a method or method descriptor.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the method and its parents. |
required |
properties |
Optional[List[str]] |
A list of properties to apply to the method. |
None |
Returns:
Type | Description |
---|---|
Method |
The documented method object. |
Source code in pytkdocs/loader.py
def get_method_documentation(self, node: ObjectNode, properties: Optional[List[str]] = None) -> Method:
"""
Get the documentation for a method or method descriptor.
Arguments:
node: The node representing the method and its parents.
properties: A list of properties to apply to the method.
Returns:
The documented method object.
"""
method = node.obj
path = node.dotted_path
signature: Optional[inspect.Signature]
source: Optional[Source]
try:
source = Source(*inspect.getsourcelines(method))
except OSError as error:
source = None
except TypeError:
source = None
if node.is_coroutine_function():
if properties is None:
properties = ["async"]
else:
properties.append("async")
try:
# for "built-in" functions, e.g. those implemented in C,
# inspect.signature() uses the __text_signature__ attribute, which
# provides a limited but still useful amount of signature information.
# "built-in" functions with no __text_signature__ will
# raise a ValueError().
signature = inspect.signature(method)
except ValueError as error:
signature = None
return Method(
name=node.name,
path=path,
file_path=node.file_path,
docstring=inspect.getdoc(method),
signature=signature,
properties=properties or [],
source=source,
)
get_module_documentation(self, node, select_members=None)
¤
Get the documentation for a module and its children.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the module and its parents. |
required |
select_members |
Explicit members to select. |
None |
Returns:
Type | Description |
---|---|
Module |
The documented module object. |
Source code in pytkdocs/loader.py
def get_module_documentation(self, node: ObjectNode, select_members=None) -> Module:
"""
Get the documentation for a module and its children.
Arguments:
node: The node representing the module and its parents.
select_members: Explicit members to select.
Returns:
The documented module object.
"""
module = node.obj
path = node.dotted_path
name = path.split(".")[-1]
source: Optional[Source]
try:
source = Source(inspect.getsource(module), 1)
except OSError as error:
try:
code = Path(node.file_path).read_text()
except (OSError, UnicodeDecodeError):
source = None
else:
source = Source(code, 1) if code else None
root_object = Module(
name=name,
path=path,
file_path=node.file_path,
docstring=inspect.getdoc(module),
source=source,
)
if select_members is False:
return root_object
select_members = select_members or set()
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, select_members):
child_node = ObjectNode(member, member_name, parent=node)
if child_node.is_class() and node.root.obj is inspect.getmodule(child_node.obj):
root_object.add_child(self.get_class_documentation(child_node))
elif child_node.is_function() and node.root.obj is inspect.getmodule(child_node.obj):
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]))
if hasattr(module, "__path__"): # noqa: WPS421 (hasattr)
for _, modname, _ in pkgutil.iter_modules(module.__path__):
if self.select(modname, select_members):
leaf = get_object_tree(f"{path}.{modname}")
root_object.add_child(self.get_module_documentation(leaf))
return root_object
get_object_documentation(self, dotted_path, members=None)
¤
Get the documentation for an object and its children.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
dotted_path |
str |
The Python dotted path to the desired object. |
required |
members |
Union[Set[str], bool] |
|
None |
Returns:
Type | Description |
---|---|
Object |
The documented object. |
Source code in pytkdocs/loader.py
def get_object_documentation(self, dotted_path: str, members: Optional[Union[Set[str], bool]] = None) -> Object:
"""
Get the documentation for an object and its children.
Arguments:
dotted_path: The Python dotted path to the desired object.
members: `True` to select members and filter them, `False` to select no members,
or a list of names to explicitly select the members with these names.
It is applied only on the root object.
Returns:
The documented object.
"""
if members is True:
members = set()
root_object: Object
leaf = get_object_tree(dotted_path, self.new_path_syntax)
if leaf.is_module():
root_object = self.get_module_documentation(leaf, members)
elif leaf.is_class():
root_object = self.get_class_documentation(leaf, members)
elif leaf.is_staticmethod():
root_object = self.get_staticmethod_documentation(leaf)
elif leaf.is_classmethod():
root_object = self.get_classmethod_documentation(leaf)
elif leaf.is_method_descriptor():
root_object = self.get_regular_method_documentation(leaf)
elif leaf.is_method():
root_object = self.get_regular_method_documentation(leaf)
elif leaf.is_function():
root_object = self.get_function_documentation(leaf)
elif leaf.is_property():
root_object = self.get_property_documentation(leaf)
else:
root_object = self.get_attribute_documentation(leaf)
root_object.parse_all_docstrings(self.docstring_parser)
return root_object
get_property_documentation(self, node)
¤
Get the documentation for a property.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the property and its parents. |
required |
Returns:
Type | Description |
---|---|
Attribute |
The documented attribute object (properties are considered attributes for now). |
Source code in pytkdocs/loader.py
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:
attr_type = None
else:
attr_type = signature.return_annotation
try:
source = Source(*inspect.getsourcelines(sig_source_func))
except (OSError, TypeError) as 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,
)
get_pydantic_field_documentation(node)
staticmethod
¤
Get the documentation for a Pydantic Field.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the Field and its parents. |
required |
Returns:
Type | Description |
---|---|
Attribute |
The documented attribute object. |
Source code in pytkdocs/loader.py
@staticmethod
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.outer_type_,
properties=properties,
)
get_regular_method_documentation(self, node)
¤
Get the documentation for a regular method (not class- nor static-method).
We do extra processing in this method to discard docstrings of __init__
methods
that were inherited from parent classes.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the method and its parents. |
required |
Returns:
Type | Description |
---|---|
Method |
The documented method object. |
Source code in pytkdocs/loader.py
def get_regular_method_documentation(self, node: ObjectNode) -> Method:
"""
Get the documentation for a regular method (not class- nor static-method).
We do extra processing in this method to discard docstrings of `__init__` methods
that were inherited from parent classes.
Arguments:
node: The node representing the method and its parents.
Returns:
The documented method object.
"""
method = self.get_method_documentation(node)
if node.parent:
class_ = node.parent.obj
if RE_SPECIAL.match(node.name):
docstring = method.docstring
parent_classes = class_.__mro__[1:]
for parent_class in parent_classes:
try:
parent_method = getattr(parent_class, node.name)
except AttributeError:
continue
else:
if docstring == inspect.getdoc(parent_method):
method.docstring = ""
break
return method
get_staticmethod_documentation(self, node)
¤
Get the documentation for a static-method.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
node |
ObjectNode |
The node representing the static-method and its parents. |
required |
Returns:
Type | Description |
---|---|
Method |
The documented method object. |
Source code in pytkdocs/loader.py
def get_staticmethod_documentation(self, node: ObjectNode) -> Method:
"""
Get the documentation for a static-method.
Arguments:
node: The node representing the static-method and its parents.
Returns:
The documented method object.
"""
return self.get_method_documentation(node, ["staticmethod"])
select(self, name, names)
¤
Tells whether we should select an object or not, given its name.
If the set of names is not empty, we check against it, otherwise we check against filters.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
The name of the object to select or not. |
required |
names |
Set[str] |
An explicit list of names to select. |
required |
Returns:
Type | Description |
---|---|
bool |
Yes or no. |
Source code in pytkdocs/loader.py
def select(self, name: str, names: Set[str]) -> bool:
"""
Tells whether we should select an object or not, given its name.
If the set of names is not empty, we check against it, otherwise we check against filters.
Arguments:
name: The name of the object to select or not.
names: An explicit list of names to select.
Returns:
Yes or no.
"""
if names:
return name in names
return not self.filter_name_out(name)
ObjectNode
¤
Helper class to represent an object tree.
It's not really a tree but more a backward-linked list: each node has a reference to its parent, but not to its child (for simplicity purposes and to avoid bugs).
Each node stores an object, its name, and a reference to its parent node.
Source code in pytkdocs/loader.py
class ObjectNode:
"""
Helper class to represent an object tree.
It's not really a tree but more a backward-linked list:
each node has a reference to its parent, but not to its child (for simplicity purposes and to avoid bugs).
Each node stores an object, its name, and a reference to its parent node.
"""
def __init__(self, obj: Any, name: str, parent: Optional["ObjectNode"] = None) -> None:
"""
Initialize the object.
Arguments:
obj: A Python object.
name: The object's name.
parent: The object's parent node.
"""
try:
obj = inspect.unwrap(obj)
except Exception: # noqa: S110,W0703 (we purposely catch every possible exception)
# inspect.unwrap at some point runs hasattr(obj, "__wrapped__"),
# which triggers the __getattr__ method of the object, which in
# turn can raise various exceptions. Probably not just __getattr__.
# See https://github.com/pawamoy/pytkdocs/issues/45
pass # noqa: WPS420 (no other way than passing)
self.obj: Any = obj
"""The actual Python object."""
self.name: str = name
"""The Python object's name."""
self.parent: Optional[ObjectNode] = parent
"""The parent node."""
@property
def dotted_path(self) -> str:
"""
Return the Python dotted path to the object.
Returns:
The Python dotted path to the object.
"""
parts = [self.name]
current = self.parent
while current:
parts.append(current.name)
current = current.parent
return ".".join(reversed(parts))
@property
def file_path(self) -> str:
"""
Return the object's module file path.
Returns:
The object's module file path.
"""
return inspect.getabsfile(self.root.obj)
@property
def root(self) -> "ObjectNode":
"""
Return the root of the tree.
Returns:
The root of the tree.
"""
if self.parent is not None:
return self.parent.root
return self
def is_module(self) -> bool:
"""
Tell if this node's object is a module.
Returns:
The root of the tree.
"""
return inspect.ismodule(self.obj)
def is_class(self) -> bool:
"""
Tell if this node's object is a class.
Returns:
If this node's object is a class.
"""
return inspect.isclass(self.obj)
def is_function(self) -> bool:
"""
Tell if this node's object is a function.
Returns:
If this node's object is a function.
"""
return inspect.isfunction(self.obj)
def is_coroutine_function(self) -> bool:
"""
Tell if this node's object is a coroutine.
Returns:
If this node's object is a coroutine.
"""
return inspect.iscoroutinefunction(self.obj)
def is_property(self) -> bool:
"""
Tell if this node's object is a property.
Returns:
If this node's object is a property.
"""
return isinstance(self.obj, property) or self.is_cached_property()
def is_cached_property(self) -> bool:
"""
Tell if this node's object is a cached property.
Returns:
If this node's object is a cached property.
"""
return isinstance(self.obj, cached_property)
def parent_is_class(self) -> bool:
"""
Tell if the object of this node's parent is a class.
Returns:
If the object of this node's parent is a class.
"""
return bool(self.parent and self.parent.is_class())
def is_method(self) -> bool:
"""
Tell if this node's object is a method.
Returns:
If this node's object is a method.
"""
function_type = type(lambda: None)
return self.parent_is_class() and isinstance(self.obj, function_type)
def is_method_descriptor(self) -> bool:
"""
Tell if this node's object is a method descriptor.
Built-in methods (e.g. those implemented in C/Rust) are often
method descriptors, rather than normal methods.
Returns:
If this node's object is a method descriptor.
"""
return inspect.ismethoddescriptor(self.obj)
def is_staticmethod(self) -> bool:
"""
Tell if this node's object is a staticmethod.
Returns:
If this node's object is a staticmethod.
"""
if not self.parent:
return False
self_from_parent = self.parent.obj.__dict__.get(self.name, None)
return self.parent_is_class() and isinstance(self_from_parent, staticmethod)
def is_classmethod(self) -> bool:
"""
Tell if this node's object is a classmethod.
Returns:
If this node's object is a classmethod.
"""
if not self.parent:
return False
self_from_parent = self.parent.obj.__dict__.get(self.name, None)
return self.parent_is_class() and isinstance(self_from_parent, classmethod)
dotted_path: str
property
readonly
¤
Return the Python dotted path to the object.
Returns:
Type | Description |
---|---|
str |
The Python dotted path to the object. |
file_path: str
property
readonly
¤
Return the object's module file path.
Returns:
Type | Description |
---|---|
str |
The object's module file path. |
root: ObjectNode
property
readonly
¤
Return the root of the tree.
Returns:
Type | Description |
---|---|
ObjectNode |
The root of the tree. |
__init__(self, obj, name, parent=None)
special
¤
Initialize the object.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
obj |
Any |
A Python object. |
required |
name |
str |
The object's name. |
required |
parent |
Optional[ObjectNode] |
The object's parent node. |
None |
Source code in pytkdocs/loader.py
def __init__(self, obj: Any, name: str, parent: Optional["ObjectNode"] = None) -> None:
"""
Initialize the object.
Arguments:
obj: A Python object.
name: The object's name.
parent: The object's parent node.
"""
try:
obj = inspect.unwrap(obj)
except Exception: # noqa: S110,W0703 (we purposely catch every possible exception)
# inspect.unwrap at some point runs hasattr(obj, "__wrapped__"),
# which triggers the __getattr__ method of the object, which in
# turn can raise various exceptions. Probably not just __getattr__.
# See https://github.com/pawamoy/pytkdocs/issues/45
pass # noqa: WPS420 (no other way than passing)
self.obj: Any = obj
"""The actual Python object."""
self.name: str = name
"""The Python object's name."""
self.parent: Optional[ObjectNode] = parent
"""The parent node."""
is_cached_property(self)
¤
Tell if this node's object is a cached property.
Returns:
Type | Description |
---|---|
bool |
If this node's object is a cached property. |
Source code in pytkdocs/loader.py
def is_cached_property(self) -> bool:
"""
Tell if this node's object is a cached property.
Returns:
If this node's object is a cached property.
"""
return isinstance(self.obj, cached_property)
is_class(self)
¤
Tell if this node's object is a class.
Returns:
Type | Description |
---|---|
bool |
If this node's object is a class. |
Source code in pytkdocs/loader.py
def is_class(self) -> bool:
"""
Tell if this node's object is a class.
Returns:
If this node's object is a class.
"""
return inspect.isclass(self.obj)
is_classmethod(self)
¤
Tell if this node's object is a classmethod.
Returns:
Type | Description |
---|---|
bool |
If this node's object is a classmethod. |
Source code in pytkdocs/loader.py
def is_classmethod(self) -> bool:
"""
Tell if this node's object is a classmethod.
Returns:
If this node's object is a classmethod.
"""
if not self.parent:
return False
self_from_parent = self.parent.obj.__dict__.get(self.name, None)
return self.parent_is_class() and isinstance(self_from_parent, classmethod)
is_coroutine_function(self)
¤
Tell if this node's object is a coroutine.
Returns:
Type | Description |
---|---|
bool |
If this node's object is a coroutine. |
Source code in pytkdocs/loader.py
def is_coroutine_function(self) -> bool:
"""
Tell if this node's object is a coroutine.
Returns:
If this node's object is a coroutine.
"""
return inspect.iscoroutinefunction(self.obj)
is_function(self)
¤
Tell if this node's object is a function.
Returns:
Type | Description |
---|---|
bool |
If this node's object is a function. |
Source code in pytkdocs/loader.py
def is_function(self) -> bool:
"""
Tell if this node's object is a function.
Returns:
If this node's object is a function.
"""
return inspect.isfunction(self.obj)
is_method(self)
¤
Tell if this node's object is a method.
Returns:
Type | Description |
---|---|
bool |
If this node's object is a method. |
Source code in pytkdocs/loader.py
def is_method(self) -> bool:
"""
Tell if this node's object is a method.
Returns:
If this node's object is a method.
"""
function_type = type(lambda: None)
return self.parent_is_class() and isinstance(self.obj, function_type)
is_method_descriptor(self)
¤
Tell if this node's object is a method descriptor.
Built-in methods (e.g. those implemented in C/Rust) are often method descriptors, rather than normal methods.
Returns:
Type | Description |
---|---|
bool |
If this node's object is a method descriptor. |
Source code in pytkdocs/loader.py
def is_method_descriptor(self) -> bool:
"""
Tell if this node's object is a method descriptor.
Built-in methods (e.g. those implemented in C/Rust) are often
method descriptors, rather than normal methods.
Returns:
If this node's object is a method descriptor.
"""
return inspect.ismethoddescriptor(self.obj)
is_module(self)
¤
Tell if this node's object is a module.
Returns:
Type | Description |
---|---|
bool |
The root of the tree. |
Source code in pytkdocs/loader.py
def is_module(self) -> bool:
"""
Tell if this node's object is a module.
Returns:
The root of the tree.
"""
return inspect.ismodule(self.obj)
is_property(self)
¤
Tell if this node's object is a property.
Returns:
Type | Description |
---|---|
bool |
If this node's object is a property. |
Source code in pytkdocs/loader.py
def is_property(self) -> bool:
"""
Tell if this node's object is a property.
Returns:
If this node's object is a property.
"""
return isinstance(self.obj, property) or self.is_cached_property()
is_staticmethod(self)
¤
Tell if this node's object is a staticmethod.
Returns:
Type | Description |
---|---|
bool |
If this node's object is a staticmethod. |
Source code in pytkdocs/loader.py
def is_staticmethod(self) -> bool:
"""
Tell if this node's object is a staticmethod.
Returns:
If this node's object is a staticmethod.
"""
if not self.parent:
return False
self_from_parent = self.parent.obj.__dict__.get(self.name, None)
return self.parent_is_class() and isinstance(self_from_parent, staticmethod)
parent_is_class(self)
¤
Tell if the object of this node's parent is a class.
Returns:
Type | Description |
---|---|
bool |
If the object of this node's parent is a class. |
Source code in pytkdocs/loader.py
def parent_is_class(self) -> bool:
"""
Tell if the object of this node's parent is a class.
Returns:
If the object of this node's parent is a class.
"""
return bool(self.parent and self.parent.is_class())
field_is_inherited(field_name, fields_name, base_class)
¤
Check if a field with a certain name was inherited from parent classes.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
field_name |
str |
The name of the field to check. |
required |
fields_name |
str |
The name of the attribute in which the fields are stored. |
required |
base_class |
type |
The base class in which the field appears. |
required |
Returns:
Type | Description |
---|---|
bool |
Whether the field was inherited. |
Source code in pytkdocs/loader.py
def field_is_inherited(field_name: str, fields_name: str, base_class: type) -> bool:
"""
Check if a field with a certain name was inherited from parent classes.
Arguments:
field_name: The name of the field to check.
fields_name: The name of the attribute in which the fields are stored.
base_class: The base class in which the field appears.
Returns:
Whether the field was inherited.
"""
# To tell if a field was inherited, we 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]
return field_name in set(
chain(
*(getattr(parent_class, fields_name, {}).keys() for parent_class in base_class.__mro__[1:-1]),
),
)
get_object_tree(path, new_path_syntax=False)
¤
Transform a path into an actual Python object.
The path can be arbitrary long. You can pass the path to a package,
a module, a class, a function or a global variable, as deep as you
want, as long as the deepest module is importable through
importlib.import_module
and each object is obtainable through
the getattr
method. It is not possible to load local objects.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
path |
str |
The dot/colon-separated path of the object. |
required |
new_path_syntax |
bool |
Whether to use the "colon" syntax for the path. |
False |
Exceptions:
Type | Description |
---|---|
ValueError |
When the path is not valid (evaluates to |
ImportError |
When the object or its parent module could not be imported. |
Returns:
Type | Description |
---|---|
ObjectNode |
The leaf node representing the object and its parents. |
Source code in pytkdocs/loader.py
def get_object_tree(path: str, new_path_syntax: bool = False) -> ObjectNode:
"""
Transform a path into an actual Python object.
The path can be arbitrary long. You can pass the path to a package,
a module, a class, a function or a global variable, as deep as you
want, as long as the deepest module is importable through
`importlib.import_module` and each object is obtainable through
the `getattr` method. It is not possible to load local objects.
Args:
path: The dot/colon-separated path of the object.
new_path_syntax: Whether to use the "colon" syntax for the path.
Raises:
ValueError: When the path is not valid (evaluates to `False`).
ImportError: When the object or its parent module could not be imported.
Returns:
The leaf node representing the object and its parents.
"""
if not path:
raise ValueError(f"path must be a valid Python path, not {path}")
objects: List[str] = []
if ":" in path or new_path_syntax:
try:
module_path, object_path = path.split(":")
except ValueError: # no colon
module_path, objects = path, []
else:
objects = object_path.split(".")
# let the ImportError bubble up
parent_module = importlib.import_module(module_path)
else:
# We will try to import the longest dotted-path first.
# If it fails, we remove the right-most part and put it in a list of "objects", used later.
# We loop until we find the deepest importable submodule.
obj_parent_modules = path.split(".")
while True:
parent_module_path = ".".join(obj_parent_modules)
try:
parent_module = importlib.import_module(parent_module_path)
except ImportError as error:
if len(obj_parent_modules) == 1:
raise ImportError(
f"Importing '{path}' failed, possible causes are:\n"
f"- an exception happened while importing\n"
f"- an element in the path does not exist",
) from error
objects.insert(0, obj_parent_modules.pop(-1))
else:
break
# We now have the module containing the desired object.
# We will build the object tree by iterating over the previously stored objects names
# and trying to get them as attributes.
current_node = ObjectNode(parent_module, parent_module.__name__)
for obj_name in objects:
obj = getattr(current_node.obj, obj_name)
child = ObjectNode(obj, obj_name, parent=current_node)
current_node = child
leaf = current_node
# We now try to get the "real" parent module, not the one the object was imported into.
# This is important if we want to be able to retrieve the docstring of an attribute for example.
# Once we find an object for which we could get the module, we stop trying to get the module.
# Once we reach the node before the root, we apply the module if found, and break.
real_module = None
while current_node.parent is not None:
if real_module is None:
real_module = inspect.getmodule(current_node.obj)
if inspect.ismodule(current_node.parent.obj):
if real_module is not None and real_module is not current_node.parent.obj:
current_node.parent = ObjectNode(real_module, real_module.__name__)
break
current_node = current_node.parent
return leaf
split_attr_name(attr_name)
¤
Split an attribute name into a first-order attribute name and remainder.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
attr_name |
str |
Attribute name (a) |
required |
Returns:
Type | Description |
---|---|
Tuple containing |
first_order_attr_name: Name of the first order attribute (a) remainder: The remainder (b.c) |
Source code in pytkdocs/loader.py
def split_attr_name(attr_name: str) -> Tuple[str, Optional[str]]:
"""
Split an attribute name into a first-order attribute name and remainder.
Args:
attr_name: Attribute name (a)
Returns:
Tuple containing:
first_order_attr_name: Name of the first order attribute (a)
remainder: The remainder (b.c)
"""
first_order_attr_name, *remaining = attr_name.split(".", maxsplit=1)
remainder = remaining[0] if remaining else None
return first_order_attr_name, remainder