async def test_error_handler_command_invoke_error(self): """Should call `handle_api_error` or `handle_unexpected_error` depending on original error.""" cog = ErrorHandler(self.bot) cog.handle_api_error = AsyncMock() cog.handle_unexpected_error = AsyncMock() test_cases = ( { "args": (self.ctx, errors.CommandInvokeError(ResponseCodeError(AsyncMock()))), "expect_mock_call": cog.handle_api_error }, { "args": (self.ctx, errors.CommandInvokeError(TypeError)), "expect_mock_call": cog.handle_unexpected_error }, { "args": (self.ctx, errors.CommandInvokeError(LockedResourceError("abc", "test"))), "expect_mock_call": "send" }, { "args": (self.ctx, errors.CommandInvokeError(InvalidInfractedUser(self.ctx.author))), "expect_mock_call": "send" } ) for case in test_cases: with self.subTest(args=case["args"], expect_mock_call=case["expect_mock_call"]): self.ctx.send.reset_mock() self.assertIsNone(await cog.on_command_error(*case["args"])) if case["expect_mock_call"] == "send": self.ctx.send.assert_awaited_once() else: case["expect_mock_call"].assert_awaited_once_with( self.ctx, case["args"][1].original )
async def wrapper(*args, **kwargs) -> Any: log.trace(f"{name}: mutually exclusive decorator called") if callable(resource_id): log.trace(f"{name}: binding args to signature") bound_args = function.get_bound_args(func, args, kwargs) log.trace(f"{name}: calling the given callable to get the resource ID") id_ = resource_id(bound_args) if inspect.isawaitable(id_): log.trace(f"{name}: awaiting to get resource ID") id_ = await id_ else: id_ = resource_id log.trace(f"{name}: getting the lock object for resource {namespace!r}:{id_!r}") # Get the lock for the ID. Create a lock if one doesn't exist yet. locks = __lock_dicts[namespace] lock_ = locks.setdefault(id_, asyncio.Lock()) # It's safe to check an asyncio.Lock is free before acquiring it because: # 1. Synchronous code like `if not lock_.locked()` does not yield execution # 2. `asyncio.Lock.acquire()` does not internally await anything if the lock is free # 3. awaits only yield execution to the event loop at actual I/O boundaries if wait or not lock_.locked(): log.debug(f"{name}: acquiring lock for resource {namespace!r}:{id_!r}...") async with lock_: return await func(*args, **kwargs) else: log.info(f"{name}: aborted because resource {namespace!r}:{id_!r} is locked") if raise_error: raise LockedResourceError(str(namespace), id_)
async def wrapper(*args, **kwargs) -> Any: log.trace(f"{name}: mutually exclusive decorator called") if callable(resource_id): log.trace(f"{name}: binding args to signature") bound_args = function.get_bound_args(func, args, kwargs) log.trace(f"{name}: calling the given callable to get the resource ID") id_ = resource_id(bound_args) if inspect.isawaitable(id_): log.trace(f"{name}: awaiting to get resource ID") id_ = await id_ else: id_ = resource_id log.trace(f"{name}: getting lock for resource {id_!r} under namespace {namespace!r}") # Get the lock for the ID. Create a lock if one doesn't exist yet. locks = __lock_dicts[namespace] lock_guard = locks.setdefault(id_, LockGuard()) if not lock_guard.locked: log.debug(f"{name}: resource {namespace!r}:{id_!r} is free; acquiring it...") with lock_guard: return await func(*args, **kwargs) else: log.info(f"{name}: aborted because resource {namespace!r}:{id_!r} is locked") if raise_error: raise LockedResourceError(str(namespace), id_)