diff --git a/src/easynetwork/lowlevel/api_async/backend/_asyncio/backend.py b/src/easynetwork/lowlevel/api_async/backend/_asyncio/backend.py index a48b2356..68fa4d51 100644 --- a/src/easynetwork/lowlevel/api_async/backend/_asyncio/backend.py +++ b/src/easynetwork/lowlevel/api_async/backend/_asyncio/backend.py @@ -281,10 +281,13 @@ def create_event(self) -> IEvent: return self.__asyncio.Event() def create_condition_var(self, lock: ILock | None = None) -> ICondition: - if lock is not None: - assert isinstance(lock, self.__asyncio.Lock) # nosec assert_used - - return self.__asyncio.Condition(lock) + match lock: + case None: + return self.__asyncio.Condition() + case self.__asyncio.Lock(): + return self.__asyncio.Condition(lock) + case _: + raise TypeError("lock must be a asyncio.Lock") async def run_in_thread( self, diff --git a/src/easynetwork/lowlevel/api_async/backend/_trio/backend.py b/src/easynetwork/lowlevel/api_async/backend/_trio/backend.py index 166a946d..bfa4fa95 100644 --- a/src/easynetwork/lowlevel/api_async/backend/_trio/backend.py +++ b/src/easynetwork/lowlevel/api_async/backend/_trio/backend.py @@ -260,10 +260,13 @@ def create_event(self) -> IEvent: return self.__trio.Event() def create_condition_var(self, lock: ILock | None = None) -> ICondition: - if lock is not None: - assert isinstance(lock, self.__trio.Lock) # nosec assert_used - - return self.__trio.Condition(lock) + match lock: + case None: + return self.__trio.Condition() + case self.__trio.Lock(): + return self.__trio.Condition(lock) + case _: + raise TypeError("lock must be a trio.Lock") async def run_in_thread( self, diff --git a/src/easynetwork/lowlevel/api_async/backend/abc.py b/src/easynetwork/lowlevel/api_async/backend/abc.py index 79974cb6..3c5eadef 100644 --- a/src/easynetwork/lowlevel/api_async/backend/abc.py +++ b/src/easynetwork/lowlevel/api_async/backend/abc.py @@ -1175,8 +1175,23 @@ def create_condition_var(self, lock: ILock | None = ...) -> ICondition: """ Creates a Condition variable object for inter-task synchronization. + If `lock` is given and not :data:`None`, it should be a lock created by :meth:`create_lock`. + While it is guaranteed to work with a lock from :meth:`create_lock`, it can be any other implementation + (such as the lock returned by :meth:`create_fair_lock`), but it can also refuse other implementations. + + Generic code should expect the function to fail:: + + try: + cond = backend.create_condition_var(backend.create_fair_lock()) + except TypeError: + # Cannot use a fair lock. Use the default implementation instead. + cond = backend.create_condition_var() + Parameters: - lock: If given, it must be a lock created by :meth:`create_lock`. Otherwise a new Lock object is created automatically. + lock: The lock instance to use under the hood. + + Raises: + TypeError: `lock` type is not supported. Returns: A new Condition. diff --git a/tests/unit_test/test_async/test_asyncio_backend/test_backend.py b/tests/unit_test/test_async/test_asyncio_backend/test_backend.py index 1ea45716..280eb66e 100644 --- a/tests/unit_test/test_async/test_asyncio_backend/test_backend.py +++ b/tests/unit_test/test_async/test_asyncio_backend/test_backend.py @@ -11,6 +11,7 @@ from easynetwork.lowlevel.api_async.backend._asyncio.dns_resolver import AsyncIODNSResolver from easynetwork.lowlevel.api_async.backend._asyncio.stream.listener import AbstractAcceptedSocketFactory, AcceptedSocketFactory from easynetwork.lowlevel.api_async.backend._asyncio.stream.socket import StreamReaderBufferedProtocol +from easynetwork.lowlevel.api_async.backend.abc import ILock import pytest @@ -805,9 +806,28 @@ async def test____create_condition_var____use_asyncio_Condition_class( condition = backend.create_condition_var(mock_lock) # Assert - mock_Condition.assert_called_once_with(mock_lock) + if mock_lock is None: + mock_Condition.assert_called_once_with() + else: + mock_Condition.assert_called_once_with(mock_lock) assert condition is mocker.sentinel.condition_var + async def test____create_condition_var____invalid_Lock_type( + self, + backend: AsyncIOBackend, + mocker: MockerFixture, + ) -> None: + # Arrange + mock_lock: MagicMock = mocker.NonCallableMagicMock(spec=ILock) + mock_Condition = mocker.patch("asyncio.Condition", return_value=mocker.sentinel.condition_var) + + # Act + with pytest.raises(TypeError, match=r"^lock must be a asyncio\.Lock$"): + _ = backend.create_condition_var(mock_lock) + + # Assert + mock_Condition.assert_not_called() + @pytest.mark.parametrize("abandon_on_cancel", [False, True], ids=lambda p: f"abandon_on_cancel=={p}") async def test____run_in_thread____use_loop_run_in_executor( self, diff --git a/tests/unit_test/test_async/test_trio_backend/test_backend.py b/tests/unit_test/test_async/test_trio_backend/test_backend.py index 959b734d..cb82b952 100644 --- a/tests/unit_test/test_async/test_trio_backend/test_backend.py +++ b/tests/unit_test/test_async/test_trio_backend/test_backend.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, Final from easynetwork.lowlevel.api_async.backend._trio.backend import TrioBackend +from easynetwork.lowlevel.api_async.backend.abc import ILock import pytest @@ -675,9 +676,28 @@ async def test____create_condition_var____use_trio_Condition_class( condition = backend.create_condition_var(mock_lock) # Assert - mock_Condition.assert_called_once_with(mock_lock) + if mock_lock is None: + mock_Condition.assert_called_once_with() + else: + mock_Condition.assert_called_once_with(mock_lock) assert condition is mocker.sentinel.condition_var + async def test____create_condition_var____invalid_Lock_type( + self, + backend: TrioBackend, + mocker: MockerFixture, + ) -> None: + # Arrange + mock_lock: MagicMock = mocker.NonCallableMagicMock(spec=ILock) + mock_Condition = mocker.patch("trio.Condition", return_value=mocker.sentinel.condition_var) + + # Act + with pytest.raises(TypeError, match=r"^lock must be a trio\.Lock$"): + _ = backend.create_condition_var(mock_lock) + + # Assert + mock_Condition.assert_not_called() + @pytest.mark.parametrize("abandon_on_cancel", [False, True], ids=lambda p: f"abandon_on_cancel=={p}") async def test____run_in_thread____use_loop_run_in_executor( self,