def test_bunchify(): x = bunchify(dict(a=[dict(b=5), 9, (1, 2)], c=8)) assert x.a[0].b == 5 assert x.a[1] == 9 assert isinstance(x.a[2], tuple) assert x.c == 8 assert x.pop("c") == 8
def __init__(self, host, port, path="/api", name=None, timeout=None, ttl=120, init_methods=True, client_type=None, credentials=DEFAULT_CREDENTIALS, rpc_retries=2): self._rpc_retries = rpc_retries self._conn_params = bunchify(host=host, port=port, timeout=timeout or 120) self._path = path self._ttl = ttl self._conn = None self._headers = {} if credentials: b64 = codecs.encode(credentials.encode("latin1"), "base64").replace(b"\n", b"") self._headers["authorization"] = b"Basic " + b64 if client_type: self._headers['Client-Type'] = client_type self._spec = None self._exception_handlers = {} self._lock = RLock() self.long_operations = self._LongOperations(self) self.name = name if init_methods: self._populate_methods()
class IndentableTextBuffer(): NICE_BOX = bunchify( LINE="─", INDENT_SEGMENT="│ ", OPEN="┬", INDENT_OPEN="├───", INDENT_CLOSE="╰", SEGMENT_END="╼ ", SEGMENT_START=" ╾", SECTION_OPEN="╮", SECTION_SEGMENT="│", SECTION_CLOSE="╯", ) TEXTUAL_BOX = bunchify( LINE="-", INDENT_SEGMENT="| ", INDENT_OPEN="+---", OPEN=".", INDENT_CLOSE="`", SEGMENT_END="- ", SEGMENT_START=" -", SECTION_OPEN=".", SECTION_SEGMENT="|", SECTION_CLOSE="*", ) def __init__(self, fmt="", *args, **kwargs): self.current = self.root = Node(fmt, args, kwargs, []) def __repr__(self): return self.render(width=80) def __len__(self): def count(root): for child in root.children: if isinstance(child, str): yield 1 else: yield from count(child) return sum(count(self.root)) def write(self, line, *args, **kwargs): if args or kwargs: line = line.format(*args, **kwargs) self.current.children.append(line) def extend(self, other): self.current.children.extend(other.root.children) @contextmanager def indent(self, fmt, *args, **kwargs): parent = self.current self.current = Node(fmt, args, kwargs, []) parent.children.append(self.current) yield self.current = parent def render(self, width=None, textual=None, prune=False, file=None, overflow='ignore', edges=True): if width is None: from .logging import TERM_WIDTH as width if textual is None: from .logging import GRAPHICAL textual = not GRAPHICAL buff = file if file else StringIO() G = self.TEXTUAL_BOX if textual else self.NICE_BOX from textwrap import wrap def has_descendents(elem): if isinstance(elem, str): return True return any(map(has_descendents, elem.children)) def write_tree(elem, depth=0): if isinstance(elem, str): prefix = G.INDENT_SEGMENT * (depth - 1) prefix += G.INDENT_SEGMENT for par in elem.splitlines(): if overflow == "wrap": lines = wrap(par, width - len(prefix) - 1) elif overflow == "trim": lines = [compact(par, width - len(prefix) - 1)] else: lines = [par] for line in lines: line = prefix + line if len(line) < width: if edges: line = line.ljust(width - 1) + G.SECTION_SEGMENT buff.write(line + "\n") elif not prune or has_descendents(elem): if depth: prefix = G.INDENT_SEGMENT * (depth - 1) + G.INDENT_OPEN else: prefix = "" txt = (G.SEGMENT_END + elem.fmt.format(*elem.args, **elem.kwargs) + G.SEGMENT_START) if elem.fmt else "" header = prefix + G.OPEN + txt buff.write( header.ljust(width - 1, G.LINE) + G.SECTION_OPEN + "\n") for child in elem.children: write_tree(child, depth + 1) footer = (G.INDENT_SEGMENT * depth + G.INDENT_CLOSE + G.LINE * (width - len(G.INDENT_SEGMENT) * (depth + 1) - len(txt) + 2) + txt) buff.write(footer + G.SECTION_CLOSE + "\n") write_tree(self.root) if not file: return buff.getvalue()
def make_request(): if not quiet: _logger.debug("request >> #%04d: %s/%s", message_id, self.url, method) try: try: conn = self.get_connection( timeout_override=timeout_override) except: typ, exc, tb = sys.exc_info() raise_if_async_exception(exc) raise_with_traceback( ConnectionError(url=self.url, message_id=message_id, exc=exc), tb) json_req = _make_request(message_id, method, params) try: conn.request('POST', self._path, json_req, self._headers) except socket.timeout: typ, exc, tb = sys.exc_info() raise_if_async_exception(exc) raise_with_traceback(ServerTimeout(**exc_params), tb) except: typ, exc, tb = sys.exc_info() raise_if_async_exception(exc) raise_with_traceback(RequestError(exc=exc, **exc_params), tb) try: response = conn.getresponse() except socket.timeout: typ, exc, tb = sys.exc_info() raise_if_async_exception(exc) raise_with_traceback(ServerTimeout(**exc_params), tb) except: typ, exc, tb = sys.exc_info() raise_if_async_exception(exc) # To address leader temporarily being down ("BadStatusLine") time.sleep(1) raise_with_traceback(ReplyError(exc=exc, **exc_params), tb) except: self._conn = None # create a new connection next time raise if response.status == 408 or response.status == 400: # AWS issues _logger.debug("Got error code %s, on request #%04d, retrying", response.status, message_id) self._conn = None # create a new connection next time time.sleep(0.5) self.retry_last_rpc( 'Got error code %s - %s' % (response.status, httpclient.responses[response.status])) if response.status == 301: # redirect new_url = response.getheader("location") parsed = urlparse(new_url) _logger.debug("redirecting http://%s:%s/%s to %s", self._conn_params.host, self._conn_params.port, self._path, new_url) # print("redirect to", new_url) orig = Bunch(**self._conn_params) if ":" in parsed.netloc: host, port = parsed.netloc.split(":") self._conn_params.host = host self._conn_params.port = int(port) else: self._conn_params.host = parsed.netloc self.expire_connection() conn = self.get_connection(timeout_override=timeout_override) conn.request('POST', self._path, _make_request(message_id, method, params), self._headers) response = conn.getresponse() self.expire_connection() self._conn_params = orig if response.status == 503: # service not available, retry again later time.sleep(0.5) return self.retry_last_rpc("Service unavailable, retry later") try: response_text = response.read().decode('utf-8') except socket.timeout: self._conn = None # create a new connection next time typ, exc, tb = sys.exc_info() raise_with_traceback(ServerTimeout(**exc_params), tb) except IncompleteRead: self._conn = None # create a new connection next time typ, exc, tb = sys.exc_info() raise_with_traceback(ReadError(exc=exc, **exc_params), tb) if response.status != 200: # some other error raise HTTPException(status=response.status, reason=response.reason, text=response_text, **exc_params) try: response_object = json.loads(response_text) except ValueError: raise ResponseError("Could not parse json respone", response_text=response_text, **exc_params) if not quiet: _logger.debug("response << #%04d: %s -> %s", response_object.get('id', -1), method, DataSize(response.getheader("content-length"))) if message_id != response_object.get('id'): raise ResponseIdMismatch(responded=response_object.get('id'), response_text=response_text, **exc_params) if 'error' not in response_object: result = response_object['result'] return bunchify(result) if should_bunchify else result error_code = response_object['error'].pop('code', None) error_data = response_object['error'].pop( 'data', "(no further information)") remote_message = response_object['error'].pop( 'message', "(no message)") server_node_id = response.getheader('server-node-id', None) if error_code == -32601: # Method not Found raise RemoteMethodNotFound(method=method) # raise AttributeError('%s' % remote_message) if error_code == -32602: # Invalid Params raise TypeError('%s(%s)' % (remote_message, error_data)) if isinstance(error_data, dict): if server_node_id: error_data['served_by_node'] = server_node_id if 'exceptionClass' in error_data: exception_class = error_data.pop('exceptionClass') if isinstance(exception_class, list): exceptions._register_ancestry(exception_class) exception_class = exception_class[0] exception_name = exception_class.rpartition(".")[-1] handler = self._exception_handlers.get( exception_name) or getattr(exceptions, exception_name) if hasattr(handler, '__bases__') and error_data.get( "retry", False) and RetrySignal not in handler.__bases__: handler.__bases__ += (RetrySignal, ) exception_text = error_data.pop('exceptionText', remote_message) error_data['jrpc'] = exc_params raise handler(exception_text, **error_data) else: exc_params.update(error_data) raise RemoteException(remote_message, **exc_params) else: if error_data: remote_message += "\n%s" % (error_data, ) exc_params.update(response_object['error']) if server_node_id: exc_params['served_by_node'] = server_node_id raise RemoteException(remote_message, **exc_params)