From 9f679b93e49219fe569ae0596184fe834aa05c09 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Mon, 28 Dec 2015 20:30:02 +0100 Subject: [PATCH 1/7] Do not truncate the query containing the '|' char. pg_view used to serialize both the column name and value into the string, delimiting them by the '|' char, and deserialize them before the output in order to calculate the layout and color of the value and, if necessary, the header. That obvisouly did not work if the string contained '|' characters, i.e. for a query doing string appends. Since there is no need for serialization between the collectors and the output code (they are running on the same system, and even if they were not, there are better ways), the format was changed to the named tuple, containing the header name, value and the position of the header relative to the value. This fixes another problem - for the headers before the values pg_view used to errorneously highlight the header if the value reached the warning or critical threshold. --- pg_view.py | 134 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 97 insertions(+), 37 deletions(-) diff --git a/pg_view.py b/pg_view.py index adf0580..dc0a934 100644 --- a/pg_view.py +++ b/pg_view.py @@ -19,6 +19,7 @@ import time import traceback import json +from collections import namedtuple __appname__ = 'pg_view' __version__ = '1.2.0' @@ -61,6 +62,14 @@ def enum(**enums): return type('Enum', (), enums) +class ColumnType(namedtuple('ColumnType', 'value header align')): + __slots__ = () + + @property + def length(self): + return len(self.value) + (0 if not self.align else len(self.header) + 1) + + COLSTATUS = enum(cs_ok=0, cs_warning=1, cs_critical=2) COLALIGN = enum(ca_none=0, ca_left=1, ca_center=2, ca_right=3) COLTYPES = enum(ct_string=0, ct_number=1) @@ -436,24 +445,24 @@ def curses_cook_value(self, attname, raw_val, output_data): """ return cooked version of the row, with values transformed. A transformation is the same for all columns and depends on the values only. """ - val = raw_val + align = None # change the None output to '' if raw_val is None: - return '' + return ColumnType(value='', header='', align=None) if str(raw_val) == 'True': val = 'T' elif str(raw_val) == 'False': val = 'F' if output_data.get('maxw', 0) > 0 and not self.notrim and len(str(val)) > output_data['maxw']: - # if the value is higher than maximum allowed width - trim it byt removing chars from the middle + # if the value is larger than the maximum allowed width - trim it by removing chars from the middle val = self._trim_text_middle(val, output_data['maxw']) if self.ncurses_custom_fields.get('append_column_headers') or output_data.get('column_header', COHEADER.ch_default) == COHEADER.ch_prepend: - val = '{0}|{1}'.format(attname, val) + align = COHEADER.ch_prepend elif output_data.get('column_header', COHEADER.ch_default) == COHEADER.ch_append: - val = '{0}|{1}'.format(val, attname) - return val + align = COHEADER.ch_append + return ColumnType(value=str(val), header=str(attname), align=align) @staticmethod def _trim_text_middle(val, maxw): @@ -578,7 +587,7 @@ def _get_columns_to_hide(self, result_rows, status_rows): attname = self._produce_output_name(col) empty = True for r in result_rows: - if r[attname] != '': + if r[attname].value != '': empty = False break if empty: @@ -763,7 +772,10 @@ def _calculate_dynamic_width(self, rows, method=OUTPUT_METHOD.console): val = self._produce_output_value(row, col, method) if self.cook_function.get(method): val = self.cook_function[method](attname, val, col) - curw = len(str(val)) + if method == OUTPUT_METHOD.curses: + curw = val.length + else: + curw = len(str(val)) if curw > col['w']: col['w'] = curw if minw > 0: @@ -2354,26 +2366,38 @@ def _status_to_color(self, status, highlight): return self.COLOR_HIGHLIGHT | curses.A_BOLD return self.COLOR_NORMAL - def color_text(self, status_map, highlight, text): + def color_text(self, status_map, highlight, text, header, align): + """ for a given header and text - decide on the position and output color """ result = [] xcol = 0 - # split the text into the header and the rest - f = text.split('|') - if len(f) < 2: - # header is not there - values = f[0] + # 'alignment' is either put the header before the value, or after + # if align is empty, no header is present + if align == COHEADER.ch_prepend: + xcol = self.color_header(header, xcol, result) + # the text might be empty, if it was truncated by truncate_column_value + if text: + self.color_value(text, xcol, status_map, highlight, result) + elif align == COHEADER.ch_append: + xcol = self.color_value(text, xcol, status_map, highlight, result) + # ditto for the header + if header: + self.color_header(header, xcol, result) else: - # add header with a normal color - xcol += len(f[0]) - result.append({ - 'start': 0, - 'width': xcol, - 'word': f[0], - 'color': self.COLOR_NORMAL, - }) - values = f[1] - # add extra space between the header and the values - xcol += 1 + self.color_value(text, 0, status_map, highlight, result) + return result + + def color_header(self, header, xcol, result): + """ add a header outout information""" + result.append({ + 'start': xcol, + 'width': len(header), + 'word': header, + 'color': self.COLOR_NORMAL, + }) + return xcol + len(header) + 1 + + def color_value(self, val, xcol, status_map, highlight, result): + """ add a text optut information """ # status format: field_no -> color # if the status field contain a single value of -1 - just # highlight everything without splitting the text into words @@ -2382,12 +2406,16 @@ def color_text(self, status_map, highlight, text): color = self._status_to_color(status_map[-1], highlight) result.append({ 'start': xcol, - 'word': values, - 'width': len(values), + 'word': val, + 'width': len(val), 'color': color, }) + xcol += (len(val) + 1) else: - words = list(re.finditer(r'(\S+)', values)) + # XXX: we are calculating the world boundaries again here + # (first one in calculate_output_status) and using a different method to do so. + words = list(re.finditer(r'(\S+)', val)) + last_position = xcol for no, word in enumerate(words): if no in status_map: status = status_map[no] @@ -2406,7 +2434,9 @@ def color_text(self, status_map, highlight, text): 'width': word_len, 'color': color, }) - return result + last_position = xcol + word.end(0) + xcol += (last_position + 1) + return xcol def help(self): y = 0 @@ -2488,19 +2518,49 @@ def show_collector_data(self, collector, clock=False): # now check if we need to add ellipsis to indicate that the value has been truncated. # we don't do this if the value is less than a certain length or when the column is marked as # containing truncated values, but the actual value is not truncated. + if layout[field].get('truncate', False) \ and w > self.MIN_ELLIPSIS_FIELD_LENGTH \ - and w < len(str(row[field])): - text = str(row[field])[:w - 3] + '...' + and w < row[field].length: + # XXX: recheck + header, text = self.truncate_column_value(row[field], w) else: - text = str(row[field])[:w] - text = self._align_field(text, w, column_alignment, types.get(field, COLTYPES.ct_string)) - color_fields = self.color_text(status[field], highlights[field], text) + header, text = self.truncate_column_value(row[field], w, False) + text = self._align_field(text, header, w, column_alignment, types.get(field, COLTYPES.ct_string)) + color_fields = self.color_text(status[field], highlights[field], text, header, row[field].align) for f in color_fields: self.screen.addnstr(self.next_y, layout[field]['start'] + f['start'], f['word'], f['width'], f['color']) self.next_y += 1 + def truncate_column_value(self, cv, maxlen, ellipsis=True): + """ make sure that a pair of header and value fits into the allocated field length """ + value = cv.value + header = cv.header + align = cv.align + h_len = len(header) + v_len = len(value) + maxlen = (maxlen - 3) if ellipsis else maxlen + if align: + if align == COHEADER.ch_prepend: + if h_len + 1 >= maxlen: + # prepend the header, consider if we have to truncate the header and omit the value altogether + header = header[:maxlen] + (' ' if maxlen == h_len + 1 else '') + ('...' if ellipsis else '') + value = '' + else: + value = value[:(maxlen - h_len - 1)] + ('...' if ellipsis else '') + elif align == COHEADER.ch_append: + if v_len + 1 >= maxlen: + # prepend the value, consider if we have to truncate it and omit the header altogether + value = value[:maxlen] + (' ' if maxlen == v_len + 1 else '') + ('...' if ellipsis else '') + header = '' + else: + header = header[:(maxlen - v_len - 1)] + ('...' if ellipsis else '') + else: + value = value[:maxlen] + ('...' if ellipsis else '') + header = '' + return header, value + def display_prefix(self, collector, header): prefix = self.data[collector]['prefix'] if prefix: @@ -2524,7 +2584,7 @@ def display_prefix(self, collector, header): def display_header(self, layout, align, types): for field in layout: - text = self._align_field(field, layout[field]['width'], align.get(field, COLALIGN.ca_none), + text = self._align_field(field, '', layout[field]['width'], align.get(field, COLALIGN.ca_none), types.get(field, COLTYPES.ct_string)) self.screen.addnstr(self.next_y, layout[field]['start'], text, layout[field]['width'], self.COLOR_NORMAL | curses.A_BOLD) @@ -2549,13 +2609,13 @@ def show_status_of_invisible_fields(self, layout, status, xstart): self.screen.addch(self.next_y, 0, ' ', color_rest) @staticmethod - def _align_field(text, width, align, typ): + def _align_field(text, header, width, align, typ): if align == COLALIGN.ca_none: if typ == COLTYPES.ct_number: align = COLALIGN.ca_right else: align = COLALIGN.ca_left - textlen = len(text) + textlen = len(text) + len(header) + (1 if header and text else 0) width_left = width - textlen if align == COLALIGN.ca_right: return '{0}{1}'.format(' ' * width_left, text) From d07d6d4112558dcbcc0693bebf9d5d8c6b7f6fca Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Tue, 29 Dec 2015 10:57:28 +0100 Subject: [PATCH 2/7] Rename some variables to avoid confusion, no funcitonal changes. --- pg_view.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/pg_view.py b/pg_view.py index dc0a934..2ca598d 100644 --- a/pg_view.py +++ b/pg_view.py @@ -62,18 +62,18 @@ def enum(**enums): return type('Enum', (), enums) -class ColumnType(namedtuple('ColumnType', 'value header align')): +class ColumnType(namedtuple('ColumnType', 'value header header_position')): __slots__ = () @property def length(self): - return len(self.value) + (0 if not self.align else len(self.header) + 1) + return len(self.value) + (0 if not self.header_position else len(self.header) + 1) COLSTATUS = enum(cs_ok=0, cs_warning=1, cs_critical=2) COLALIGN = enum(ca_none=0, ca_left=1, ca_center=2, ca_right=3) COLTYPES = enum(ct_string=0, ct_number=1) -COHEADER = enum(ch_default=0, ch_prepend=1, ch_append=2) +COLHEADER = enum(ch_default=0, ch_prepend=1, ch_append=2) OUTPUT_METHOD = enum(console='console', json='json', curses='curses') STAT_FIELD = enum(st_pid=0, st_process_name=1, st_state=2, st_ppid=3, st_start_time=21) @@ -164,7 +164,7 @@ class StatCollector(object): 'noautohide': False, 'w': 0, 'align': COLALIGN.ca_none, - 'column_header': COHEADER.ch_default, + 'column_header': COLHEADER.ch_default, } NCURSES_CUSTOM_OUTPUT_FIELDS = ['header', 'prefix', 'append_column_headers'] @@ -446,10 +446,10 @@ def curses_cook_value(self, attname, raw_val, output_data): the same for all columns and depends on the values only. """ val = raw_val - align = None + header_position = None # change the None output to '' if raw_val is None: - return ColumnType(value='', header='', align=None) + return ColumnType(value='', header='', header_position=None) if str(raw_val) == 'True': val = 'T' elif str(raw_val) == 'False': @@ -458,11 +458,11 @@ def curses_cook_value(self, attname, raw_val, output_data): # if the value is larger than the maximum allowed width - trim it by removing chars from the middle val = self._trim_text_middle(val, output_data['maxw']) if self.ncurses_custom_fields.get('append_column_headers') or output_data.get('column_header', - COHEADER.ch_default) == COHEADER.ch_prepend: - align = COHEADER.ch_prepend - elif output_data.get('column_header', COHEADER.ch_default) == COHEADER.ch_append: - align = COHEADER.ch_append - return ColumnType(value=str(val), header=str(attname), align=align) + COLHEADER.ch_default) == COLHEADER.ch_prepend: + header_position = COLHEADER.ch_prepend + elif output_data.get('column_header', COLHEADER.ch_default) == COLHEADER.ch_append: + header_position = COLHEADER.ch_append + return ColumnType(value=str(val), header=str(attname), header_position=header_position) @staticmethod def _trim_text_middle(val, maxw): @@ -2062,7 +2062,7 @@ def __init__(self): 'noautohide': True, 'warning': 5, 'critical': 20, - 'column_header': COHEADER.ch_prepend, + 'column_header': COLHEADER.ch_prepend, 'status_fn': self._load_avg_state, }, { @@ -2070,7 +2070,7 @@ def __init__(self): 'in': 'uptime', 'pos': 1, 'noautohide': True, - 'column_header': COHEADER.ch_prepend, + 'column_header': COLHEADER.ch_prepend, }, { 'out': 'host', @@ -2083,7 +2083,7 @@ def __init__(self): 'out': 'cores', 'pos': 2, 'noautohide': True, - 'column_header': COHEADER.ch_append, + 'column_header': COLHEADER.ch_append, }, { 'out': 'name', @@ -2366,18 +2366,18 @@ def _status_to_color(self, status, highlight): return self.COLOR_HIGHLIGHT | curses.A_BOLD return self.COLOR_NORMAL - def color_text(self, status_map, highlight, text, header, align): + def color_text(self, status_map, highlight, text, header, header_position): """ for a given header and text - decide on the position and output color """ result = [] xcol = 0 - # 'alignment' is either put the header before the value, or after - # if align is empty, no header is present - if align == COHEADER.ch_prepend: + # header_position is either put the header before the value, or after + # if header_position is empty, no header is present + if header_position == COLHEADER.ch_prepend: xcol = self.color_header(header, xcol, result) # the text might be empty, if it was truncated by truncate_column_value if text: self.color_value(text, xcol, status_map, highlight, result) - elif align == COHEADER.ch_append: + elif header_position == COLHEADER.ch_append: xcol = self.color_value(text, xcol, status_map, highlight, result) # ditto for the header if header: @@ -2522,12 +2522,12 @@ def show_collector_data(self, collector, clock=False): if layout[field].get('truncate', False) \ and w > self.MIN_ELLIPSIS_FIELD_LENGTH \ and w < row[field].length: - # XXX: recheck + # XXX: why do we truncate even when truncate for the column is set to False? header, text = self.truncate_column_value(row[field], w) else: header, text = self.truncate_column_value(row[field], w, False) text = self._align_field(text, header, w, column_alignment, types.get(field, COLTYPES.ct_string)) - color_fields = self.color_text(status[field], highlights[field], text, header, row[field].align) + color_fields = self.color_text(status[field], highlights[field], text, header, row[field].header_position) for f in color_fields: self.screen.addnstr(self.next_y, layout[field]['start'] + f['start'], f['word'], f['width'], f['color']) @@ -2537,19 +2537,19 @@ def truncate_column_value(self, cv, maxlen, ellipsis=True): """ make sure that a pair of header and value fits into the allocated field length """ value = cv.value header = cv.header - align = cv.align + header_position = cv.header_position h_len = len(header) v_len = len(value) maxlen = (maxlen - 3) if ellipsis else maxlen - if align: - if align == COHEADER.ch_prepend: + if header_position: + if header_position == COLHEADER.ch_prepend: if h_len + 1 >= maxlen: # prepend the header, consider if we have to truncate the header and omit the value altogether header = header[:maxlen] + (' ' if maxlen == h_len + 1 else '') + ('...' if ellipsis else '') value = '' else: value = value[:(maxlen - h_len - 1)] + ('...' if ellipsis else '') - elif align == COHEADER.ch_append: + elif header_position == COLHEADER.ch_append: if v_len + 1 >= maxlen: # prepend the value, consider if we have to truncate it and omit the header altogether value = value[:maxlen] + (' ' if maxlen == v_len + 1 else '') + ('...' if ellipsis else '') From 85cc489122cb77f953f561563bbf93cc192b40c1 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Tue, 29 Dec 2015 14:36:37 +0100 Subject: [PATCH 3/7] Do not call truncate in ncurses out if not needed. Layout calculation marks some attributes as subjects for truncation. Only call the truncate code on them. Initialize the header to empty string if we don't need to include it along the attribute value. --- pg_view.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pg_view.py b/pg_view.py index 2ca598d..3d513c2 100644 --- a/pg_view.py +++ b/pg_view.py @@ -446,7 +446,7 @@ def curses_cook_value(self, attname, raw_val, output_data): the same for all columns and depends on the values only. """ val = raw_val - header_position = None + header = str(attname) # change the None output to '' if raw_val is None: return ColumnType(value='', header='', header_position=None) @@ -462,7 +462,10 @@ def curses_cook_value(self, attname, raw_val, output_data): header_position = COLHEADER.ch_prepend elif output_data.get('column_header', COLHEADER.ch_default) == COLHEADER.ch_append: header_position = COLHEADER.ch_append - return ColumnType(value=str(val), header=str(attname), header_position=header_position) + else: + header = '' + header_position = None + return ColumnType(value=str(val), header=header, header_position=header_position) @staticmethod def _trim_text_middle(val, maxw): @@ -2519,13 +2522,11 @@ def show_collector_data(self, collector, clock=False): # we don't do this if the value is less than a certain length or when the column is marked as # containing truncated values, but the actual value is not truncated. - if layout[field].get('truncate', False) \ - and w > self.MIN_ELLIPSIS_FIELD_LENGTH \ - and w < row[field].length: + if layout[field].get('truncate', False): # XXX: why do we truncate even when truncate for the column is set to False? - header, text = self.truncate_column_value(row[field], w) + header, text = self.truncate_column_value(row[field], w, (w > self.MIN_ELLIPSIS_FIELD_LENGTH)) else: - header, text = self.truncate_column_value(row[field], w, False) + header, text = row[field].header, row[field].value text = self._align_field(text, header, w, column_alignment, types.get(field, COLTYPES.ct_string)) color_fields = self.color_text(status[field], highlights[field], text, header, row[field].header_position) for f in color_fields: @@ -2557,8 +2558,8 @@ def truncate_column_value(self, cv, maxlen, ellipsis=True): else: header = header[:(maxlen - v_len - 1)] + ('...' if ellipsis else '') else: + # header is set to '' by the collector value = value[:maxlen] + ('...' if ellipsis else '') - header = '' return header, value def display_prefix(self, collector, header): From 2b5ed93942bf995b96949a8034a91e72b48ce457 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Tue, 29 Dec 2015 14:48:56 +0100 Subject: [PATCH 4/7] Correct a wrongly named property. No functional changes. --- pg_view.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pg_view.py b/pg_view.py index 3d513c2..e80672c 100644 --- a/pg_view.py +++ b/pg_view.py @@ -167,7 +167,7 @@ class StatCollector(object): 'column_header': COLHEADER.ch_default, } - NCURSES_CUSTOM_OUTPUT_FIELDS = ['header', 'prefix', 'append_column_headers'] + NCURSES_CUSTOM_OUTPUT_FIELDS = ['header', 'prefix', 'prepend_column_headers'] def __init__(self, ticks_per_refresh=1, produce_diffs=True): self.rows_prev = [] @@ -457,7 +457,7 @@ def curses_cook_value(self, attname, raw_val, output_data): if output_data.get('maxw', 0) > 0 and not self.notrim and len(str(val)) > output_data['maxw']: # if the value is larger than the maximum allowed width - trim it by removing chars from the middle val = self._trim_text_middle(val, output_data['maxw']) - if self.ncurses_custom_fields.get('append_column_headers') or output_data.get('column_header', + if self.ncurses_custom_fields.get('prepend_column_headers') or output_data.get('column_header', COLHEADER.ch_default) == COLHEADER.ch_prepend: header_position = COLHEADER.ch_prepend elif output_data.get('column_header', COLHEADER.ch_default) == COLHEADER.ch_append: @@ -765,7 +765,7 @@ def _calculate_dynamic_width(self, rows, method=OUTPUT_METHOD.console): minw = col.get('minw', 0) attname = self._produce_output_name(col) # XXX: if append_column_header, min width should include the size of the attribut name - if method == OUTPUT_METHOD.curses and self.ncurses_custom_fields.get('append_column_headers'): + if method == OUTPUT_METHOD.curses and self.ncurses_custom_fields.get('prepend_column_headers'): minw += len(attname) + 1 col['w'] = len(attname) # use cooked values @@ -1643,7 +1643,7 @@ def __init__(self): self.previos_total_cpu_time = 0 self.current_total_cpu_time = 0 self.cpu_time_diff = 0 - self.ncurses_custom_fields = {'header': False, 'prefix': 'sys: ', 'append_column_headers': True} + self.ncurses_custom_fields = {'header': False, 'prefix': 'sys: ', 'prepend_column_headers': True} self.postinit() @@ -1991,7 +1991,7 @@ def __init__(self): }, ] - self.ncurses_custom_fields = {'header': False, 'prefix': 'mem: ', 'append_column_headers': True} + self.ncurses_custom_fields = {'header': False, 'prefix': 'mem: ', 'prepend_column_headers': True} self.postinit() @@ -2096,7 +2096,7 @@ def __init__(self): }, ] - self.ncurses_custom_fields = {'header': False, 'prefix': None, 'append_column_headers': False} + self.ncurses_custom_fields = {'header': False, 'prefix': None, 'prepend_column_headers': False} self.postinit() @@ -2482,7 +2482,7 @@ def show_collector_data(self, collector, clock=False): statuses = self.data[collector]['statuses'] align = self.data[collector]['align'] header = self.data[collector].get('header', False) or False - append_column_headers = self.data[collector].get('append_column_headers', False) + prepend_column_headers = self.data[collector].get('prepend_column_headers', False) highlights = self.data[collector]['highlights'] types = self.data[collector]['types'] @@ -2516,7 +2516,7 @@ def show_collector_data(self, collector, clock=False): for field in layout: # calculate colors and alignment for the data value column_alignment = (align.get(field, - COLALIGN.ca_none) if not append_column_headers else COLALIGN.ca_left) + COLALIGN.ca_none) if not prepend_column_headers else COLALIGN.ca_left) w = layout[field]['width'] # now check if we need to add ellipsis to indicate that the value has been truncated. # we don't do this if the value is less than a certain length or when the column is marked as From 5abbf4abb60b89475532dcc88a305db63f0f0dcb Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Tue, 29 Dec 2015 15:25:13 +0100 Subject: [PATCH 5/7] Move the Linux check to not prevent flake8 run. --- pg_view.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pg_view.py b/pg_view.py index e80672c..0e1c889 100644 --- a/pg_view.py +++ b/pg_view.py @@ -50,11 +50,6 @@ print('Unable to import ncurses, curses output will be unavailable') curses_available = False -# bail out if we are not running Linux -if platform.system() != 'Linux': - print('Non Linux database hosts are not supported at the moment. Can not continue') - sys.exit(243) - # enum emulation @@ -2528,7 +2523,8 @@ def show_collector_data(self, collector, clock=False): else: header, text = row[field].header, row[field].value text = self._align_field(text, header, w, column_alignment, types.get(field, COLTYPES.ct_string)) - color_fields = self.color_text(status[field], highlights[field], text, header, row[field].header_position) + color_fields = self.color_text(status[field], highlights[field], + text, header, row[field].header_position) for f in color_fields: self.screen.addnstr(self.next_y, layout[field]['start'] + f['start'], f['word'], f['width'], f['color']) @@ -3309,6 +3305,11 @@ def parse_single_line(self, inode): def main(): global TICK_LENGTH, logger, options + # bail out if we are not running Linux + if platform.system() != 'Linux': + print('Non Linux database hosts are not supported at the moment. Can not continue') + sys.exit(243) + if not psycopg2_available: print('Unable to import psycopg2 module, please, install it (python-psycopg2). Can not continue') sys.exit(254) From 10b1443e30ab5aa7eb0e8f9d951e7c7e755f9a1d Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Tue, 29 Dec 2015 15:50:49 +0100 Subject: [PATCH 6/7] Avoid tests to fail because of lack of cov import. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index e9da0ca..51dea0f 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,6 @@ def run_tests(self): params = {'args': self.test_args} if self.cov: params['args'] += self.cov - params['plugins'] = ['cov'] params['args'] += ['--doctest-modules', MAIN_MODULE + '.py', '-s', '-vv'] errno = pytest.main(**params) sys.exit(errno) From add65ee09224a18e2c0bf646c018f867f9b7f6fa Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Tue, 29 Dec 2015 15:59:20 +0100 Subject: [PATCH 7/7] Avoid crashing with python3 because of non-integer array slice. --- pg_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_view.py b/pg_view.py index 0e1c889..729c209 100644 --- a/pg_view.py +++ b/pg_view.py @@ -468,7 +468,7 @@ def _trim_text_middle(val, maxw): This kind of trimming seems to be better than tail trimming for user and database names. """ - half = (maxw - 2) / 2 + half = int((maxw - 2) / 2) return val[:half] + '..' + val[-half:] def _do_refresh(self, new_rows):