diff --git a/README.rst b/README.rst index feb4e91ac..8a9c2b594 100644 --- a/README.rst +++ b/README.rst @@ -96,6 +96,7 @@ in development * Add highlighting when clicking on "View In Hierarchy" link from class page. * Recognize variadic generics type variables (PEP 646). * Fix support for introspection of cython3 generated modules. +* Instance variables are marked as such across subclasses. pydoctor 23.4.1 ^^^^^^^^^^^^^^^ diff --git a/pydoctor/model.py b/pydoctor/model.py index 7a720f5e1..2cb8cc375 100644 --- a/pydoctor/model.py +++ b/pydoctor/model.py @@ -1452,6 +1452,9 @@ def postProcess(self) -> None: if is_exception(cls): cls.kind = DocumentableKind.EXCEPTION + for attrib in self.objectsOfType(Attribute): + _inherits_instance_variable_kind(attrib) + for post_processor in self._post_processors: post_processor(self) @@ -1463,6 +1466,20 @@ def fetchIntersphinxInventories(self, cache: CacheT) -> None: for url in self.options.intersphinx: self.intersphinx.update(cache, url) +def _inherits_instance_variable_kind(attr: Attribute) -> None: + """ + If any of the inherited members of a class variable is an instance variable, + then the subclass' class variable become an instance variable as well. + """ + if attr.kind is not DocumentableKind.CLASS_VARIABLE: + return + docsources = attr.docsources() + next(docsources) + for inherited in docsources: + if inherited.kind is DocumentableKind.INSTANCE_VARIABLE: + attr.kind = DocumentableKind.INSTANCE_VARIABLE + break + def get_docstring( obj: Documentable ) -> Tuple[Optional[str], Optional[Documentable]]: diff --git a/pydoctor/test/test_astbuilder.py b/pydoctor/test/test_astbuilder.py index 8edba83aa..b5af2de2e 100644 --- a/pydoctor/test/test_astbuilder.py +++ b/pydoctor/test/test_astbuilder.py @@ -2421,6 +2421,76 @@ def __init__(self): mod = fromText(src, systemcls=systemcls) assert getConstructorsText(mod.contents['Animal']) == "Animal()" +@systemcls_param +def test_class_var_override(systemcls: Type[model.System]) -> None: + + src = '''\ + from number import Number + class Thing(object): + def __init__(self): + self.var: Number = 1 + class Stuff(Thing): + var:float + ''' + + mod = fromText(src, systemcls=systemcls, modname='mod') + var = mod.system.allobjects['mod.Stuff.var'] + assert var.kind == model.DocumentableKind.INSTANCE_VARIABLE + +@systemcls_param +def test_class_var_override_traverse_subclasses(systemcls: Type[model.System]) -> None: + + src = '''\ + from number import Number + class Thing(object): + def __init__(self): + self.var: Number = 1 + class _Stuff(Thing): + ... + class Stuff(_Stuff): + var:float + ''' + + mod = fromText(src, systemcls=systemcls, modname='mod') + var = mod.system.allobjects['mod.Stuff.var'] + assert var.kind == model.DocumentableKind.INSTANCE_VARIABLE + + src = '''\ + from number import Number + class Thing(object): + def __init__(self): + self.var: Optional[Number] = 0 + class _Stuff(Thing): + var = None + class Stuff(_Stuff): + var: float + ''' + + mod = fromText(src, systemcls=systemcls, modname='mod') + var = mod.system.allobjects['mod._Stuff.var'] + assert var.kind == model.DocumentableKind.INSTANCE_VARIABLE + + mod = fromText(src, systemcls=systemcls, modname='mod') + var = mod.system.allobjects['mod.Stuff.var'] + assert var.kind == model.DocumentableKind.INSTANCE_VARIABLE + +def test_class_var_override_attrs() -> None: + + systemcls = AttrsSystem + + src = '''\ + import attr + @attr.s + class Thing(object): + var = attr.ib() + class Stuff(Thing): + var: float + ''' + + mod = fromText(src, systemcls=systemcls, modname='mod') + var = mod.system.allobjects['mod.Stuff.var'] + assert var.kind == model.DocumentableKind.INSTANCE_VARIABLE + @systemcls_param def test_explicit_annotation_wins_over_inferred_type(systemcls: Type[model.System]) -> None: """