async def execute(self, raise_on_error=True): """Executes all the commands in the current pipeline""" stack = self.command_stack if not stack: return [] if self.scripts: await self.load_scripts() if self.transaction or self.explicit_transaction: exec = self._execute_transaction else: exec = self._execute_pipeline conn = self.connection if not conn: conn = self.connection_pool.get_connection() # assign to self.connection so reset() releases the connection # back to the pool after we're done self.connection = conn try: return await exec(conn, stack, raise_on_error) except (ConnectionError, TimeoutError, aredis.compat.CancelledError) as e: conn.disconnect() if not conn.retry_on_timeout and isinstance(e, TimeoutError): raise # if we were watching a variable, the watch is no longer valid # since this connection has died. raise a WatchError, which # indicates the user should retry his transaction. If this is more # than a temporary failure, the WATCH that the user next issues # will fail, propegating the real ConnectionError if self.watching: raise WatchError("A ConnectionError occured on while watching " "one or more keys") # otherwise, it's safe to retry since the transaction isn't # predicated on any state return await exec(conn, stack, raise_on_error) finally: await self.reset()
async def _execute_transaction(self, connection, commands, raise_on_error): cmds = chain([(('MULTI', ), {})], commands, [(('EXEC', ), {})]) all_cmds = connection.pack_commands([args for args, _ in cmds]) await connection.send_packed_command(all_cmds) errors = [] # parse off the response for MULTI # NOTE: we need to handle ResponseErrors here and continue # so that we read all the additional command messages from # the socket try: await self.parse_response(connection, '_') except ResponseError: errors.append((0, sys.exc_info()[1])) # and all the other commands for i, command in enumerate(commands): try: await self.parse_response(connection, '_') except ResponseError: ex = sys.exc_info()[1] self.annotate_exception(ex, i + 1, command[0]) errors.append((i, ex)) # parse the EXEC. try: response = await self.parse_response(connection, '_') except ExecAbortError: if self.explicit_transaction: await self.immediate_execute_command('DISCARD') if errors: raise errors[0][1] raise sys.exc_info()[1] if response is None: raise WatchError("Watched variable changed.") # put any parse errors into the response for i, e in errors: response.insert(i, e) if len(response) != len(commands): self.connection.disconnect() raise ResponseError("Wrong number of response items from " "pipeline execution") # find any errors in the response and raise if necessary if raise_on_error: self.raise_first_error(commands, response) # We have to run response callbacks manually data = [] for r, cmd in zip(response, commands): if not isinstance(r, Exception): args, options = cmd command_name = args[0] if command_name in self.response_callbacks: callback = self.response_callbacks[command_name] r = callback(r, **options) # typing.Awaitable is not available in Python3.5 # so use inspect.isawaitable instead # according to issue https://github.com/NoneGG/aredis/issues/77 if inspect.isawaitable(response): r = await r data.append(r) return data