[Python-checkins] GH-94597: Deprecate child watcher getters and setters (#98215)

gvanrossum webhook-mailer at python.org
Sat Oct 15 19:09:57 EDT 2022


https://github.com/python/cpython/commit/660f10248ba321e7783c07f3801991275e2aee1e
commit: 660f10248ba321e7783c07f3801991275e2aee1e
branch: main
author: Kumar Aditya <59607654+kumaraditya303 at users.noreply.github.com>
committer: gvanrossum <gvanrossum at gmail.com>
date: 2022-10-15T16:09:30-07:00
summary:

GH-94597: Deprecate child watcher getters and setters (#98215)

This is the next step for deprecating child watchers.

Until we've removed the API completely we have to use it, so this PR is mostly suppressing a lot of warnings when using the API internally.

Once the child watcher API is totally removed, the two child watcher implementations we actually use and need (Pidfd and Thread) will be turned into internal helpers.

files:
A Misc/NEWS.d/next/Library/2022-10-12-11-20-54.gh-issue-94597.GYJZlb.rst
M Doc/library/asyncio-policy.rst
M Doc/whatsnew/3.12.rst
M Lib/asyncio/unix_events.py
M Lib/test/test_asyncio/test_events.py
M Lib/test/test_asyncio/test_streams.py
M Lib/test/test_asyncio/test_subprocess.py
M Lib/test/test_asyncio/test_unix_events.py
M Lib/test/test_asyncio/utils.py

diff --git a/Doc/library/asyncio-policy.rst b/Doc/library/asyncio-policy.rst
index bfc3e3090fdc..052378ef3274 100644
--- a/Doc/library/asyncio-policy.rst
+++ b/Doc/library/asyncio-policy.rst
@@ -88,12 +88,16 @@ The abstract event loop policy base class is defined as follows:
 
       This function is Unix specific.
 
+      .. deprecated:: 3.12
+
    .. method:: set_child_watcher(watcher)
 
       Set the current child process watcher to *watcher*.
 
       This function is Unix specific.
 
+      .. deprecated:: 3.12
+
 
 .. _asyncio-policy-builtin:
 
@@ -158,12 +162,16 @@ implementation used by the asyncio event loop:
 
    Return the current child watcher for the current policy.
 
+   .. deprecated:: 3.12
+
 .. function:: set_child_watcher(watcher)
 
    Set the current child watcher to *watcher* for the current
    policy.  *watcher* must implement methods defined in the
    :class:`AbstractChildWatcher` base class.
 
+   .. deprecated:: 3.12
+
 .. note::
    Third-party event loops implementations might not support
    custom child watchers.  For such event loops, using
@@ -245,6 +253,8 @@ implementation used by the asyncio event loop:
 
    .. versionadded:: 3.8
 
+   .. deprecated:: 3.12
+
 .. class:: SafeChildWatcher
 
    This implementation uses active event loop from the main thread to handle
@@ -257,6 +267,8 @@ implementation used by the asyncio event loop:
    This solution is as safe as :class:`MultiLoopChildWatcher` and has the same *O(N)*
    complexity but requires a running event loop in the main thread to work.
 
+   .. deprecated:: 3.12
+
 .. class:: FastChildWatcher
 
    This implementation reaps every terminated processes by calling
@@ -269,6 +281,8 @@ implementation used by the asyncio event loop:
    This solution requires a running event loop in the main thread to work, as
    :class:`SafeChildWatcher`.
 
+   .. deprecated:: 3.12
+
 .. class:: PidfdChildWatcher
 
    This implementation polls process file descriptors (pidfds) to await child
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index ebc490691e30..ecc74e9beba2 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -126,6 +126,12 @@ asyncio
   if supported and :class:`~asyncio.ThreadedChildWatcher` otherwise).
   (Contributed by Kumar Aditya in :gh:`94597`.)
 
+* :func:`asyncio.set_child_watcher`, :func:`asyncio.get_child_watcher`,
+  :meth:`asyncio.AbstractEventLoopPolicy.set_child_watcher` and
+  :meth:`asyncio.AbstractEventLoopPolicy.get_child_watcher` are deprecated
+  and will be removed in Python 3.14.
+  (Contributed by Kumar Aditya in :gh:`94597`.)
+
 
 pathlib
 -------
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
index 9f9b03e0cb21..ea7010ee1073 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -195,30 +195,32 @@ def _make_write_pipe_transport(self, pipe, protocol, waiter=None,
     async def _make_subprocess_transport(self, protocol, args, shell,
                                          stdin, stdout, stderr, bufsize,
                                          extra=None, **kwargs):
-        with events.get_child_watcher() as watcher:
-            if not watcher.is_active():
-                # Check early.
-                # Raising exception before process creation
-                # prevents subprocess execution if the watcher
-                # is not ready to handle it.
-                raise RuntimeError("asyncio.get_child_watcher() is not activated, "
-                                   "subprocess support is not installed.")
-            waiter = self.create_future()
-            transp = _UnixSubprocessTransport(self, protocol, args, shell,
-                                              stdin, stdout, stderr, bufsize,
-                                              waiter=waiter, extra=extra,
-                                              **kwargs)
-
-            watcher.add_child_handler(transp.get_pid(),
-                                      self._child_watcher_callback, transp)
-            try:
-                await waiter
-            except (SystemExit, KeyboardInterrupt):
-                raise
-            except BaseException:
-                transp.close()
-                await transp._wait()
-                raise
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', DeprecationWarning)
+            with events.get_child_watcher() as watcher:
+                if not watcher.is_active():
+                    # Check early.
+                    # Raising exception before process creation
+                    # prevents subprocess execution if the watcher
+                    # is not ready to handle it.
+                    raise RuntimeError("asyncio.get_child_watcher() is not activated, "
+                                    "subprocess support is not installed.")
+                waiter = self.create_future()
+                transp = _UnixSubprocessTransport(self, protocol, args, shell,
+                                                stdin, stdout, stderr, bufsize,
+                                                waiter=waiter, extra=extra,
+                                                **kwargs)
+
+                watcher.add_child_handler(transp.get_pid(),
+                                        self._child_watcher_callback, transp)
+                try:
+                    await waiter
+                except (SystemExit, KeyboardInterrupt):
+                    raise
+                except BaseException:
+                    transp.close()
+                    await transp._wait()
+                    raise
 
         return transp
 
@@ -1469,6 +1471,9 @@ def get_child_watcher(self):
         if self._watcher is None:
             self._init_watcher()
 
+        warnings._deprecated("get_child_watcher",
+                            "{name!r} is deprecated as of Python 3.12 and will be "
+                            "removed in Python {remove}.", remove=(3, 14))
         return self._watcher
 
     def set_child_watcher(self, watcher):
@@ -1480,6 +1485,9 @@ def set_child_watcher(self, watcher):
             self._watcher.close()
 
         self._watcher = watcher
+        warnings._deprecated("set_child_watcher",
+                            "{name!r} is deprecated as of Python 3.12 and will be "
+                            "removed in Python {remove}.", remove=(3, 14))
 
 
 SelectorEventLoop = _UnixSelectorEventLoop
diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py
index 98b55dec37f4..cabe75f56d9f 100644
--- a/Lib/test/test_asyncio/test_events.py
+++ b/Lib/test/test_asyncio/test_events.py
@@ -2058,11 +2058,13 @@ def setUp(self):
             with warnings.catch_warnings():
                 warnings.simplefilter('ignore', DeprecationWarning)
                 watcher = asyncio.SafeChildWatcher()
-            watcher.attach_loop(self.loop)
-            asyncio.set_child_watcher(watcher)
+                watcher.attach_loop(self.loop)
+                asyncio.set_child_watcher(watcher)
 
         def tearDown(self):
-            asyncio.set_child_watcher(None)
+            with warnings.catch_warnings():
+                warnings.simplefilter('ignore', DeprecationWarning)
+                asyncio.set_child_watcher(None)
             super().tearDown()
 
 
@@ -2657,13 +2659,15 @@ def setUp(self):
             with warnings.catch_warnings():
                 warnings.simplefilter('ignore', DeprecationWarning)
                 watcher = asyncio.SafeChildWatcher()
-            watcher.attach_loop(self.loop)
-            asyncio.set_child_watcher(watcher)
+                watcher.attach_loop(self.loop)
+                asyncio.set_child_watcher(watcher)
 
     def tearDown(self):
         try:
             if sys.platform != 'win32':
-                asyncio.set_child_watcher(None)
+                with warnings.catch_warnings():
+                    warnings.simplefilter('ignore', DeprecationWarning)
+                    asyncio.set_child_watcher(None)
 
             super().tearDown()
         finally:
diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py
index 8fb9313e09dd..01d5407a497a 100644
--- a/Lib/test/test_asyncio/test_streams.py
+++ b/Lib/test/test_asyncio/test_streams.py
@@ -797,7 +797,9 @@ def test_read_all_from_pipe_reader(self):
             watcher = asyncio.SafeChildWatcher()
         watcher.attach_loop(self.loop)
         try:
-            asyncio.set_child_watcher(watcher)
+            with warnings.catch_warnings():
+                warnings.simplefilter('ignore', DeprecationWarning)
+                asyncio.set_child_watcher(watcher)
             create = asyncio.create_subprocess_exec(
                 *args,
                 pass_fds={wfd},
@@ -805,7 +807,9 @@ def test_read_all_from_pipe_reader(self):
             proc = self.loop.run_until_complete(create)
             self.loop.run_until_complete(proc.wait())
         finally:
-            asyncio.set_child_watcher(None)
+            with warnings.catch_warnings():
+                warnings.simplefilter('ignore', DeprecationWarning)
+                asyncio.set_child_watcher(None)
 
         os.close(wfd)
         data = self.loop.run_until_complete(reader.read(-1))
diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py
index 64df1b17189b..8e5511559061 100644
--- a/Lib/test/test_asyncio/test_subprocess.py
+++ b/Lib/test/test_asyncio/test_subprocess.py
@@ -582,7 +582,9 @@ async def kill_running():
         # manually to avoid a warning when the watcher is detached.
         if (sys.platform != 'win32' and
                 isinstance(self, SubprocessFastWatcherTests)):
-            asyncio.get_child_watcher()._callbacks.clear()
+            with warnings.catch_warnings():
+                warnings.simplefilter('ignore', DeprecationWarning)
+                asyncio.get_child_watcher()._callbacks.clear()
 
     async def _test_popen_error(self, stdin):
         if sys.platform == 'win32':
@@ -696,13 +698,17 @@ def setUp(self):
 
             watcher = self._get_watcher()
             watcher.attach_loop(self.loop)
-            policy.set_child_watcher(watcher)
+            with warnings.catch_warnings():
+                warnings.simplefilter('ignore', DeprecationWarning)
+                policy.set_child_watcher(watcher)
 
         def tearDown(self):
             super().tearDown()
             policy = asyncio.get_event_loop_policy()
-            watcher = policy.get_child_watcher()
-            policy.set_child_watcher(None)
+            with warnings.catch_warnings():
+                warnings.simplefilter('ignore', DeprecationWarning)
+                watcher = policy.get_child_watcher()
+                policy.set_child_watcher(None)
             watcher.attach_loop(None)
             watcher.close()
 
@@ -752,7 +758,9 @@ def test_create_subprocess_fails_with_inactive_watcher(self):
             )
 
             async def execute():
-                asyncio.set_child_watcher(watcher)
+                with warnings.catch_warnings():
+                    warnings.simplefilter('ignore', DeprecationWarning)
+                    asyncio.set_child_watcher(watcher)
 
                 with self.assertRaises(RuntimeError):
                     await subprocess.create_subprocess_exec(
@@ -761,7 +769,9 @@ async def execute():
                 watcher.add_child_handler.assert_not_called()
 
             with asyncio.Runner(loop_factory=asyncio.new_event_loop) as runner:
-                self.assertIsNone(runner.run(execute()))
+                with warnings.catch_warnings():
+                    warnings.simplefilter('ignore', DeprecationWarning)
+                    self.assertIsNone(runner.run(execute()))
             self.assertListEqual(watcher.mock_calls, [
                 mock.call.__enter__(),
                 mock.call.__enter__().is_active(),
@@ -788,15 +798,16 @@ async def main():
                 with self.assertRaises(RuntimeError):
                     asyncio.get_event_loop_policy().get_event_loop()
                 return await asyncio.to_thread(asyncio.run, in_thread())
-
-            asyncio.set_child_watcher(asyncio.PidfdChildWatcher())
+            with self.assertWarns(DeprecationWarning):
+                asyncio.set_child_watcher(asyncio.PidfdChildWatcher())
             try:
                 with asyncio.Runner(loop_factory=asyncio.new_event_loop) as runner:
                     returncode, stdout = runner.run(main())
                 self.assertEqual(returncode, 0)
                 self.assertEqual(stdout, b'some data')
             finally:
-                asyncio.set_child_watcher(None)
+                with self.assertWarns(DeprecationWarning):
+                    asyncio.set_child_watcher(None)
 else:
     # Windows
     class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase):
diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py
index 025da0f20ed4..d806ed497aaa 100644
--- a/Lib/test/test_asyncio/test_unix_events.py
+++ b/Lib/test/test_asyncio/test_unix_events.py
@@ -1709,33 +1709,36 @@ def test_get_default_child_watcher(self):
         self.assertIsNone(policy._watcher)
         unix_events.can_use_pidfd = mock.Mock()
         unix_events.can_use_pidfd.return_value = False
-        watcher = policy.get_child_watcher()
+        with self.assertWarns(DeprecationWarning):
+            watcher = policy.get_child_watcher()
         self.assertIsInstance(watcher, asyncio.ThreadedChildWatcher)
 
         self.assertIs(policy._watcher, watcher)
-
-        self.assertIs(watcher, policy.get_child_watcher())
+        with self.assertWarns(DeprecationWarning):
+            self.assertIs(watcher, policy.get_child_watcher())
 
         policy = self.create_policy()
         self.assertIsNone(policy._watcher)
         unix_events.can_use_pidfd = mock.Mock()
         unix_events.can_use_pidfd.return_value = True
-        watcher = policy.get_child_watcher()
+        with self.assertWarns(DeprecationWarning):
+            watcher = policy.get_child_watcher()
         self.assertIsInstance(watcher, asyncio.PidfdChildWatcher)
 
         self.assertIs(policy._watcher, watcher)
-
-        self.assertIs(watcher, policy.get_child_watcher())
+        with self.assertWarns(DeprecationWarning):
+            self.assertIs(watcher, policy.get_child_watcher())
 
     def test_get_child_watcher_after_set(self):
         policy = self.create_policy()
         with warnings.catch_warnings():
             warnings.simplefilter("ignore", DeprecationWarning)
             watcher = asyncio.FastChildWatcher()
+            policy.set_child_watcher(watcher)
 
-        policy.set_child_watcher(watcher)
         self.assertIs(policy._watcher, watcher)
-        self.assertIs(watcher, policy.get_child_watcher())
+        with self.assertWarns(DeprecationWarning):
+            self.assertIs(watcher, policy.get_child_watcher())
 
     def test_get_child_watcher_thread(self):
 
@@ -1769,7 +1772,7 @@ def test_child_watcher_replace_mainloop_existing(self):
         with warnings.catch_warnings():
             warnings.simplefilter("ignore", DeprecationWarning)
             watcher = asyncio.SafeChildWatcher()
-        policy.set_child_watcher(watcher)
+            policy.set_child_watcher(watcher)
         watcher.attach_loop(loop)
 
         self.assertIs(watcher._loop, loop)
diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py
index 96be5a1c3bcf..5b9c86eb9859 100644
--- a/Lib/test/test_asyncio/utils.py
+++ b/Lib/test/test_asyncio/utils.py
@@ -14,7 +14,7 @@
 import threading
 import unittest
 import weakref
-
+import warnings
 from unittest import mock
 
 from http.server import HTTPServer
@@ -544,7 +544,9 @@ def close_loop(loop):
         policy = support.maybe_get_event_loop_policy()
         if policy is not None:
             try:
-                watcher = policy.get_child_watcher()
+                with warnings.catch_warnings():
+                    warnings.simplefilter('ignore', DeprecationWarning)
+                    watcher = policy.get_child_watcher()
             except NotImplementedError:
                 # watcher is not implemented by EventLoopPolicy, e.g. Windows
                 pass
diff --git a/Misc/NEWS.d/next/Library/2022-10-12-11-20-54.gh-issue-94597.GYJZlb.rst b/Misc/NEWS.d/next/Library/2022-10-12-11-20-54.gh-issue-94597.GYJZlb.rst
new file mode 100644
index 000000000000..5ea1358b7d8a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-10-12-11-20-54.gh-issue-94597.GYJZlb.rst
@@ -0,0 +1 @@
+Deprecated :meth:`asyncio.AbstractEventLoopPolicy.get_child_watcher` and :meth:`asyncio.AbstractEventLoopPolicy.set_child_watcher` methods to be removed in Python 3.14. Patch by Kumar Aditya.



More information about the Python-checkins mailing list