class IPCProtocol(Protocol): class State(object): UNKNOWN = -1 HANDSHAKE = 0 CONNECTED = 1 def connectionMade(self): self.state = IPCProtocol.State.UNKNOWN self.credentials = self.factory.username + ':' + self.factory.password if self.factory.password else '' self.transport.write(self.credentials + '\3\0') self._message = None def dataReceived(self, data): if self.state == IPCProtocol.State.CONNECTED: try: if not self._message: self._message = self._reader.read_header(source = data) self._buffer = '' self._buffer += data buffer_len = len(self._buffer) if self._buffer else 0 while self._message and self._message.size <= buffer_len: complete_message = self._buffer[:self._message.size] if buffer_len > self._message.size: self._buffer = self._buffer[self._message.size:] buffer_len = len(self._buffer) if self._buffer else 0 self._message = self._reader.read_header(source = self._buffer) else: self._message = None self._buffer = '' buffer_len = 0 self.factory.onMessage(self._reader.read(source = complete_message, numpy_temporals = True)) except: self.factory.onError(sys.exc_info()) self._message = None self._buffer = '' elif self.state == IPCProtocol.State.UNKNOWN: # handshake if len(data) == 1: self._init(data) else: self.state = IPCProtocol.State.HANDSHAKE self.transport.write(self.credentials + '\0') else: # protocol version fallback if len(data) == 1: self._init(data) else: raise QAuthenticationException('Connection denied.') def _init(self, data): self.state = IPCProtocol.State.CONNECTED self.protocol_version = min(struct.unpack('B', data)[0], 3) self._writer = QWriter(stream = None, protocol_version = self.protocol_version) self._reader = QReader(stream = None) self.factory.clientReady(self) def query(self, msg_type, query, *parameters): if parameters and len(parameters) > 8: raise QWriterException('Too many parameters.') if not parameters or len(parameters) == 0: self.transport.write(self._writer.write(query, msg_type)) else: self.transport.write(self._writer.write([query] + list(parameters), msg_type))
class QConnection(object): '''Connector class for interfacing with the q service. Provides methods for synchronous and asynchronous interaction. The :class:`.QConnection` class provides a context manager API and can be used with a ``with`` statement:: with qconnection.QConnection(host = 'localhost', port = 5000) as q: print q print q('{`int$ til x}', 10) :Parameters: - `host` (`string`) - q service hostname - `port` (`integer`) - q service port - `username` (`string` or `None`) - username for q authentication/authorization - `password` (`string` or `None`) - password for q authentication/authorization - `timeout` (`nonnegative float` or `None`) - set a timeout on blocking socket operations :Options: - `raw` (`boolean`) - if ``True`` returns raw data chunk instead of parsed data, **Default**: ``False`` - `numpy_temporals` (`boolean`) - if ``False`` temporal vectors are backed by raw q representation (:class:`.QTemporalList`, :class:`.QTemporal`) instances, otherwise are represented as `numpy datetime64`/`timedelta64` arrays and atoms, **Default**: ``False`` ''' def __init__(self, host, port, username = None, password = None, timeout = None, **options): self.host = host self.port = port self.username = username self.password = password self._connection = None self._protocol_version = None self.timeout = timeout self._options = MetaData(**READER_CONFIGURATION.union_dict(**options)) def __enter__(self): self.open() return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() @property def protocol_version(self): '''Retrieves established version of the IPC protocol. :returns: `integer` -- version of the IPC protocol ''' return self._protocol_version def open(self): '''Initialises connection to q service. If the connection hasn't been initialised yet, invoking the :func:`.open` creates a new socket and performs a handshake with a q service. :raises: :class:`.QConnectionException`, :class:`.QAuthenticationException` ''' if not self._connection: if not self.host: raise QConnectionException('Host cannot be None') self._init_socket() self._initialize() self._writer = QWriter(self._connection, protocol_version = self._protocol_version) self._reader = QReader(self._connection.makefile()) def _init_socket(self): '''Initialises the socket used for communicating with a q service,''' try: self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._connection.connect((self.host, self.port)) self._connection.settimeout(self.timeout) except: self._connection = None raise def close(self): '''Closes connection with the q service.''' if self._connection: self._connection.close() self._connection = None def is_connected(self): '''Checks whether connection with a q service has been established. Connection is considered inactive when: - it has not been initialised, - it has been closed. :returns: `boolean` -- ``True`` if connection has been established, ``False`` otherwise ''' return True if self._connection else False def _initialize(self): '''Performs a IPC protocol handshake.''' credentials = self.username + ':' + self.password if self.password else '' self._connection.send(credentials + '\3\0') response = self._connection.recv(1) if len(response) != 1: self.close() self._init_socket() self._connection.send(credentials + '\0') response = self._connection.recv(1) if len(response) != 1: self.close() raise QAuthenticationException('Connection denied.') self._protocol_version = min(struct.unpack('B', response)[0], 3) def __str__(self): return '%s@:%s:%s' % (self.username, self.host, self.port) if self.username else ':%s:%s' % (self.host, self.port) def query(self, msg_type, query, *parameters): '''Performs a query against a q service. In typical use case, `query` is the name of the function to call and `parameters` are its parameters. When `parameters` list is empty, the query can be an arbitrary q expression (e.g. ``0 +/ til 100``). Calls a anonymous function with a single parameter: >>> q.query(qconnection.MessageType.SYNC,'{til x}', 10) Executes a q expression: >>> q.query(qconnection.MessageType.SYNC,'til 10') :Parameters: - `msg_type` (one of the constants defined in :class:`.MessageType`) - type of the query to be executed - `query` (`string`) - query to be executed - `parameters` (`list` or `None`) - parameters for the query :raises: :class:`.QConnectionException`, :class:`.QWriterException` ''' if not self._connection: raise QConnectionException('Connection is not established.') if parameters and len(parameters) > 8: raise QWriterException('Too many parameters.') if not parameters or len(parameters) == 0: self._writer.write(query, msg_type) else: self._writer.write([query] + list(parameters), msg_type) def sync(self, query, *parameters, **options): '''Performs a synchronous query against a q service and returns parsed data. In typical use case, `query` is the name of the function to call and `parameters` are its parameters. When `parameters` list is empty, the query can be an arbitrary q expression (e.g. ``0 +/ til 100``). Executes a q expression: >>> print q.sync('til 10') [0 1 2 3 4 5 6 7 8 9] Executes an anonymous q function with a single parameter: >>> print q.sync('{til x}', 10) [0 1 2 3 4 5 6 7 8 9] Executes an anonymous q function with two parameters: >>> print q.sync('{y + til x}', 10, 1) [ 1 2 3 4 5 6 7 8 9 10] >>> print q.sync('{y + til x}', *[10, 1]) [ 1 2 3 4 5 6 7 8 9 10] The :func:`.sync` is called from the overloaded :func:`.__call__` function. This allows :class:`.QConnection` instance to be called as a function: >>> print q('{y + til x}', 10, 1) [ 1 2 3 4 5 6 7 8 9 10] :Parameters: - `query` (`string`) - query to be executed - `parameters` (`list` or `None`) - parameters for the query :Options: - `raw` (`boolean`) - if ``True`` returns raw data chunk instead of parsed data, **Default**: ``False`` - `numpy_temporals` (`boolean`) - if ``False`` temporal vectors are backed by raw q representation (:class:`.QTemporalList`, :class:`.QTemporal`) instances, otherwise are represented as `numpy datetime64`/`timedelta64` arrays and atoms, **Default**: ``False`` :returns: query result parsed to Python data structures :raises: :class:`.QConnectionException`, :class:`.QWriterException`, :class:`.QReaderException` ''' self.query(MessageType.SYNC, query, *parameters) response = self.receive(data_only = False, **options) if response.type == MessageType.RESPONSE: return response.data else: self._writer.write(QException('nyi: qPython expected response message'), MessageType.ASYNC if response.type == MessageType.ASYNC else MessageType.RESPONSE) raise QReaderException('Received message of type: %s where response was expected') def async(self, query, *parameters): '''Performs an asynchronous query and returns **without** retrieving of the response. In typical use case, `query` is the name of the function to call and `parameters` are its parameters. When `parameters` list is empty, the query can be an arbitrary q expression (e.g. ``0 +/ til 100``). Calls a anonymous function with a single parameter: >>> q.async('{til x}', 10) Executes a q expression: >>> q.async('til 10') :Parameters: - `query` (`string`) - query to be executed - `parameters` (`list` or `None`) - parameters for the query :raises: :class:`.QConnectionException`, :class:`.QWriterException` ''' self.query(MessageType.ASYNC, query, *parameters)
class QConnection(object): '''Connector class for interfacing with the q service. Provides methods for synchronous and asynchronous interaction. The :class:`.QConnection` class provides a context manager API and can be used with a ``with`` statement:: with qconnection.QConnection(host = 'localhost', port = 5000) as q: print q print q('{`int$ til x}', 10) :Parameters: - `host` (`string`) - q service hostname - `port` (`integer`) - q service port - `username` (`string` or `None`) - username for q authentication/authorization - `password` (`string` or `None`) - password for q authentication/authorization - `timeout` (`nonnegative float` or `None`) - set a timeout on blocking socket operations :Options: - `raw` (`boolean`) - if ``True`` returns raw data chunk instead of parsed data, **Default**: ``False`` - `numpy_temporals` (`boolean`) - if ``False`` temporal vectors are backed by raw q representation (:class:`.QTemporalList`, :class:`.QTemporal`) instances, otherwise are represented as `numpy datetime64`/`timedelta64` arrays and atoms, **Default**: ``False`` ''' def __init__(self, host, port, username=None, password=None, timeout=None, **options): self.host = host self.port = port self.username = username self.password = password self._connection = None self._protocol_version = None self.timeout = timeout self._options = MetaData(**READER_CONFIGURATION.union_dict(**options)) def __enter__(self): self.open() return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() @property def protocol_version(self): '''Retrieves established version of the IPC protocol. :returns: `integer` -- version of the IPC protocol ''' return self._protocol_version def open(self): '''Initialises connection to q service. If the connection hasn't been initialised yet, invoking the :func:`.open` creates a new socket and performs a handshake with a q service. :raises: :class:`.QConnectionException`, :class:`.QAuthenticationException` ''' if not self._connection: if not self.host: raise QConnectionException('Host cannot be None') self._init_socket() self._initialize() self._writer = QWriter(self._connection, protocol_version=self._protocol_version) self._reader = QReader(self._connection.makefile()) def _init_socket(self): '''Initialises the socket used for communicating with a q service,''' try: self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._connection.connect((self.host, self.port)) self._connection.settimeout(self.timeout) except: self._connection = None raise def close(self): '''Closes connection with the q service.''' if self._connection: self._connection.close() self._connection = None def is_connected(self): '''Checks whether connection with a q service has been established. Connection is considered inactive when: - it has not been initialised, - it has been closed. :returns: `boolean` -- ``True`` if connection has been established, ``False`` otherwise ''' return True if self._connection else False def _initialize(self): '''Performs a IPC protocol handshake.''' credentials = self.username + ':' + self.password if self.password else '' self._connection.send(credentials + '\3\0') response = self._connection.recv(1) if len(response) != 1: self.close() self._init_socket() self._connection.send(credentials + '\0') response = self._connection.recv(1) if len(response) != 1: self.close() raise QAuthenticationException('Connection denied.') self._protocol_version = min(struct.unpack('B', response)[0], 3) def __str__(self): return '%s@:%s:%s' % (self.username, self.host, self.port) if self.username else ':%s:%s' % ( self.host, self.port) def query(self, msg_type, query, *parameters): '''Performs a query against a q service. In typical use case, `query` is the name of the function to call and `parameters` are its parameters. When `parameters` list is empty, the query can be an arbitrary q expression (e.g. ``0 +/ til 100``). Calls a anonymous function with a single parameter: >>> q.query(qconnection.MessageType.SYNC,'{til x}', 10) Executes a q expression: >>> q.query(qconnection.MessageType.SYNC,'til 10') :Parameters: - `msg_type` (one of the constants defined in :class:`.MessageType`) - type of the query to be executed - `query` (`string`) - query to be executed - `parameters` (`list` or `None`) - parameters for the query :raises: :class:`.QConnectionException`, :class:`.QWriterException` ''' if not self._connection: raise QConnectionException('Connection is not established.') if parameters and len(parameters) > 8: raise QWriterException('Too many parameters.') if not parameters or len(parameters) == 0: self._writer.write(query, msg_type) else: self._writer.write([query] + list(parameters), msg_type) def sync(self, query, *parameters, **options): '''Performs a synchronous query against a q service and returns parsed data. In typical use case, `query` is the name of the function to call and `parameters` are its parameters. When `parameters` list is empty, the query can be an arbitrary q expression (e.g. ``0 +/ til 100``). Executes a q expression: >>> print q.sync('til 10') [0 1 2 3 4 5 6 7 8 9] Executes an anonymous q function with a single parameter: >>> print q.sync('{til x}', 10) [0 1 2 3 4 5 6 7 8 9] Executes an anonymous q function with two parameters: >>> print q.sync('{y + til x}', 10, 1) [ 1 2 3 4 5 6 7 8 9 10] >>> print q.sync('{y + til x}', *[10, 1]) [ 1 2 3 4 5 6 7 8 9 10] The :func:`.sync` is called from the overloaded :func:`.__call__` function. This allows :class:`.QConnection` instance to be called as a function: >>> print q('{y + til x}', 10, 1) [ 1 2 3 4 5 6 7 8 9 10] :Parameters: - `query` (`string`) - query to be executed - `parameters` (`list` or `None`) - parameters for the query :Options: - `raw` (`boolean`) - if ``True`` returns raw data chunk instead of parsed data, **Default**: ``False`` - `numpy_temporals` (`boolean`) - if ``False`` temporal vectors are backed by raw q representation (:class:`.QTemporalList`, :class:`.QTemporal`) instances, otherwise are represented as `numpy datetime64`/`timedelta64` arrays and atoms, **Default**: ``False`` :returns: query result parsed to Python data structures :raises: :class:`.QConnectionException`, :class:`.QWriterException`, :class:`.QReaderException` ''' self.query(MessageType.SYNC, query, *parameters) response = self.receive(data_only=False, **options) if response.type == MessageType.RESPONSE: return response.data else: self._writer.write( QException('nyi: qPython expected response message'), MessageType.ASYNC if response.type == MessageType.ASYNC else MessageType.RESPONSE) raise QReaderException( 'Received message of type: %s where response was expected') def async (self, query, *parameters): '''Performs an asynchronous query and returns **without** retrieving of the response. In typical use case, `query` is the name of the function to call and `parameters` are its parameters. When `parameters` list is empty, the query can be an arbitrary q expression (e.g. ``0 +/ til 100``). Calls a anonymous function with a single parameter: >>> q.async('{til x}', 10) Executes a q expression: >>> q.async('til 10') :Parameters: - `query` (`string`) - query to be executed - `parameters` (`list` or `None`) - parameters for the query :raises: :class:`.QConnectionException`, :class:`.QWriterException` ''' self.query(MessageType.ASYNC, query, *parameters) def receive(self, data_only=True, **options): '''Reads and (optionally) parses the response from a q service. Retrieves query result along with meta-information: >>> q.query(qconnection.MessageType.SYNC,'{x}', 10) >>> print q.receive(data_only = False, raw = False) QMessage: message type: 2, data size: 13, is_compressed: False, data: 10 Retrieves parsed query result: >>> q.query(qconnection.MessageType.SYNC,'{x}', 10) >>> print q.receive(data_only = True, raw = False) 10 Retrieves not-parsed (raw) query result: >>> from binascii import hexlify >>> q.query(qconnection.MessageType.SYNC,'{x}', 10) >>> print hexlify(q.receive(data_only = True, raw = True)) fa0a000000 :Parameters: - `data_only` (`boolean`) - if ``True`` returns only data part of the message, otherwise returns data and message meta-information encapsulated in :class:`.QMessage` instance :Options: - `raw` (`boolean`) - if ``True`` returns raw data chunk instead of parsed data, **Default**: ``False`` - `numpy_temporals` (`boolean`) - if ``False`` temporal vectors are backed by raw q representation (:class:`.QTemporalList`, :class:`.QTemporal`) instances, otherwise are represented as `numpy datetime64`/`timedelta64` arrays and atoms, **Default**: ``False`` :returns: depending on parameter flags: :class:`.QMessage` instance, parsed message, raw data :raises: :class:`.QReaderException` ''' result = self._reader.read(**self._options.union_dict(**options)) return result.data if data_only else result def __call__(self, *parameters, **options): return self.sync(parameters[0], *parameters[1:], **options)
class IPCProtocol(Protocol): class State(object): UNKNOWN = -1 HANDSHAKE = 0 CONNECTED = 1 def connectionMade(self): self.state = IPCProtocol.State.UNKNOWN self.credentials = self.factory.username + ':' + self.factory.password if self.factory.password else '' self.transport.write(self.credentials + '\3\0') self._message = None def dataReceived(self, data): if self.state == IPCProtocol.State.CONNECTED: try: if not self._message: self._message = self._reader.read_header(source=data) self._buffer = '' self._buffer += data buffer_len = len(self._buffer) if self._buffer else 0 while self._message and self._message.size <= buffer_len: complete_message = self._buffer[:self._message.size] if buffer_len > self._message.size: self._buffer = self._buffer[self._message.size:] buffer_len = len(self._buffer) if self._buffer else 0 self._message = self._reader.read_header( source=self._buffer) else: self._message = None self._buffer = '' buffer_len = 0 self.factory.onMessage( self._reader.read(source=complete_message, numpy_temporals=True)) except: self.factory.onError(sys.exc_info()) self._message = None self._buffer = '' elif self.state == IPCProtocol.State.UNKNOWN: # handshake if len(data) == 1: self._init(data) else: self.state = IPCProtocol.State.HANDSHAKE self.transport.write(self.credentials + '\0') else: # protocol version fallback if len(data) == 1: self._init(data) else: raise QAuthenticationException('Connection denied.') def _init(self, data): self.state = IPCProtocol.State.CONNECTED self.protocol_version = min(struct.unpack('B', data)[0], 3) self._writer = QWriter(stream=None, protocol_version=self.protocol_version) self._reader = QReader(stream=None) self.factory.clientReady(self) def query(self, msg_type, query, *parameters): if parameters and len(parameters) > 8: raise QWriterException('Too many parameters.') if not parameters or len(parameters) == 0: self.transport.write(self._writer.write(query, msg_type)) else: self.transport.write( self._writer.write([query] + list(parameters), msg_type))
class QConnection(object): ''' Connector class for interfacing with the q service. Provides methods for synchronous and asynchronous interaction. ''' def __init__(self, host, port, username = None, password = None, timeout = None): self.host = host self.port = port self.username = username self.password = password self._connection = None self._protocol_version = None self.timeout = timeout '''Retrieves q protocol version estabilished with remote q service.''' @property def protocol_version(self): return self._protocol_version '''Initializes connection to q service.''' def open(self): if not self._connection: if not self.host: raise QConnectionException('Host cannot be None') self._init_socket() self._initialize() self._writer = QWriter(self._connection, protocol_version = self._protocol_version) self._reader = QReader(self._connection.makefile()) def _init_socket(self): self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._connection.connect((self.host, self.port)) self._connection.settimeout(self.timeout) '''Closes connection with q service.''' def close(self): if self._connection: self._connection.close() self._connection = None ''' Checks whether connection has been established. Connection is considered inactive when: - it has not been initialized, - it has been closed. ''' def is_connected(self): return True if self._connection else False def _initialize(self): credentials = self.username + ':' + self.password if self.password else '' self._connection.send(credentials + '\3\0') response = self._connection.recv(1) if len(response) != 1: self.close() self._init_socket() self._connection.send(credentials + '\0') response = self._connection.recv(1) if len(response) != 1: self.close() raise QAuthenticationException('Connection denied.') self._protocol_version = min(struct.unpack('B', response)[0], 3) def __str__(self): return '%s@:%s:%s' % (self.username, self.host, self.port) if self.username else ':%s:%s' % (self.host, self.port) ''' Performs a query against a q service. Arguments: msg_type -- type of the query to be executed (as defined in MessageType class) query -- query to be executed parameters -- parameters for the query ''' def query(self, msg_type, query, *parameters): if not self._connection: raise QConnectionException('Connection is not established.') if parameters and len(parameters) > 8: raise QWriterException('Too many parameters.') if not parameters or len(parameters) == 0: return self._writer.write(query, msg_type) else: return self._writer.write([query] + list(parameters), msg_type) '''Performs a synchronous query and returns parsed data.''' def sync(self, query, *parameters): self.query(MessageType.SYNC, query, *parameters) response = self.receive(data_only = False) if response.type == MessageType.RESPONSE: return response.data else: self._writer.write(QException('nyi: qPython expected response message'), MessageType.ASYNC if response.type == MessageType.ASYNC else MessageType.RESPONSE) raise QReaderException('Received message of type: %s where response was expected') '''Performs an asynchronous query and returns without retrieving of the response.''' def async(self, query, *parameters): self.query(MessageType.ASYNC, query, *parameters)