def on_connect(self, connection): "Called when the socket connects" self._sock = connection._sock self._buffer = SocketLineReader(self._sock, self.socket_read_size, name=connection.name) if connection.decode_responses: self.encoding = connection.encoding
class PythonParser(BaseParser): "Plain Python parsing class" encoding = None def __init__(self, socket_read_size): self.socket_read_size = socket_read_size self._sock = None self._buffer = None def shutdown(self): ''' Free all resources with the assumption that this instance will never be used again. ''' if self._buffer is not None: self._buffer.close() self._buffer.join(JOIN_WAIT_TIME) if self._buffer.is_alive(): raise TimeoutError("Could not stop SocketLineReader in PythonParser instance.") def __del__(self): try: self.on_disconnect() except Exception: pass def on_connect(self, connection): "Called when the socket connects" self._sock = connection._sock self._buffer = SocketLineReader(self._sock, self.socket_read_size, name=connection.name) if connection.decode_responses: self.encoding = connection.encoding def on_disconnect(self): "Called when the socket disconnects" if self._buffer is not None: self._buffer.close() self._buffer = None if self._sock is not None: self._sock.close() self._sock = None self.encoding = None def can_read(self): return self._buffer and not self._buffer.empty() def read_int(self): ''' Use if an integer or long is expected in response to a command. Provides efficiency for when higher layers know what to expect back on this connection. :return: the expected integer or long :rtype: long :raise InvalidResponse if a non-integer is received from the Redis server. ''' response = self._buffer.readline().strip() if not response: raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR) if not response.startswith(':'): raise InvalidResponse("Expecting integer response but received '%s'" % response) try: return long(response[1:]) except ValueError: raise InvalidResponse("Expecting integer response but received '%s'" % response) def read_subscription_cmd_status_return(self, subscription_command, channel): ''' Given a subscription related command, send the command, parse the status response, and return the number of channels caller is subscribed to after the command was processed on the server.N :param subscription_command: the command to which a status response is expected: {subsribe | unsubscribe | psubscribe | punsubscribe} :type subscription_command: string :return: the number of channels caller is subscribed to after the subscription command. :rtype: int :raises ResponseError: if data read from the server is not of proper format. :raises TimeoutError: if server does not respond in time. ''' return self._buffer.read_subscription_cmd_status_return(subscription_command, channel) def read_response(self, socket_buffer=None, encoding=None): ''' Reads one line from the wire, and interprets it. Example: the acknowledgment to an unsubscribe from channel myChannel on the wire looks like this: *3\r\n$11\r\nUNSUBSCRIBE\r\n$7\r\nmyChannel\r\n:1\r\n' *3 # three items to follow $11 # string of 11 chars UNSUBSCRIBE $7 # string of 7 chars myChannel :1 # one channel subscribed to now Each line will cause a recursive call to this method (see elif byte == '*' below). Simpler calls will be individual elements, such as ':12', which returns the integer 12. These are the possible prefixes; each item is followed by a \r\n, which is stripped by SocketLineReader: +<str> simple string :<int> integer $<n> string of length <n> *<num> start of array with <num> elements :return: response string :rtype: string ''' if socket_buffer is None: socket_buffer = self._buffer response = socket_buffer.readline() if not response: raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR) return self.parse_response(response, socket_buffer=self._buffer, encoding=encoding)