# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # import re from sys import stdout class ThriftPrettyPrinter(object): """Implements a pretty printer for Thrift objects. Generates string representations. Does not output to stdout, stderr, or any other file handles.""" # Inputs: # redacted_fields - list of names of object attributes whose # values will not be printed out # indent - string containing only spaces, used as the # base indentation where all other indentations # will be multiples of this string # objects_to_skip - list of names of objects attributes that # will not be printed out, useful to ignore # duplicate information such as query results def __init__(self, redacted_fields=("secret", "password"), indent=" ", objects_to_skip=("TRowSet", "TGetRuntimeProfileResp")): if redacted_fields is not None: assert type(redacted_fields) is list or \ type(redacted_fields) is tuple, \ "redacted_fields must be either a list or a tuple" self.base_indent = indent self.redacted_fields = redacted_fields self.objects_to_skip = objects_to_skip self._objname_re = re.compile("^.*?'(.*?)'.*?$") def print_obj(self, thrift_obj, file_handle=stdout): """Prints the provided 'thrift_obj' to the provided file handle. If no file handle is specified, then stdout will be used. The 'file_handle' object must have a write(string) method. While this class is specifically targeted to printing Thrift objects, there is no technical limitation preventing any other type of object from being printed. However, the output of non-Thrift objects may not be as nicely formatted.""" # Inputs: # thrift_obj - the object to print out, its attributes will # be walked recursively through the entire # object structure and printed out # file_handle - where the object will be written, defaults to stdout # but can be any object with a write(str) method self._internal_print(thrift_obj, self.base_indent, file_handle) def _internal_print(self, thrift_obj, indent, file_handle): """Recursive function that does the work of walking and printing an object.""" # parse out the type name of the thrift object obj_name = self._objname_re.match(str(type(thrift_obj))) \ .group(1).split(".")[-1] file_handle.write("<{0}>".format(obj_name)) if self.objects_to_skip.count(obj_name): file_handle.write(" - \n") return indent = "{0}{1}".format(indent, self.base_indent) file_handle.write("\n") if obj_name == "list" or obj_name == "tuple": # lists and tuples have to be handled differently # because the vars function does not operate on them for attr_val in thrift_obj: file_handle.write(indent) self._internal_print(attr_val, indent, file_handle) else: # print out simple types first before printing out objects # this ensures the simple types are easier to see child_simple_attrs = {} child_objs = {} for attr_name in vars(thrift_obj): attr_val = getattr(thrift_obj, attr_name) if (hasattr(attr_val, '__dict__') or attr_val is list or attr_val is tuple): child_objs[attr_name] = attr_val else: child_simple_attrs[attr_name] = attr_val # print out child attributes in alphabetical order for child_attr_name in sorted(child_simple_attrs): self._print_attr(child_attr_name, child_simple_attrs[child_attr_name], indent, file_handle) # print out complex types objects, lists, or tuples # in alphabetical order for attr_name in sorted(child_objs): self._print_attr(attr_name, child_objs[attr_name], indent, file_handle) def _print_attr(self, attr_name, attr_val, indent, file_handle): """Handles a single object attribute by either printing out its name/value (for simple types) or recursing down into the object (for objects/lists/tuples).""" file_handle.write(indent) if attr_val is not None and self.redacted_fields.count(attr_name) > 0: file_handle.write("- {0}: *******\n".format(attr_name)) elif attr_val is None: file_handle.write("- {0}: \n".format(attr_name)) elif type(attr_val) is list or type(attr_val) is tuple: file_handle.write("[") self._internal_print(attr_val, indent, file_handle) file_handle.write("{0}]\n".format(indent)) elif hasattr(attr_val, '__dict__'): indent += "{0:{1}} {2}".format("", len(attr_name), self.base_indent) file_handle.write("- {0}: ".format(attr_name)) self._internal_print(attr_val, indent, file_handle) else: file_handle.write("- {0}: ".format(attr_name)) try: str(attr_val).decode("ascii") file_handle.write("{0}".format(attr_val)) except UnicodeDecodeError: # python2 - string contains binary data file_handle.write("") except AttributeError: # python3 - does not require decoding strings and thus falls into this code if isinstance(attr_val, bytes): file_handle.write("") else: file_handle.write("{0}".format(attr_val)) file_handle.write("\n")