Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix sidebar #602

Merged
merged 6 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pydoctor/templatewriter/pages/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ def __init__(self,
docgetter: Optional[util.DocGetter] = None
):
super().__init__(ob, template_lookup, docgetter)
self.baselists = util.inherited_members(self.ob)
self.baselists = util.class_members(self.ob)

def extras(self) -> List[Tag]:
r: List[Tag] = []
Expand Down
58 changes: 31 additions & 27 deletions pydoctor/templatewriter/pages/sidebar.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
"""
Classes for the sidebar generation.
"""
from typing import Any, Iterable, Iterator, List, Optional, Tuple, Type, Union
from typing import Any, Iterator, List, Optional, Sequence, Tuple, Type, Union
from twisted.web.iweb import IRequest, ITemplateLoader
from twisted.web.template import TagLoader, renderer, Tag, Element, tags

from pydoctor import epydoc2stan
from pydoctor.model import Attribute, Class, Function, Documentable, Module
from pydoctor.templatewriter import util, TemplateLookup, TemplateElement

from pydoctor.napoleon.iterators import peek_iter

class SideBar(TemplateElement):
"""
Sidebar.
Expand Down Expand Up @@ -112,7 +114,7 @@ class ObjContent(Element):
Composed by L{ContentList} elements.
"""

#TODO: Hide the childrenKindTitle if they are all private and show private is off -> need JS
#FIXME: https://github.com/twisted/pydoctor/issues/600

