bpo-35113: Fix inspect.getsource to return correct source for inner classes (#10307)
* Use ast module to find class definition
* Add NEWS entry
* Fix class with multiple children and move decorator code to the method
* Fix PR comments
1. Use node.decorator_list to select decorators
2. Remove unwanted variables in ClassVisitor
3. Simplify stack management as per review
* Add test for nested functions and async calls
* Fix pydoc test since comments are returned now correctly
* Set event loop policy as None to fix environment related change
* Refactor visit_AsyncFunctionDef and tests
* Refactor to use local variables and fix tests
* Add patch attribution
* Use self.addCleanup for asyncio
* Rename ClassVisitor to ClassFinder and fix asyncio cleanup
* Return first class inside conditional in case of multiple definitions. Remove decorator for class source.
* Add docstring to make the test correct
* Modify NEWS entry regarding decorators
* Return decorators too for bpo-15856
* Move ast and the class source code to top. Use proper Exception.
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 2dc8454..98a9c0a 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -473,6 +473,7 @@
def test_getcomments(self):
self.assertEqual(inspect.getcomments(mod), '# line 1\n')
self.assertEqual(inspect.getcomments(mod.StupidGit), '# line 20\n')
+ self.assertEqual(inspect.getcomments(mod2.cls160), '# line 159\n')
# If the object source file is not available, return None.
co = compile('x=1', '_non_existing_filename.py', 'exec')
self.assertIsNone(inspect.getcomments(co))
@@ -709,6 +710,45 @@
def test_nested_func(self):
self.assertSourceEqual(mod2.cls135.func136, 136, 139)
+ def test_class_definition_in_multiline_string_definition(self):
+ self.assertSourceEqual(mod2.cls149, 149, 152)
+
+ def test_class_definition_in_multiline_comment(self):
+ self.assertSourceEqual(mod2.cls160, 160, 163)
+
+ def test_nested_class_definition_indented_string(self):
+ self.assertSourceEqual(mod2.cls173.cls175, 175, 176)
+
+ def test_nested_class_definition(self):
+ self.assertSourceEqual(mod2.cls183, 183, 188)
+ self.assertSourceEqual(mod2.cls183.cls185, 185, 188)
+
+ def test_class_decorator(self):
+ self.assertSourceEqual(mod2.cls196, 194, 201)
+ self.assertSourceEqual(mod2.cls196.cls200, 198, 201)
+
+ def test_class_inside_conditional(self):
+ self.assertSourceEqual(mod2.cls238, 238, 240)
+ self.assertSourceEqual(mod2.cls238.cls239, 239, 240)
+
+ def test_multiple_children_classes(self):
+ self.assertSourceEqual(mod2.cls203, 203, 209)
+ self.assertSourceEqual(mod2.cls203.cls204, 204, 206)
+ self.assertSourceEqual(mod2.cls203.cls204.cls205, 205, 206)
+ self.assertSourceEqual(mod2.cls203.cls207, 207, 209)
+ self.assertSourceEqual(mod2.cls203.cls207.cls205, 208, 209)
+
+ def test_nested_class_definition_inside_function(self):
+ self.assertSourceEqual(mod2.func212(), 213, 214)
+ self.assertSourceEqual(mod2.cls213, 218, 222)
+ self.assertSourceEqual(mod2.cls213().func219(), 220, 221)
+
+ def test_nested_class_definition_inside_async_function(self):
+ import asyncio
+ self.addCleanup(asyncio.set_event_loop_policy, None)
+ self.assertSourceEqual(asyncio.run(mod2.func225()), 226, 227)
+ self.assertSourceEqual(mod2.cls226, 231, 235)
+ self.assertSourceEqual(asyncio.run(mod2.cls226().func232()), 233, 234)
class TestNoEOL(GetSourceBase):
def setUp(self):