Skip to content

Commit

Permalink
more simplifications
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlatr committed Nov 18, 2022
1 parent d084224 commit d5fddc4
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 98 deletions.
26 changes: 11 additions & 15 deletions pydoctor/astbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def looks_like_property_func_decorator(dottedname:List[str], ctx:model.Documenta

def get_inherited_property(dottedname:List[str], _parent: model.Documentable) -> Optional[model.Attribute]:
"""
Fetch the inherited property that this new decorator overrides.
Fetch the inherited Attribute that this new decorator overrides.
None if it doesn't exist.
"""
if not get_property_function_kind(dottedname):
Expand Down Expand Up @@ -882,21 +882,21 @@ def _handleFunctionDef(self,
# Looks like inherited property
if len(property_decorator_dottedname)>2:
inherited_property = get_inherited_property(property_decorator_dottedname, parent)
if inherited_property and inherited_property._property_info:
if inherited_property and inherited_property.kind is model.DocumentableKind.PROPERTY:
func_property = self.builder.addAttribute(node.name,
kind=model.DocumentableKind.PROPERTY,
parent=parent)
func_property.setLineNumber(lineno)
func_property.decorators = node.decorator_list
# copy property info
func_property._property_info = model.PropertyInfo(
**attr.asdict(inherited_property._property_info))
func_property.contents = dict(inherited_property.contents)
is_new_property = True

else:
# fetch property info to add this info to it
maybe_prop = self.builder.current.contents.get(node.name)
if isinstance(maybe_prop, model.Attribute) and maybe_prop._property_info:
if maybe_prop and maybe_prop.kind is model.DocumentableKind.PROPERTY:
assert isinstance(maybe_prop, model.Attribute)
func_property = maybe_prop

elif is_property:
Expand Down Expand Up @@ -1005,16 +1005,15 @@ def add_arg(name: str, kind: Any, default: Optional[ast.expr]) -> None:
func.signature = signature

if func_property is not None:

assert prop_func_kind is not None
property_name = prop_func_kind.name.lower()
if is_classmethod:
func_property.report(f'{func_property.fullName()} is both property and classmethod')
func_property.report(f'{func_property.fullName()} is both property {property_name} and classmethod')
if is_staticmethod:
func_property.report(f'{func_property.fullName()} is both property and staticmethod')
func_property.report(f'{func_property.fullName()} is both property {property_name} and staticmethod')

if prop_func_kind is not None:
# Store the fact that this function implements one of the getter/setter/deleter
assert func_property._property_info is not None
func_property._property_info.set(prop_func_kind, func)
# Store the fact that this function implements one of the getter/setter/deleter
func_property.contents[property_name] = func


def depart_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
Expand Down Expand Up @@ -1249,9 +1248,6 @@ def addAttribute(self,
parentMod = self.currentMod
attr = system.Attribute(system, name, parent)
attr.kind = kind
if kind is model.DocumentableKind.PROPERTY:
# init property info if this attribute is a property
attr._property_info = model.PropertyInfo()
attr.parentMod = parentMod
system.addObject(attr)
self.currentAttr = attr
Expand Down
65 changes: 10 additions & 55 deletions pydoctor/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@
# but the arrangement of the tree is far from arbitrary and there is
# at least some code that now relies on this. so here's a list:
#
# Packages can contain Packages and Modules
# Packages can contain Packages and Modules, Functions and Classes
# Modules can contain Functions and Classes
# Classes can contain Functions (in this case they get called Methods) and
# Classes
# Functions can't contain anything.
# Attributes can exceptionnaly contains Functions, in the case of a property.


_string_lineno_is_end = sys.version_info < (3,8) \
Expand Down Expand Up @@ -711,31 +712,6 @@ class PropertyFunctionKind(Enum):
SETTER = 2
DELETER = 3

@attr.s(auto_attribs=True)
class PropertyInfo:

getter:Optional['Function'] = None
"""
The getter.
"""
setter: Optional['Function'] = None
"""
None if it has not been set with C{@name.setter} decorator.
"""
deleter: Optional['Function'] = None
"""
None if it has not been set with C{@name.deleter} decorator.
"""

def set(self, kind:PropertyFunctionKind, func:'Function') -> None:
if kind is PropertyFunctionKind.GETTER:
self.getter = func
elif kind is PropertyFunctionKind.SETTER:
self.setter = func
elif kind is PropertyFunctionKind.DELETER:
self.deleter = func
else:
assert False

class Function(Inheritable):
kind = DocumentableKind.FUNCTION
Expand Down Expand Up @@ -769,23 +745,21 @@ class FunctionOverload:
signature: Signature
decorators: Sequence[ast.expr]

def init_property(attr:'Attribute') -> Iterator['Function']:
def init_property(attr:'Attribute') -> Iterator['Documentable']:
"""
Initiates the L{Attribute} that represent the property in the tree.
Returns the functions to remove from the tree. If the property matchup fails
"""
info = attr._property_info
assert info is not None

getter = info.getter
setter = info.setter
deleter = info.deleter

getter = attr.contents.get('getter')
setter = attr.contents.get('setter')
deleter = attr.contents.get('deleter')

if getter is None:
if not isinstance(getter, Function):
# The getter should never be None
return ()

# avoid cyclic import
from pydoctor import epydoc2stan

Expand Down Expand Up @@ -843,25 +817,6 @@ class Attribute(Inheritable):
Or maybe it can be that the attribute is a property.
"""

_property_info:Optional[PropertyInfo] = None

@property
def property_setter(self) -> Optional[Function]:
"""
The property setter L{Function}, is any defined.
Only applicable if L{kind} is L{DocumentableKind.PROPERTY}
"""
if self._property_info:
return self._property_info.setter
return None

@property
def property_deleter(self) -> Optional[Function]:
"""Idem for the deleter."""
if self._property_info:
return self._property_info.deleter
return None

# Work around the attributes of the same name within the System class.
_ModuleT = Module
_PackageT = Package
Expand Down Expand Up @@ -1458,7 +1413,7 @@ def postProcess(self) -> None:
# We are transforming the tree at the end only.
to_delete: List[Documentable] = []
for attr in self.objectsOfType(Attribute):
if attr._property_info is not None:
if attr.kind is DocumentableKind.PROPERTY:
to_delete.extend(init_property(attr))
for obj in set(to_delete):
self._remove(obj)
Expand Down
4 changes: 2 additions & 2 deletions pydoctor/templatewriter/pages/attributechild.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from twisted.web.iweb import ITemplateLoader
from twisted.web.template import Tag, renderer, tags

from pydoctor.model import Attribute, DocumentableKind
from pydoctor.model import Attribute, DocumentableKind, Function
from pydoctor import epydoc2stan
from pydoctor.templatewriter import TemplateElement, util
from pydoctor.templatewriter.pages import format_decorators
Expand Down Expand Up @@ -93,7 +93,7 @@ def propertyInfo(self, request: object, tag: Tag) -> "Flattenable":

assert isinstance(self.ob, Attribute)

for func in [f for f in (self.ob.property_setter, self.ob.property_deleter) if f]:
for func in [f for name,f in self.ob.contents.items() if isinstance(f, Function) and name != 'getter']:
r.append(FunctionChild(self.docgetter, func, extras=[],
loader=self._funcLoader, silent_undoc=True))
return r
52 changes: 26 additions & 26 deletions pydoctor/test/test_astbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1535,12 +1535,12 @@ def prop(self):
assert getter.kind is model.DocumentableKind.PROPERTY
assert getter.docstring == """Getter."""

setter = getter.property_setter
setter = getter.contents['setter']
assert isinstance(setter, model.Function)
assert setter.kind is model.DocumentableKind.METHOD
assert setter.docstring == """Setter."""

deleter = getter.property_deleter
deleter = getter.contents['deleter']
assert isinstance(deleter, model.Function)
assert deleter.kind is model.DocumentableKind.METHOD
assert deleter.docstring == """Deleter."""
Expand Down Expand Up @@ -1596,7 +1596,7 @@ def prop():
C = mod.contents['C']
assert C.contents['prop'].kind is model.DocumentableKind.PROPERTY
captured = capsys.readouterr().out
assert captured == f"mod:3: mod.C.prop is both property and {decoration}\n"
assert captured == f"mod:3: mod.C.prop is both property getter and {decoration}\n"

@systemcls_param
def test_ignore_function_contents(systemcls: Type[model.System]) -> None:
Expand Down Expand Up @@ -2117,33 +2117,33 @@ def spam(self):
assert spam2.kind is model.DocumentableKind.PROPERTY
assert spam3.kind is model.DocumentableKind.PROPERTY

assert isinstance(spam0.property_setter, model.Function)
assert isinstance(spam1.property_setter, model.Function)
assert isinstance(spam2.property_setter, model.Function)
assert isinstance(spam3.property_setter, model.Function)
assert isinstance(spam0.contents['setter'], model.Function)
assert isinstance(spam1.contents['setter'], model.Function)
assert isinstance(spam2.contents['setter'], model.Function)
assert isinstance(spam3.contents['setter'], model.Function)

assert isinstance(spam0.property_deleter, model.Function)
assert isinstance(spam1.property_deleter, model.Function)
assert isinstance(spam2.property_deleter, model.Function)
assert isinstance(spam3.property_deleter, model.Function)
assert isinstance(spam0.contents['deleter'], model.Function)
assert isinstance(spam1.contents['deleter'], model.Function)
assert isinstance(spam2.contents['deleter'], model.Function)
assert isinstance(spam3.contents['deleter'], model.Function)

assert spam0._property_info.getter.fullName() == 'mod.BaseClass.spam.getter' # type:ignore
assert spam0._property_info.setter.fullName() == 'mod.BaseClass.spam.setter' # type:ignore
assert spam0._property_info.deleter.fullName() == 'mod.BaseClass.spam.deleter' # type:ignore
assert spam0.contents['getter'].fullName() == 'mod.BaseClass.spam.getter'
assert spam0.contents['setter'].fullName() == 'mod.BaseClass.spam.setter'
assert spam0.contents['deleter'].fullName() == 'mod.BaseClass.spam.deleter'

assert spam1._property_info.getter.fullName() == 'mod.SubClass.spam.getter' # type:ignore
assert spam1._property_info.setter.fullName() == 'mod.BaseClass.spam.setter' # type:ignore
assert spam1._property_info.deleter.fullName() == 'mod.BaseClass.spam.deleter' # type:ignore
assert spam1.contents['getter'].fullName() == 'mod.SubClass.spam.getter'
assert spam1.contents['setter'].fullName() == 'mod.BaseClass.spam.setter'
assert spam1.contents['deleter'].fullName() == 'mod.BaseClass.spam.deleter'

assert spam2._property_info.getter.fullName() == 'mod.BaseClass.spam.getter' # type:ignore
assert spam2._property_info.setter.fullName() == 'mod.SubClass2.spam.setter' # type:ignore
assert spam2._property_info.deleter.fullName() == 'mod.BaseClass.spam.deleter' # type:ignore
assert spam2.contents['getter'].fullName() == 'mod.BaseClass.spam.getter'
assert spam2.contents['setter'].fullName() == 'mod.SubClass2.spam.setter'
assert spam2.contents['deleter'].fullName() == 'mod.BaseClass.spam.deleter'

assert spam3._property_info.getter.fullName() == 'mod.BaseClass.spam.getter' # type:ignore
assert spam3._property_info.setter.fullName() == 'mod.BaseClass.spam.setter' # type:ignore
assert spam3._property_info.deleter.fullName() == 'mod.SubClass3.spam.deleter' # type:ignore
assert spam3.contents['getter'].fullName() == 'mod.BaseClass.spam.getter'
assert spam3.contents['setter'].fullName() == 'mod.BaseClass.spam.setter'
assert spam3.contents['deleter'].fullName() == 'mod.SubClass3.spam.deleter'

assert spam1._property_info.getter is not spam0._property_info.getter # type:ignore
assert spam1.contents['getter'] is not spam0.contents['getter']

@systemcls_param
def test_property_old_school(systemcls: Type[model.System], capsys: CapSys) -> None:
Expand Down Expand Up @@ -2497,5 +2497,5 @@ def spam(self):
mod = fromText(src, systemcls=systemcls)
p = mod.contents['BaseClass'].contents['spam']
assert isinstance(p, model.Attribute)
assert isinstance(p.property_setter, model.Function)
assert isinstance(p.property_deleter, model.Function)
assert isinstance(p.contents['setter'], model.Function)
assert isinstance(p.contents['deleter'], model.Function)

0 comments on commit d5fddc4

Please sign in to comment.