]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-42392: Improve removal of *loop* parameter in asyncio primitives (GH-23499)
authorYurii Karabas <1998uriyyo@gmail.com>
Wed, 25 Nov 2020 11:50:44 +0000 (13:50 +0200)
committerGitHub <noreply@github.com>
Wed, 25 Nov 2020 11:50:44 +0000 (06:50 -0500)
* Update code after merge review from 1st1

* Use a sentinel approach for loop parameter
Remove unnecessary _get_running_loop patching

* Use more clear function name (_verify_parameter_is_marker -> _verify_no_loop)

* Add init method to _LoopBoundMixin to check that loop param wasn't used

Lib/asyncio/locks.py
Lib/asyncio/mixins.py
Lib/asyncio/queues.py
Lib/test/test_asyncio/test_locks.py
Lib/test/test_asyncio/utils.py

index 6f322c258cfe4f12d5c1ea4dfd384115924c8e1b..a7453fb1c77287898bedeaf3828daafe49b0f013 100644 (file)
@@ -19,7 +19,7 @@ class _ContextManagerMixin:
         self.release()
 
 
-class Lock(_ContextManagerMixin, mixins._LoopBoundedMixin):
+class Lock(_ContextManagerMixin, mixins._LoopBoundMixin):
     """Primitive lock objects.
 
     A primitive lock is a synchronization primitive that is not owned
@@ -73,7 +73,8 @@ class Lock(_ContextManagerMixin, mixins._LoopBoundedMixin):
 
     """
 
-    def __init__(self):
+    def __init__(self, *, loop=mixins._marker):
+        super().__init__(loop=loop)
         self._waiters = None
         self._locked = False
 
@@ -153,7 +154,7 @@ class Lock(_ContextManagerMixin, mixins._LoopBoundedMixin):
             fut.set_result(True)
 
 
-class Event(mixins._LoopBoundedMixin):
+class Event(mixins._LoopBoundMixin):
     """Asynchronous equivalent to threading.Event.
 
     Class implementing event objects. An event manages a flag that can be set
@@ -162,7 +163,8 @@ class Event(mixins._LoopBoundedMixin):
     false.
     """
 
-    def __init__(self):
+    def __init__(self, *, loop=mixins._marker):
+        super().__init__(loop=loop)
         self._waiters = collections.deque()
         self._value = False
 
@@ -214,7 +216,7 @@ class Event(mixins._LoopBoundedMixin):
             self._waiters.remove(fut)
 
 
-class Condition(_ContextManagerMixin, mixins._LoopBoundedMixin):
+class Condition(_ContextManagerMixin, mixins._LoopBoundMixin):
     """Asynchronous equivalent to threading.Condition.
 
     This class implements condition variable objects. A condition variable
@@ -224,7 +226,8 @@ class Condition(_ContextManagerMixin, mixins._LoopBoundedMixin):
     A new Lock object is created and used as the underlying lock.
     """
 
-    def __init__(self, lock=None):
+    def __init__(self, lock=None, *, loop=mixins._marker):
+        super().__init__(loop=loop)
         if lock is None:
             lock = Lock()
         elif lock._loop is not self._get_loop():
@@ -328,7 +331,7 @@ class Condition(_ContextManagerMixin, mixins._LoopBoundedMixin):
         self.notify(len(self._waiters))
 
 
-class Semaphore(_ContextManagerMixin, mixins._LoopBoundedMixin):
+class Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin):
     """A Semaphore implementation.
 
     A semaphore manages an internal counter which is decremented by each
@@ -343,7 +346,8 @@ class Semaphore(_ContextManagerMixin, mixins._LoopBoundedMixin):
     ValueError is raised.
     """
 
-    def __init__(self, value=1):
+    def __init__(self, value=1, *, loop=mixins._marker):
+        super().__init__(loop=loop)
         if value < 0:
             raise ValueError("Semaphore initial value must be >= 0")
         self._value = value
