def _handle_batch_response(self, msgs): def _extract_msg_content(msg): if 'result' in msg: return msg['result'] else: return RpcErrors.error_to_exception(msg['error']) without_id_msgs = list(filter(lambda x: x.get('id') is None, msgs)) with_id_msgs = list(filter(lambda x: x.get('id') is not None, msgs)) resp_map = dict(map(lambda x: (x['id'], x), with_id_msgs)) msg_ids = set(resp_map.keys()) resolved = False for idtuple, promise in self._batch_responses.items(): if msg_ids.issubset(set(idtuple)): batch_response = [] for req_id in idtuple: if req_id in resp_map: batch_response.append( _extract_msg_content(resp_map[req_id])) elif without_id_msgs: batch_response.append( _extract_msg_content(without_id_msgs.pop(0))) else: batch_response.append( BsonRpcError( 'Peer did not respond to this request!')) promise.set(batch_response) resolved = True break if not resolved: self._log_error( u'Unrecognized/expired batch response from peer: ' + six.text_type(msgs))
def unregister(self, msg_id): if isinstance(msg_id, tuple): promise = self._batch_responses.get(msg_id) if msg_id in self._batch_responses: del self._batch_responses[msg_id] else: promise = self._responses.get(msg_id) if msg_id in self._responses: del self._responses[msg_id] if promise and not promise.is_set(): promise.set(BsonRpcError('Timeout'))
def put(self, item): ''' Put item to queue -> codec -> socket. :param item: Message object. :type item: dict, list or None ''' if self._closed: raise BsonRpcError('Attempt to put items to closed queue.') msg_bytes = self.codec.into_frame(self.codec.dumps(item)) with self._lock: self.socket.sendall(msg_bytes)
def _compose_batch(batch_calls): request_ids = [] batch = [] try: for call_type, method_name, args, kwargs in batch_calls: if call_type.lower().startswith('n'): batch.append( self.definitions.notification( method_name, args, kwargs)) else: msg_id = six.next(self.id_generator) batch.append( self.definitions.request(msg_id, method_name, args, kwargs)) request_ids.append(msg_id) except Exception as e: raise BsonRpcError(u'Malformed batch call: ' + six.text_type(e)) return request_ids, batch
def batch_call(self, batch_calls, timeout=None): ''' :param batch_calls: Batch of requests/notifications to be executed on the peer node. Use ``BatchBuilder()`` and pass the object here as a parameter. Example: .. code-block:: python bb = BatchBuilder(['swapit', 'times'], ['logit']) bb.swapit('hello') # request bb.times(3, 5) # request bb.logit('world') # notification results = jsonrpc.batch_call(bb, timeout=15.0) # results: ['olleh', 15] Note that ``BatchBuilder`` is used and behaves like the peer-proxy returned by ``.get_peer_proxy()``. Instead of BatchBuilder you may give a simple list argument which must be in the following format: [("r"/"n", "<method-name>", args, kwargs), ...] :type batch_calls: bsonrpc.BatchBuilder | list of 4-tuples :param timeout: Timeout in seconds for waiting results. Default: None :type timeout: float | None :returns: * list of results to requests, in order of original requests. Each result may be: * a single return value or * a tuple of return values or * an Exception object * ``None`` if ``batch_calls`` contained only notifications. :raises: ResponseTimeout in case batch_calls contains requests, for which response batch did not arrive within timeout. ''' def _compose_batch(batch_calls): request_ids = [] batch = [] try: for call_type, method_name, args, kwargs in batch_calls: if call_type.lower().startswith('n'): batch.append( self.definitions.notification( method_name, args, kwargs)) else: msg_id = six.next(self.id_generator) batch.append( self.definitions.request(msg_id, method_name, args, kwargs)) request_ids.append(msg_id) except Exception as e: raise BsonRpcError(u'Malformed batch call: ' + six.text_type(e)) return request_ids, batch if isinstance(batch_calls, BatchBuilder): batch_calls = batch_calls._batch_calls if not batch_calls: raise BsonRpcError(u'Refusing to send an empty batch.') format_info = (u'Argument "batch_calls"(list) is expected to contain ' u'4-tuples of (str, str, list, dict) -types.') for item in batch_calls: assert len(item) == 4, format_info assert isinstance(item[0], six.string_types), format_info assert isinstance(item[1], six.string_types), format_info assert isinstance(item[2], (list, tuple)), format_info assert isinstance(item[3], dict), format_info request_ids, batch = _compose_batch(batch_calls) # Notifications only: if not request_ids: self.socket_queue.put(batch) return None # At least one request in the batch: try: with ResultScope(self.dispatcher, tuple(request_ids)) as promise: self.socket_queue.put(batch) results = promise.wait(timeout) except RuntimeError: raise ResponseTimeout(u'Timeout for waiting batch result.') if isinstance(results, Exception): raise results return results