diff --git a/Doc/library/asyncio-policy.rst b/Doc/library/asyncio-policy.rst index e8e470c1b343fd..2d05c3a9f7f157 100644 --- a/Doc/library/asyncio-policy.rst +++ b/Doc/library/asyncio-policy.rst @@ -40,6 +40,10 @@ for the current process: Return the current process-wide policy. + .. deprecated:: next + The :func:`get_event_loop_policy` function is deprecated and + will be removed in Python 3.16. + .. function:: set_event_loop_policy(policy) Set the current process-wide policy to *policy*. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index d13cd2d5173a04..342456cbc397f3 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -780,6 +780,96 @@ asyncio It now raises a :exc:`RuntimeError` if there is no current event loop. (Contributed by Kumar Aditya in :gh:`126353`.) + There's a few patterns that use :func:`asyncio.get_event_loop`, most + of them can be replaced with :func:`asyncio.run`. + + If you're running an async function, simply use :func:`asyncio.run`. + + Before:: + + async def main(): + ... + + + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(main()) + finally: + loop.close() + + After:: + + async def main(): + ... + + asyncio.run(main()) + + If you need to start something, e.g. a server listening on a socket + and then run forever, use :func:`asyncio.run` and an + :class:`asyncio.Event`. + + Before:: + + def start_server(loop): + ... + + loop = asyncio.get_event_loop() + try: + start_server(loop) + loop.run_forever() + finally: + loop.close() + + After:: + + def start_server(loop): + ... + + async def main(): + start_server(asyncio.get_running_loop()) + await asyncio.Event().wait() + + asyncio.run(main()) + + If you need to run something in an event loop, then run some blocking + code around it, use :class:`asyncio.Runner`. + + Before:: + + async def operation_one(): + ... + + def blocking_code(): + ... + + async def operation_two(): + ... + + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(operation_one()) + blocking_code() + loop.run_until_complete(operation_two()) + finally: + loop.close() + + After:: + + async def operation_one(): + ... + + def blocking_code(): + ... + + async def operation_two(): + ... + + with asyncio.Runner() as runner: + runner.run(operation_one()) + blocking_code() + runner.run(operation_two()) + + collections.abc --------------- diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 0926cfe2323478..1449245edc7c7e 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -8,6 +8,7 @@ 'AbstractEventLoopPolicy', 'AbstractEventLoop', 'AbstractServer', 'Handle', 'TimerHandle', + '_get_event_loop_policy', 'get_event_loop_policy', '_set_event_loop_policy', 'set_event_loop_policy', @@ -761,12 +762,15 @@ def _init_event_loop_policy(): _event_loop_policy = DefaultEventLoopPolicy() -def get_event_loop_policy(): +def _get_event_loop_policy(): """Get the current event loop policy.""" if _event_loop_policy is None: _init_event_loop_policy() return _event_loop_policy +def get_event_loop_policy(): + warnings._deprecated('asyncio.get_event_loop_policy', remove=(3, 16)) + return _get_event_loop_policy() def _set_event_loop_policy(policy): """Set the current event loop policy. @@ -778,7 +782,7 @@ def _set_event_loop_policy(policy): _event_loop_policy = policy def set_event_loop_policy(policy): - warnings._deprecated('set_event_loop_policy', remove=(3,16)) + warnings._deprecated('asyncio.set_event_loop_policy', remove=(3,16)) _set_event_loop_policy(policy) def get_event_loop(): @@ -794,17 +798,17 @@ def get_event_loop(): current_loop = _get_running_loop() if current_loop is not None: return current_loop - return get_event_loop_policy().get_event_loop() + return _get_event_loop_policy().get_event_loop() def set_event_loop(loop): """Equivalent to calling get_event_loop_policy().set_event_loop(loop).""" - get_event_loop_policy().set_event_loop(loop) + _get_event_loop_policy().set_event_loop(loop) def new_event_loop(): """Equivalent to calling get_event_loop_policy().new_event_loop().""" - return get_event_loop_policy().new_event_loop() + return _get_event_loop_policy().new_event_loop() # Alias pure-Python implementations for testing purposes. diff --git a/Lib/tempfile.py b/Lib/tempfile.py index b5a15f7b72c872..0eb9ddeb6ac377 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -437,11 +437,19 @@ class _TemporaryFileCloser: cleanup_called = False close_called = False - def __init__(self, file, name, delete=True, delete_on_close=True): + def __init__( + self, + file, + name, + delete=True, + delete_on_close=True, + warn_message="Implicitly cleaning up unknown file", + ): self.file = file self.name = name self.delete = delete self.delete_on_close = delete_on_close + self.warn_message = warn_message def cleanup(self, windows=(_os.name == 'nt'), unlink=_os.unlink): if not self.cleanup_called: @@ -469,7 +477,10 @@ def close(self): self.cleanup() def __del__(self): + close_called = self.close_called self.cleanup() + if not close_called: + _warnings.warn(self.warn_message, ResourceWarning) class _TemporaryFileWrapper: @@ -483,8 +494,17 @@ class _TemporaryFileWrapper: def __init__(self, file, name, delete=True, delete_on_close=True): self.file = file self.name = name - self._closer = _TemporaryFileCloser(file, name, delete, - delete_on_close) + self._closer = _TemporaryFileCloser( + file, + name, + delete, + delete_on_close, + warn_message=f"Implicitly cleaning up {self!r}", + ) + + def __repr__(self): + file = self.__dict__['file'] + return f"<{type(self).__name__} {file=}>" def __getattr__(self, name): # Attribute lookups are delegated to the underlying file diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index 50df1b6ff9e09f..d43f66c13d2f96 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -2397,7 +2397,7 @@ def test_handle_repr_debug(self): self.assertRegex(repr(h), regex) def test_handle_source_traceback(self): - loop = asyncio.get_event_loop_policy().new_event_loop() + loop = asyncio.new_event_loop() loop.set_debug(True) self.set_event_loop(loop) @@ -2759,24 +2759,31 @@ def test_set_event_loop(self): old_loop.close() def test_get_event_loop_policy(self): - policy = asyncio.get_event_loop_policy() - self.assertIsInstance(policy, asyncio.AbstractEventLoopPolicy) - self.assertIs(policy, asyncio.get_event_loop_policy()) + with self.assertWarnsRegex( + DeprecationWarning, "'asyncio.get_event_loop_policy' is deprecated"): + policy = asyncio.get_event_loop_policy() + self.assertIsInstance(policy, asyncio.AbstractEventLoopPolicy) + self.assertIs(policy, asyncio.get_event_loop_policy()) def test_set_event_loop_policy(self): with self.assertWarnsRegex( - DeprecationWarning, "'set_event_loop_policy' is deprecated"): + DeprecationWarning, "'asyncio.set_event_loop_policy' is deprecated"): self.assertRaises( TypeError, asyncio.set_event_loop_policy, object()) - old_policy = asyncio.get_event_loop_policy() + with self.assertWarnsRegex( + DeprecationWarning, "'asyncio.get_event_loop_policy' is deprecated"): + old_policy = asyncio.get_event_loop_policy() policy = asyncio.DefaultEventLoopPolicy() with self.assertWarnsRegex( - DeprecationWarning, "'set_event_loop_policy' is deprecated"): + DeprecationWarning, "'asyncio.set_event_loop_policy' is deprecated"): asyncio.set_event_loop_policy(policy) - self.assertIs(policy, asyncio.get_event_loop_policy()) - self.assertIsNot(policy, old_policy) + + with self.assertWarnsRegex( + DeprecationWarning, "'asyncio.get_event_loop_policy' is deprecated"): + self.assertIs(policy, asyncio.get_event_loop_policy()) + self.assertIsNot(policy, old_policy) class GetEventLoopTestsMixin: @@ -2859,7 +2866,7 @@ class Policy(asyncio.DefaultEventLoopPolicy): def get_event_loop(self): raise TestError - old_policy = asyncio.get_event_loop_policy() + old_policy = asyncio._get_event_loop_policy() try: asyncio._set_event_loop_policy(Policy()) loop = asyncio.new_event_loop() @@ -2899,7 +2906,7 @@ async def func(): self.assertIs(asyncio._get_running_loop(), None) def test_get_event_loop_returns_running_loop2(self): - old_policy = asyncio.get_event_loop_policy() + old_policy = asyncio._get_event_loop_policy() try: asyncio._set_event_loop_policy(asyncio.DefaultEventLoopPolicy()) loop = asyncio.new_event_loop() diff --git a/Lib/test/test_asyncio/test_runners.py b/Lib/test/test_asyncio/test_runners.py index f9afccc937f1de..e1f82f7f7bec0c 100644 --- a/Lib/test/test_asyncio/test_runners.py +++ b/Lib/test/test_asyncio/test_runners.py @@ -64,7 +64,7 @@ def setUp(self): asyncio._set_event_loop_policy(policy) def tearDown(self): - policy = asyncio.get_event_loop_policy() + policy = asyncio._get_event_loop_policy() if policy.loop is not None: self.assertTrue(policy.loop.is_closed()) self.assertTrue(policy.loop.shutdown_ag_run) @@ -208,7 +208,7 @@ async def main(): await asyncio.sleep(0) return 42 - policy = asyncio.get_event_loop_policy() + policy = asyncio._get_event_loop_policy() policy.set_event_loop = mock.Mock() asyncio.run(main()) self.assertTrue(policy.set_event_loop.called) @@ -495,7 +495,7 @@ def test_set_event_loop_called_once(self): async def coro(): pass - policy = asyncio.get_event_loop_policy() + policy = asyncio._get_event_loop_policy() policy.set_event_loop = mock.Mock() runner = asyncio.Runner() runner.run(coro()) diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py index 467e964b26c824..57decaf2d277fb 100644 --- a/Lib/test/test_asyncio/test_subprocess.py +++ b/Lib/test/test_asyncio/test_subprocess.py @@ -886,8 +886,7 @@ class SubprocessWatcherMixin(SubprocessMixin): def setUp(self): super().setUp() - policy = asyncio.get_event_loop_policy() - self.loop = policy.new_event_loop() + self.loop = asyncio.new_event_loop() self.set_event_loop(self.loop) def test_watcher_implementation(self): diff --git a/Lib/test/test_asyncio/test_windows_events.py b/Lib/test/test_asyncio/test_windows_events.py index 26ca5f905f752f..28b05d24dc25a1 100644 --- a/Lib/test/test_asyncio/test_windows_events.py +++ b/Lib/test/test_asyncio/test_windows_events.py @@ -332,7 +332,7 @@ async def main(): asyncio.get_running_loop(), asyncio.SelectorEventLoop) - old_policy = asyncio.get_event_loop_policy() + old_policy = asyncio._get_event_loop_policy() try: asyncio._set_event_loop_policy( asyncio.WindowsSelectorEventLoopPolicy()) @@ -346,7 +346,7 @@ async def main(): asyncio.get_running_loop(), asyncio.ProactorEventLoop) - old_policy = asyncio.get_event_loop_policy() + old_policy = asyncio._get_event_loop_policy() try: asyncio._set_event_loop_policy( asyncio.WindowsProactorEventLoopPolicy()) diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index d8ee5757b12c15..88dcdadd5e027d 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -496,7 +496,7 @@ class TestAsyncExitStack(TestBaseExitStack, unittest.IsolatedAsyncioTestCase): class SyncAsyncExitStack(AsyncExitStack): @staticmethod def run_coroutine(coro): - loop = asyncio.get_event_loop_policy().get_event_loop() + loop = asyncio.new_event_loop() t = loop.create_task(coro) t.add_done_callback(lambda f: loop.stop()) loop.run_forever() diff --git a/Lib/test/test_ctypes/test_dlerror.py b/Lib/test/test_ctypes/test_dlerror.py index c3c34d43481d36..6bf492399cbf95 100644 --- a/Lib/test/test_ctypes/test_dlerror.py +++ b/Lib/test/test_ctypes/test_dlerror.py @@ -133,24 +133,20 @@ def configure_locales(func): @classmethod def setUpClass(cls): cls.libc_filename = find_library("c") + if cls.libc_filename is None: + raise unittest.SkipTest('cannot find libc') @configure_locales def test_localized_error_from_dll(self): dll = CDLL(self.libc_filename) - with self.assertRaises(AttributeError) as cm: + with self.assertRaises(AttributeError): dll.this_name_does_not_exist - if sys.platform.startswith('linux'): - # On macOS, the filename is not reported by dlerror(). - self.assertIn(self.libc_filename, str(cm.exception)) @configure_locales def test_localized_error_in_dll(self): dll = CDLL(self.libc_filename) - with self.assertRaises(ValueError) as cm: + with self.assertRaises(ValueError): c_int.in_dll(dll, 'this_name_does_not_exist') - if sys.platform.startswith('linux'): - # On macOS, the filename is not reported by dlerror(). - self.assertIn(self.libc_filename, str(cm.exception)) @unittest.skipUnless(hasattr(_ctypes, 'dlopen'), 'test requires _ctypes.dlopen()') @@ -172,11 +168,8 @@ def test_localized_error_dlopen(self): @configure_locales def test_localized_error_dlsym(self): dll = _ctypes.dlopen(self.libc_filename) - with self.assertRaises(OSError) as cm: + with self.assertRaises(OSError): _ctypes.dlsym(dll, 'this_name_does_not_exist') - if sys.platform.startswith('linux'): - # On macOS, the filename is not reported by dlerror(). - self.assertIn(self.libc_filename, str(cm.exception)) if __name__ == "__main__": diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index 57e9bd20c77ee1..7adc021d298254 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -1112,11 +1112,14 @@ def my_func(dir): # Testing extreme case, where the file is not explicitly closed # f.close() return tmp_name - # Make sure that the garbage collector has finalized the file object. - gc.collect() dir = tempfile.mkdtemp() try: - tmp_name = my_func(dir) + with self.assertWarnsRegex( + expected_warning=ResourceWarning, + expected_regex=r"Implicitly cleaning up <_TemporaryFileWrapper file=.*>", + ): + tmp_name = my_func(dir) + support.gc_collect() self.assertFalse(os.path.exists(tmp_name), f"NamedTemporaryFile {tmp_name!r} " f"exists after finalizer ") diff --git a/Lib/test/test_unittest/test_async_case.py b/Lib/test/test_unittest/test_async_case.py index 664ca5efe57f84..993e6bf013cfbf 100644 --- a/Lib/test/test_unittest/test_async_case.py +++ b/Lib/test/test_unittest/test_async_case.py @@ -480,7 +480,7 @@ def test_setup_get_event_loop(self): class TestCase1(unittest.IsolatedAsyncioTestCase): def setUp(self): - asyncio.get_event_loop_policy().get_event_loop() + asyncio._get_event_loop_policy().get_event_loop() async def test_demo1(self): pass diff --git a/Misc/NEWS.d/next/Library/2024-11-11-07-56-03.gh-issue-126639.AmVSt-.rst b/Misc/NEWS.d/next/Library/2024-11-11-07-56-03.gh-issue-126639.AmVSt-.rst new file mode 100644 index 00000000000000..0b75e5858de731 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-11-07-56-03.gh-issue-126639.AmVSt-.rst @@ -0,0 +1 @@ +:class:`tempfile.NamedTemporaryFile` will now issue a :exc:`ResourceWarning` when it is finalized by the garbage collector without being explicitly closed. diff --git a/Misc/NEWS.d/next/Library/2024-12-17-12-41-07.gh-issue-126742.l07qvT.rst b/Misc/NEWS.d/next/Library/2024-12-17-12-41-07.gh-issue-126742.l07qvT.rst new file mode 100644 index 00000000000000..70f7cc129f66e3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-12-17-12-41-07.gh-issue-126742.l07qvT.rst @@ -0,0 +1,3 @@ +Fix support of localized error messages reported by :manpage:`dlerror(3)` and +:manpage:`gdbm_strerror ` in :mod:`ctypes` and :mod:`dbm.gnu` +functions respectively. Patch by Bénédikt Tran. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 5c0f256ca19c1e..27c16364457336 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3778,7 +3778,7 @@ module_init(asyncio_state *state) } WITH_MOD("asyncio.events") - GET_MOD_ATTR(state->asyncio_get_event_loop_policy, "get_event_loop_policy") + GET_MOD_ATTR(state->asyncio_get_event_loop_policy, "_get_event_loop_policy") WITH_MOD("asyncio.base_futures") GET_MOD_ATTR(state->asyncio_future_repr_func, "_future_repr")