Repository: pylint-dev/astroid

Issue:
error during inference of class inheriting from another with `mod.Type` format
Consider package a `level` with a class `Model` defined in `level`'s `__init__.py` file.

```
class Model:
    data: int = 1
```

If a class `Test` inherits from `Model` as `class Test(Model)`, and `Model` comes from `from level import Model`,  then inferring `Test.data` works fine (below, A is an alias for astroid).

<img width="248" alt="Screen Shot 2021-02-19 at 09 41 09" src="https://user-images.githubusercontent.com/2905588/108505730-9b3c1900-7296-11eb-8bb8-5b66b7253cf4.png">

However, if a `Test` inherits from `Model` as `class Test(level.Model)` and `level` comes from `import level`, then inference triggers an exception.

<img width="784" alt="Screen Shot 2021-02-19 at 09 42 09" src="https://user-images.githubusercontent.com/2905588/108505815-beff5f00-7296-11eb-92a2-641be827e1f0.png">







Discussion:

Patch:
diff --git a/astroid/inference.py b/astroid/inference.py
--- a/astroid/inference.py
+++ b/astroid/inference.py
@@ -309,6 +309,7 @@ def infer_attribute(self, context=None):
         elif not context:
             context = contextmod.InferenceContext()
 
+        old_boundnode = context.boundnode
         try:
             context.boundnode = owner
             yield from owner.igetattr(self.attrname, context)
@@ -319,7 +320,7 @@ def infer_attribute(self, context=None):
         ):
             pass
         finally:
-            context.boundnode = None
+            context.boundnode = old_boundnode
     return dict(node=self, context=context)
 
 


Test patch:
diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py
--- a/tests/unittest_inference.py
+++ b/tests/unittest_inference.py
@@ -3894,6 +3894,65 @@ class Clazz(metaclass=_Meta):
         ).inferred()[0]
         assert isinstance(cls, nodes.ClassDef) and cls.name == "Clazz"
 
+    def test_infer_subclass_attr_outer_class(self):
+        node = extract_node(
+            """
+        class Outer:
+            data = 123
+
+        class Test(Outer):
+            pass
+        Test.data
+        """
+        )
+        inferred = next(node.infer())
+        assert isinstance(inferred, nodes.Const)
+        assert inferred.value == 123
+
+    def test_infer_subclass_attr_inner_class_works_indirectly(self):
+        node = extract_node(
+            """
+        class Outer:
+            class Inner:
+                data = 123
+        Inner = Outer.Inner
+
+        class Test(Inner):
+            pass
+        Test.data
+        """
+        )
+        inferred = next(node.infer())
+        assert isinstance(inferred, nodes.Const)
+        assert inferred.value == 123
+
+    def test_infer_subclass_attr_inner_class(self):
+        clsdef_node, attr_node = extract_node(
+            """
+        class Outer:
+            class Inner:
+                data = 123
+
+        class Test(Outer.Inner):
+            pass
+        Test  #@
+        Test.data  #@
+            """
+        )
+        clsdef = next(clsdef_node.infer())
+        assert isinstance(clsdef, nodes.ClassDef)
+        inferred = next(clsdef.igetattr("data"))
+        assert isinstance(inferred, nodes.Const)
+        assert inferred.value == 123
+        # Inferring the value of .data via igetattr() worked before the
+        # old_boundnode fixes in infer_subscript, so it should have been
+        # possible to infer the subscript directly. It is the difference
+        # between these two cases that led to the discovery of the cause of the
+        # bug in https://github.com/PyCQA/astroid/issues/904
+        inferred = next(attr_node.infer())
+        assert isinstance(inferred, nodes.Const)
+        assert inferred.value == 123
+
     def test_delayed_attributes_without_slots(self):
         ast_node = extract_node(
             """