From 4968cd6644e0b88509a7e6fa6088dbcb0361838b Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Tue, 20 Jan 2026 18:20:06 +0800 Subject: [PATCH] gh-143999: Fix: handle suspended state on types.coroutine wrappers (GH-144000) (cherry picked from commit 76b484b9d16d6a3b1749dc89d99773b5b4a5c4a5) Co-authored-by: Yongtao Huang --- Lib/test/test_inspect/test_inspect.py | 24 +++++++++++++++++++ Lib/types.py | 4 ++++ ...-01-18-14-35-37.gh-issue-143999.MneN4O.rst | 1 + 3 files changed, 29 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-01-18-14-35-37.gh-issue-143999.MneN4O.rst diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 49996bba2ca9ed..beab76b516d48d 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -2962,6 +2962,30 @@ def running_check_generator(): # Running after the first yield next(self.generator) + def test_types_coroutine_wrapper_state(self): + def gen(): + yield 1 + yield 2 + + @types.coroutine + def wrapped_generator_coro(): + # return a generator iterator so types.coroutine + # wraps it into types._GeneratorWrapper. + return gen() + + g = wrapped_generator_coro() + self.addCleanup(g.close) + self.assertIs(type(g), types._GeneratorWrapper) + + # _GeneratorWrapper must provide gi_suspended/cr_suspended + # so inspect.get*state() doesn't raise AttributeError. + self.assertEqual(inspect.getgeneratorstate(g), inspect.GEN_CREATED) + self.assertEqual(inspect.getcoroutinestate(g), inspect.CORO_CREATED) + + next(g) + self.assertEqual(inspect.getgeneratorstate(g), inspect.GEN_SUSPENDED) + self.assertEqual(inspect.getcoroutinestate(g), inspect.CORO_SUSPENDED) + def test_easy_debugging(self): # repr() and str() of a generator state should contain the state name names = 'GEN_CREATED GEN_RUNNING GEN_SUSPENDED GEN_CLOSED'.split() diff --git a/Lib/types.py b/Lib/types.py index 1484c22ee9dffa..ff474c1414462c 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -261,10 +261,14 @@ def gi_running(self): @property def gi_yieldfrom(self): return self.__wrapped.gi_yieldfrom + @property + def gi_suspended(self): + return self.__wrapped.gi_suspended cr_code = gi_code cr_frame = gi_frame cr_running = gi_running cr_await = gi_yieldfrom + cr_suspended = gi_suspended def __next__(self): return next(self.__wrapped) def __iter__(self): diff --git a/Misc/NEWS.d/next/Library/2026-01-18-14-35-37.gh-issue-143999.MneN4O.rst b/Misc/NEWS.d/next/Library/2026-01-18-14-35-37.gh-issue-143999.MneN4O.rst new file mode 100644 index 00000000000000..dc87411aacc821 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-18-14-35-37.gh-issue-143999.MneN4O.rst @@ -0,0 +1 @@ +Fix an issue where :func:`inspect.getgeneratorstate` and :func:`inspect.getcoroutinestate` could fail for generators wrapped by :func:`types.coroutine` in the suspended state.