From 2b17e33fe42e0517a62067cc6e702ea5aac0d3da Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Mon, 9 Dec 2019 17:29:16 +0200 Subject: [PATCH 1/2] Drop old-style context managers in asyncio.locks --- Doc/whatsnew/3.9.rst | 5 + Lib/asyncio/locks.py | 83 --------- Lib/test/test_asyncio/test_locks.py | 165 ++++-------------- Lib/test/test_asyncio/test_pep492.py | 13 +- .../2019-12-09-17-24-29.bpo-34793.D82Dyu.rst | 3 + 5 files changed, 48 insertions(+), 221 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-12-09-17-24-29.bpo-34793.D82Dyu.rst diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 7cf49bfbb93f94..e1d733a5ed6be6 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -337,6 +337,11 @@ Removed of :pep:`442`. Patch by Joannah Nanjekye. (Contributed by Joannah Nanjekye in :issue:`15088`) +* ``with (await asyncio.lock):`` and ``with (yield from asyncio.lock):`` statements are + not longer supported, use ``async with lock`` instead. The same is correct for + ``asyncio.Condition`` and ``asyncio.Semaphore``. + (Contributed by Andrew Svetlov in :issue:`34793`.) + Porting to Python 3.9 ===================== diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index d94daeb5a173f5..f1ce7324785ba9 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -3,96 +3,13 @@ __all__ = ('Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore') import collections -import types import warnings from . import events -from . import futures from . import exceptions -from .import coroutines - - -class _ContextManager: - """Context manager. - - This enables the following idiom for acquiring and releasing a - lock around a block: - - with (yield from lock): - - - while failing loudly when accidentally using: - - with lock: - - - Deprecated, use 'async with' statement: - async with lock: - - """ - - def __init__(self, lock): - self._lock = lock - - def __enter__(self): - # We have no use for the "as ..." clause in the with - # statement for locks. - return None - - def __exit__(self, *args): - try: - self._lock.release() - finally: - self._lock = None # Crudely prevent reuse. class _ContextManagerMixin: - def __enter__(self): - raise RuntimeError( - '"yield from" should be used as context manager expression') - - def __exit__(self, *args): - # This must exist because __enter__ exists, even though that - # always raises; that's how the with-statement works. - pass - - @types.coroutine - def __iter__(self): - # This is not a coroutine. It is meant to enable the idiom: - # - # with (yield from lock): - # - # - # as an alternative to: - # - # yield from lock.acquire() - # try: - # - # finally: - # lock.release() - # Deprecated, use 'async with' statement: - # async with lock: - # - warnings.warn("'with (yield from lock)' is deprecated " - "use 'async with lock' instead", - DeprecationWarning, stacklevel=2) - yield from self.acquire() - return _ContextManager(self) - - # The flag is needed for legacy asyncio.iscoroutine() - __iter__._is_coroutine = coroutines._is_coroutine - - async def __acquire_ctx(self): - await self.acquire() - return _ContextManager(self) - - def __await__(self): - warnings.warn("'with await lock' is deprecated " - "use 'async with lock' instead", - DeprecationWarning, stacklevel=2) - # To make "with await lock" work. - return self.__acquire_ctx().__await__() - async def __aenter__(self): await self.acquire() # We have no use for the "as ..." clause in the with diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py index 9468e740b3c1de..8c93fae2b51c6c 100644 --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -47,13 +47,7 @@ def test_repr(self): self.assertTrue(repr(lock).endswith('[unlocked]>')) self.assertTrue(RGX_REPR.match(repr(lock))) - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def acquire_lock(): - with self.assertWarns(DeprecationWarning): - yield from lock - - self.loop.run_until_complete(acquire_lock()) + self.loop.run_until_complete(lock.acquire()) self.assertTrue(repr(lock).endswith('[locked]>')) self.assertTrue(RGX_REPR.match(repr(lock))) @@ -61,18 +55,16 @@ def test_lock(self): with self.assertWarns(DeprecationWarning): lock = asyncio.Lock(loop=self.loop) - @asyncio.coroutine def acquire_lock(): - with self.assertWarns(DeprecationWarning): - return (yield from lock) - - res = self.loop.run_until_complete(acquire_lock()) + return (yield from lock) - self.assertTrue(res) - self.assertTrue(lock.locked()) + with self.assertRaisesRegex( + TypeError, + "object is not iterable" + ): + self.loop.run_until_complete(acquire_lock()) - lock.release() self.assertFalse(lock.locked()) def test_lock_by_with_statement(self): @@ -90,13 +82,13 @@ def test_lock_by_with_statement(self): def test(lock): yield from asyncio.sleep(0.01) self.assertFalse(lock.locked()) - with self.assertWarns(DeprecationWarning): - with (yield from lock) as _lock: - self.assertIs(_lock, None) - self.assertTrue(lock.locked()) - yield from asyncio.sleep(0.01) - self.assertTrue(lock.locked()) - self.assertFalse(lock.locked()) + with self.assertRaisesRegex( + TypeError, + "object is not iterable" + ): + with (yield from lock): + pass + self.assertFalse(lock.locked()) for primitive in primitives: loop.run_until_complete(test(primitive)) @@ -302,52 +294,16 @@ def test_release_no_waiters(self): self.assertFalse(lock.locked()) def test_context_manager(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + async def f(): + lock = asyncio.Lock() + self.assertFalse(lock.locked()) - @asyncio.coroutine - def acquire_lock(): - with self.assertWarns(DeprecationWarning): - return (yield from lock) + async with lock: + self.assertTrue(lock.locked()) - with self.loop.run_until_complete(acquire_lock()): - self.assertTrue(lock.locked()) + self.assertFalse(lock.locked()) - self.assertFalse(lock.locked()) - - def test_context_manager_cant_reuse(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) - - @asyncio.coroutine - def acquire_lock(): - with self.assertWarns(DeprecationWarning): - return (yield from lock) - - # This spells "yield from lock" outside a generator. - cm = self.loop.run_until_complete(acquire_lock()) - with cm: - self.assertTrue(lock.locked()) - - self.assertFalse(lock.locked()) - - with self.assertRaises(AttributeError): - with cm: - pass - - def test_context_manager_no_yield(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) - - try: - with lock: - self.fail('RuntimeError is not raised in with expression') - except RuntimeError as err: - self.assertEqual( - str(err), - '"yield from" should be used as context manager expression') - - self.assertFalse(lock.locked()) + self.loop.run_until_complete(f()) class EventTests(test_utils.TestCase): @@ -809,33 +765,14 @@ def test_repr(self): self.assertTrue(RGX_REPR.match(repr(cond))) def test_context_manager(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def acquire_cond(): - with self.assertWarns(DeprecationWarning): - return (yield from cond) - - with self.loop.run_until_complete(acquire_cond()): - self.assertTrue(cond.locked()) - - self.assertFalse(cond.locked()) - - def test_context_manager_no_yield(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) - - try: - with cond: - self.fail('RuntimeError is not raised in with expression') - except RuntimeError as err: - self.assertEqual( - str(err), - '"yield from" should be used as context manager expression') + async def f(): + cond = asyncio.Condition() + self.assertFalse(cond.locked()) + async with cond: + self.assertTrue(cond.locked()) + self.assertFalse(cond.locked()) - self.assertFalse(cond.locked()) + self.loop.run_until_complete(f()) def test_explicit_lock(self): with self.assertWarns(DeprecationWarning): @@ -920,16 +857,14 @@ def test_semaphore(self): with self.assertWarns(DeprecationWarning): @asyncio.coroutine def acquire_lock(): - with self.assertWarns(DeprecationWarning): - return (yield from sem) + return (yield from sem) - res = self.loop.run_until_complete(acquire_lock()) - - self.assertTrue(res) - self.assertTrue(sem.locked()) - self.assertEqual(0, sem._value) + with self.assertRaisesRegex( + TypeError, + "'Semaphore' object is not iterable", + ): + self.loop.run_until_complete(acquire_lock()) - sem.release() self.assertFalse(sem.locked()) self.assertEqual(1, sem._value) @@ -1064,38 +999,6 @@ def test_release_no_waiters(self): sem.release() self.assertFalse(sem.locked()) - def test_context_manager(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(2, loop=self.loop) - - @asyncio.coroutine - def acquire_lock(): - with self.assertWarns(DeprecationWarning): - return (yield from sem) - - with self.loop.run_until_complete(acquire_lock()): - self.assertFalse(sem.locked()) - self.assertEqual(1, sem._value) - - with self.loop.run_until_complete(acquire_lock()): - self.assertTrue(sem.locked()) - - self.assertEqual(2, sem._value) - - def test_context_manager_no_yield(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(2, loop=self.loop) - - try: - with sem: - self.fail('RuntimeError is not raised in with expression') - except RuntimeError as err: - self.assertEqual( - str(err), - '"yield from" should be used as context manager expression') - - self.assertEqual(2, sem._value) - if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_asyncio/test_pep492.py b/Lib/test/test_asyncio/test_pep492.py index a1f27dd5721c83..c5e3a5c1483573 100644 --- a/Lib/test/test_asyncio/test_pep492.py +++ b/Lib/test/test_asyncio/test_pep492.py @@ -77,13 +77,12 @@ def test_context_manager_with_await(self): async def test(lock): await asyncio.sleep(0.01) self.assertFalse(lock.locked()) - with self.assertWarns(DeprecationWarning): - with await lock as _lock: - self.assertIs(_lock, None) - self.assertTrue(lock.locked()) - await asyncio.sleep(0.01) - self.assertTrue(lock.locked()) - self.assertFalse(lock.locked()) + with self.assertRaisesRegex( + TypeError, + "can't be used in 'await' expression" + ): + with await lock: + pass for primitive in primitives: self.loop.run_until_complete(test(primitive)) diff --git a/Misc/NEWS.d/next/Library/2019-12-09-17-24-29.bpo-34793.D82Dyu.rst b/Misc/NEWS.d/next/Library/2019-12-09-17-24-29.bpo-34793.D82Dyu.rst new file mode 100644 index 00000000000000..2089285ecdb7ff --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-12-09-17-24-29.bpo-34793.D82Dyu.rst @@ -0,0 +1,3 @@ +Remove support for ``with (await asyncio.lock):`` and ``with (yield from +asyncio.lock):``. The same is correct for ``asyncio.Condition`` and +``asyncio.Semaphore``. From 36d7e3f135e956fdf83705de066e7acdf1e997e1 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 10 Dec 2019 14:02:09 +0200 Subject: [PATCH 2/2] Update docs --- Doc/library/asyncio-sync.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/asyncio-sync.rst b/Doc/library/asyncio-sync.rst index f080b03bc7c51c..84a52cb2d57571 100644 --- a/Doc/library/asyncio-sync.rst +++ b/Doc/library/asyncio-sync.rst @@ -347,8 +347,8 @@ BoundedSemaphore --------- -.. deprecated:: 3.7 +.. versionchanged:: 3.9 Acquiring a lock using ``await lock`` or ``yield from lock`` and/or :keyword:`with` statement (``with await lock``, ``with (yield from - lock)``) is deprecated. Use ``async with lock`` instead. + lock)``) was removed. Use ``async with lock`` instead.