@@ -406,9 +410,9 @@ class BoundedSemaphore(Semaphore):
     above the initial value.
     """
 
-    def __init__(self, value=1):
+    def __init__(self, value=1, *, loop=mixins._marker):
         self._bound_value = value
-        super().__init__(value)
+        super().__init__(value, loop=loop)
 
     def release(self):
         if self._value >= self._bound_value:
index dbc4b5faccb019d8aaca1b687562ff1d83af210a..650df05ccc93ea652d22637b740b56d8a4bb60a3 100644 (file)
@@ -5,10 +5,20 @@ from . import events
 
 _global_lock = threading.Lock()
 
+# Used as a sentinel for loop parameter
+_marker = object()
 
-class _LoopBoundedMixin:
+
+class _LoopBoundMixin:
     _loop = None
 
+    def __init__(self, *, loop=_marker):
+        if loop is not _marker:
+            raise TypeError(
+                f'As of 3.10, the *loop* parameter was removed from '
+                f'{type(self).__name__}() since it is no longer necessary'
+            )
+
     def _get_loop(self):
         loop = events._get_running_loop()
 
@@ -17,5 +27,5 @@ class _LoopBoundedMixin:
                 if self._loop is None:
                     self._loop = loop
         if loop is not self._loop:
-            raise RuntimeError(f'{type(self).__name__} have already bounded to another loop')
+            raise RuntimeError(f'{self!r} is bound to a different event loop')
         return loop
index 78ae9e99ccf0e9b6a1010c2c26f7f1d7ea633ca3..a87ec8b2158767a90fbaf2dcb5bae1f45eb46829 100644 (file)
@@ -17,7 +17,7 @@ class QueueFull(Exception):
     pass
 
 
-class Queue(mixins._LoopBoundedMixin):
+class Queue(mixins._LoopBoundMixin):
     """A queue, useful for coordinating producer and consumer coroutines.
 
     If maxsize is less than or equal to zero, the queue size is infinite. If it
@@ -29,7 +29,8 @@ class Queue(mixins._LoopBoundedMixin):
     interrupted between calling qsize() and doing an operation on the Queue.
     """
 
-    def __init__(self, maxsize=0):
+    def __init__(self, maxsize=0, *, loop=mixins._marker):
+        super().__init__(loop=loop)
         self._maxsize = maxsize
 
         # Futures.
index 6c34ef60e30b5168e27da7530b6ed8622ae3614d..6194cd06176dac3341500b2b973edcbe6493e042 100644 (file)
@@ -51,6 +51,23 @@ class LockTests(test_utils.TestCase):
 
         self.assertFalse(lock.locked())
 
+    def test_lock_doesnt_accept_loop_parameter(self):
+        primitives_cls = [
+            asyncio.Lock,
+            asyncio.Condition,
+            asyncio.Event,
+            asyncio.Semaphore,
+            asyncio.BoundedSemaphore,
+        ]
+
+        for cls in primitives_cls:
+            with self.assertRaisesRegex(
+                TypeError,
+                rf'As of 3.10, the \*loop\* parameter was removed from '
+                rf'{cls.__name__}\(\) since it is no longer necessary'
+            ):
+                cls(loop=self.loop)
+
     def test_lock_by_with_statement(self):
         loop = asyncio.new_event_loop()  # don't use TestLoop quirks
         self.set_event_loop(loop)
index aba90c970a8e249dde8dcb519864d9b774af3415..67180f7eb3955aa5409b46fb9fc9fc007a545136 100644 (file)
@@ -541,31 +541,10 @@ class TestCase(unittest.TestCase):
         self.set_event_loop(loop)
         return loop
 
-    def unpatch_get_running_loop(self):
-        events._get_running_loop = self._get_running_loop
-
     def setUp(self):
-        self._get_running_loop = events._get_running_loop
-
-        def _get_running_loop():
-            frame = sys._getframe(1)
-
-            if frame.f_globals['__name__'] == 'asyncio.mixins':
-                # When we called from LoopBoundedMixin we should
-                # fallback to default implementation of get_running_loop
-                try:
-                    return events.get_running_loop()
-                except RuntimeError:
-                    return None
-
-            return None
-
-        events._get_running_loop = _get_running_loop
         self._thread_cleanup = threading_helper.threading_setup()
 
     def tearDown(self):
-        self.unpatch_get_running_loop()
-
         events.set_event_loop(None)
 
         # Detect CPython bug #23353: ensure that yield/yield-from is not used