bpo-34793: Drop old-style context managers in asyncio.locks (GH-17533)

This commit is contained in:
Andrew Svetlov 2020-02-01 13:12:52 +02:00 committed by GitHub
parent abb9a448de
commit 90d9ba6ef1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 223 deletions

View file

@ -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.

View file

@ -470,6 +470,11 @@ Removed
:exc:`DeprecationWarning` since Python 3.8.
(Contributed by Inada Naoki in :issue:`39377`)
* ``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
=====================

View file

@ -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):
<block>
while failing loudly when accidentally using:
with lock:
<block>
Deprecated, use 'async with' statement:
async with lock:
<block>
"""
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):
# <block>
#
# as an alternative to:
#
# yield from lock.acquire()
# try:
# <block>
# finally:
# lock.release()
# Deprecated, use 'async with' statement:
# async with lock:
# <block>
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

View file

@ -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)
return (yield from lock)
res = self.loop.run_until_complete(acquire_lock())
with self.assertRaisesRegex(
TypeError,
"object is not iterable"
):
self.loop.run_until_complete(acquire_lock())
self.assertTrue(res)
self.assertTrue(lock.locked())
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)
async def f():
cond = asyncio.Condition()
self.assertFalse(cond.locked())
async with cond:
self.assertTrue(cond.locked())
self.assertFalse(cond.locked())
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')
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())
with self.assertRaisesRegex(
TypeError,
"'Semaphore' object is not iterable",
):
self.loop.run_until_complete(acquire_lock())
self.assertTrue(res)
self.assertTrue(sem.locked())
self.assertEqual(0, sem._value)
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()

View file

@ -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))

View file

@ -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``.