def __init__(self, loader: ITemplateLoader, ob: Documentable, documented_ob: Documentable,
template_lookup: TemplateLookup, depth: int, level: int = 0):
Expand All @@ -125,48 +127,50 @@ def __init__(self, loader: ITemplateLoader, ob: Documentable, documented_ob: Doc
self._depth = depth
self._level = level + 1

self.classList = self._getListOf(Class)
self.functionList = self._getListOf(Function)
self.variableList = self._getListOf(Attribute)
self.subModuleList = self._getListOf(Module)
_direct_children = self._children(inherited=False)

self.inheritedFunctionList = self._getListOf(Function, inherited=True) if isinstance(self.ob, Class) else None
self.inheritedVariableList = self._getListOf(Attribute, inherited=True) if isinstance(self.ob, Class) else None

def _getListOf(self, type_: Type[Documentable],
inherited: bool = False) -> Optional['ContentList']:
children = self._children(inherited=inherited)
if children:
things = [ child for child in children if isinstance(child, type_) ]
return self._getListFrom(things, expand=self._isExpandable(type_))
else:
return None
self.classList = self._getContentList(_direct_children, Class)
self.functionList = self._getContentList(_direct_children, Function)
self.variableList = self._getContentList(_direct_children, Attribute)
self.subModuleList = self._getContentList(_direct_children, Module)

self.inheritedFunctionList: Optional[ContentList] = None
self.inheritedVariableList: Optional[ContentList] = None

if isinstance(self.ob, Class):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Longer-term question: could this be added as a method to Documentable so we can just call it on self.ob rather than dispatching on type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the _children() method?
I’ts using processing code that is not currently in the model, in the util.py file.
But I agree it could be moved in the model.

We could introduce a method as follow:

def children(inherited:bool=False, ofclass:Optional[Type[Documentable]]=None) -> Iterator[Documentable]

_inherited_children = self._children(inherited=True)

self.inheritedFunctionList = self._getContentList(_inherited_children, Function)
self.inheritedVariableList = self._getContentList(_inherited_children, Attribute)

#TODO: ensure not to crash if heterogeneous Documentable types are passed

def _getListFrom(self, things: List[Documentable], expand: bool) -> Optional['ContentList']:
if things:
def _getContentList(self, children: Sequence[Documentable], type_: Type[Documentable]) -> Optional['ContentList']:
# We use the filter and iterators (instead of lists) for performance reasons.

things = peek_iter(filter(lambda o: isinstance(o, type_,), children))

if things.has_next():

assert self.loader is not None
return ContentList(ob=self.ob, children=things,
documented_ob=self.documented_ob,
expand=expand,
expand=self._isExpandable(type_),
nested_content_loader=self.loader,
template_lookup=self.template_lookup,
level_depth=(self._level, self._depth))
else:
return None


def _children(self, inherited: bool = False) -> Optional[List[Documentable]]:
def _children(self, inherited: bool = False) -> List[Documentable]:
"""
Compute the children of this object.
"""
if inherited:
if isinstance(self.ob, Class):
return sorted((o for o in util.list_inherited_members(self.ob) if o.isVisible),
assert isinstance(self.ob, Class), "Use inherited=True only with Class instances"
return sorted((o for o in util.inherited_members(self.ob) if o.isVisible),
key=util.objects_order)
else:
return None
else:
return sorted((o for o in self.ob.contents.values() if o.isVisible),
key=util.objects_order)
Expand Down Expand Up @@ -267,7 +271,7 @@ class ContentList(TemplateElement):
filename = 'sidebar-list.html'

def __init__(self, ob: Documentable,
children: Iterable[Documentable], documented_ob: Documentable,
children: Iterator[Documentable], documented_ob: Documentable,
expand: bool, nested_content_loader: ITemplateLoader, template_lookup: TemplateLookup,
level_depth: Tuple[int, int]):
super().__init__(loader=self.lookup_loader(template_lookup))
Expand All @@ -282,7 +286,7 @@ def __init__(self, ob: Documentable,
self.template_lookup = template_lookup

@renderer
def items(self, request: IRequest, tag: Tag) -> Iterable['ContentItem']:
def items(self, request: IRequest, tag: Tag) -> Iterator['ContentItem']:

for child in self.children:

Expand Down
13 changes: 7 additions & 6 deletions pydoctor/templatewriter/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ def objects_order(o: model.Documentable) -> Tuple[int, int, str]:
"""
return (-o.privacyClass.value, -o.kind.value if o.kind else 0, o.fullName().lower())

def inherited_members(cls: model.Class) -> List[Tuple[Tuple[model.Class, ...], Sequence[model.Documentable]]]:
def class_members(cls: model.Class) -> List[Tuple[Tuple[model.Class, ...], Sequence[model.Documentable]]]:
"""
Compute the inherited members of a class.
Returns the members as well as the inherited members of a class.

@returns: Tuples of tuple: C{inherited_via:Tuple[model.Class, ...], attributes:Sequence[model.Documentable]}.
"""
Expand All @@ -108,14 +108,15 @@ def inherited_members(cls: model.Class) -> List[Tuple[Tuple[model.Class, ...], S
baselists.append((baselist, attrs))
return baselists

def list_inherited_members(cls: model.Class) -> List[model.Documentable]:
def inherited_members(cls: model.Class) -> List[model.Documentable]:
"""
Like L{inherited_members}, but returns a plain list.
Returns only the inherited members of a class, as a plain list.
"""

children : List[model.Documentable] = []
for _,attrs in inherited_members(cls):
children.extend(attrs)
for inherited_via,attrs in class_members(cls):
if len(inherited_via)>1:
children.extend(attrs)
return children

def templatefile(filename: str) -> None:
Expand Down
22 changes: 22 additions & 0 deletions pydoctor/test/test_mro.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,25 @@ def z():...
assert isinstance(klass, model.Class)
assert klass.subclasses == [dimond.contents['A'], dimond.contents['B']]
assert list(util.overriding_subclasses(klass, 'z')) == [dimond.contents['A'], dimond.contents['C']]

def test_inherited_members() -> None:
"""
The inherited_members() function computes only the inherited members
of a given class. It does not include members defined in the class itself.
"""
dimond = fromText("""\
class _MyBase:
def z():...
class A(_MyBase):
def a():...
def z():...
class B(_MyBase):
def b():...
class C(A,B):
...
""", modname='diamond')

assert len(util.inherited_members(dimond.contents['B']))==1 # type:ignore
assert len(util.inherited_members(dimond.contents['C']))==3 # type:ignore
assert len(util.inherited_members(dimond.contents['A']))==0 # type:ignore
assert len(util.inherited_members(dimond.contents['_MyBase']))==0 # type:ignore
2 changes: 1 addition & 1 deletion pydoctor/test/test_templatewriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def test_multipleInheritanceNewClass(className: str) -> None:
getob = system.allobjects.get

if className == 'Diamond':
assert util.inherited_members(cls) == [
assert util.class_members(cls) == [
(
(getob('multipleinheritance.mod.Diamond'),),
[getob('multipleinheritance.mod.Diamond.newMethod')]
Expand Down