From 057b4492fea5432f264c759b31f5e43baa456714 Mon Sep 17 00:00:00 2001 From: tristanlatr Date: Fri, 7 Apr 2023 21:31:00 -0400 Subject: [PATCH 1/4] Fix #671 --- pydoctor/model.py | 15 ++++++++++++++ pydoctor/test/test_astbuilder.py | 35 +++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/pydoctor/model.py b/pydoctor/model.py index d400be924..a08f6a12f 100644 --- a/pydoctor/model.py +++ b/pydoctor/model.py @@ -827,6 +827,18 @@ class Attribute(Inheritable): None value means the value is not initialized at the current point of the the process. """ + def _inherits_instance_variable_kind(self) -> None: + if self.value is not None: + return + if self.kind is not DocumentableKind.CLASS_VARIABLE: + return + docsources = self.docsources() + next(docsources) + for inherited in docsources: + if inherited.kind is DocumentableKind.INSTANCE_VARIABLE: + self.kind = DocumentableKind.INSTANCE_VARIABLE + return + # Work around the attributes of the same name within the System class. _ModuleT = Module _PackageT = Package @@ -1429,6 +1441,9 @@ def postProcess(self) -> None: if is_exception(cls): cls.kind = DocumentableKind.EXCEPTION + for attrib in self.objectsOfType(Attribute): + attrib._inherits_instance_variable_kind() + for post_processor in self._post_processors: post_processor(self) diff --git a/pydoctor/test/test_astbuilder.py b/pydoctor/test/test_astbuilder.py index 63782d7d7..7b2682de2 100644 --- a/pydoctor/test/test_astbuilder.py +++ b/pydoctor/test/test_astbuilder.py @@ -2379,4 +2379,37 @@ def __init__(self): ''' mod = fromText(src, systemcls=systemcls) - assert getConstructorsText(mod.contents['Animal']) == "Animal()" \ No newline at end of file + 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 + +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 From 0c60df6cf47b2873d074bbd783afee9b9fac9d91 Mon Sep 17 00:00:00 2001 From: tristanlatr Date: Sat, 8 Apr 2023 12:45:51 -0400 Subject: [PATCH 2/4] Simplify implementation and add docs. Now we don't care if value if None, as soon as a class variable has an inherited member of type instance variable, it become an instance variable as well. --- pydoctor/model.py | 8 ++++--- pydoctor/test/test_astbuilder.py | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/pydoctor/model.py b/pydoctor/model.py index a08f6a12f..810938c1b 100644 --- a/pydoctor/model.py +++ b/pydoctor/model.py @@ -828,8 +828,10 @@ class Attribute(Inheritable): """ def _inherits_instance_variable_kind(self) -> None: - if self.value is not None: - return + """ + 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 self.kind is not DocumentableKind.CLASS_VARIABLE: return docsources = self.docsources() @@ -837,7 +839,7 @@ def _inherits_instance_variable_kind(self) -> None: for inherited in docsources: if inherited.kind is DocumentableKind.INSTANCE_VARIABLE: self.kind = DocumentableKind.INSTANCE_VARIABLE - return + break # Work around the attributes of the same name within the System class. _ModuleT = Module diff --git a/pydoctor/test/test_astbuilder.py b/pydoctor/test/test_astbuilder.py index 7b2682de2..8a16e90a8 100644 --- a/pydoctor/test/test_astbuilder.py +++ b/pydoctor/test/test_astbuilder.py @@ -2397,6 +2397,43 @@ class Stuff(Thing): 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 From 9a44cc2ede8a393ce461a56ec1690a37c436d898 Mon Sep 17 00:00:00 2001 From: tristanlatr Date: Sat, 9 Sep 2023 22:46:58 -0400 Subject: [PATCH 3/4] Refactor so it doesn't create a new documentable method --- pydoctor/model.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pydoctor/model.py b/pydoctor/model.py index ee678c55b..3318da439 100644 --- a/pydoctor/model.py +++ b/pydoctor/model.py @@ -849,20 +849,6 @@ class Attribute(Inheritable): None value means the value is not initialized at the current point of the the process. """ - def _inherits_instance_variable_kind(self) -> 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 self.kind is not DocumentableKind.CLASS_VARIABLE: - return - docsources = self.docsources() - next(docsources) - for inherited in docsources: - if inherited.kind is DocumentableKind.INSTANCE_VARIABLE: - self.kind = DocumentableKind.INSTANCE_VARIABLE - break - # Work around the attributes of the same name within the System class. _ModuleT = Module _PackageT = Package @@ -1469,7 +1455,7 @@ def postProcess(self) -> None: cls.kind = DocumentableKind.EXCEPTION for attrib in self.objectsOfType(Attribute): - attrib._inherits_instance_variable_kind() + _inherits_instance_variable_kind(attrib) for post_processor in self._post_processors: post_processor(self) @@ -1482,6 +1468,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]]: From b41312f0a4e37987e7f2e5c1e92c557ba85d9c42 Mon Sep 17 00:00:00 2001 From: tristanlatr Date: Sat, 9 Sep 2023 22:50:34 -0400 Subject: [PATCH 4/4] Add readme entry --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 1c71c6ccd..a9e91dd2a 100644 --- a/README.rst +++ b/README.rst @@ -95,6 +95,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 ^^^^^^^^^^^^^^^