converting tableprint from a module to a package

This commit is contained in:
Niru Maheswaranathan
2017-05-25 15:02:14 -07:00
parent d7596f611f
commit 6e94c9e99c
10 changed files with 177 additions and 148 deletions

View File

@@ -14,6 +14,6 @@ test:
nosetests -v --with-coverage --cover-package=tableprint --logging-level=INFO nosetests -v --with-coverage --cover-package=tableprint --logging-level=INFO
clean: clean:
rm -rf tableprint.egg-info rm -R tableprint.egg-info
rm -f *.pyc rm -f tableprint/*.pyc
rm -rf __pycache__ rm -R tableprint/__pycache__/

View File

@@ -52,8 +52,7 @@ setup(
# You can just specify the packages manually here if your project is # You can just specify the packages manually here if your project is
# simple. Or you can use find_packages(). # simple. Or you can use find_packages().
packages=[], packages=['tableprint'],
py_modules=['tableprint'],
# List run-time dependencies here. These will be installed by pip when your # List run-time dependencies here. These will be installed by pip when your
# project is installed. For an analysis of "install_requires" vs pip's # project is installed. For an analysis of "install_requires" vs pip's

8
tableprint/__init__.py Normal file
View File

@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
"""
Tableprint
"""
from .metadata import __author__, __version__
from .printer import *
from .style import *
from .utils import *

View File

@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
# Version info # Version info
__name__ = 'tableprint' __name__ = 'tableprint'
__version__ = '0.6.7' __version__ = '0.6.7'

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Tableprint Table printing
A module to print and display formatted tables of data A module to print and display formatted tables of data
@@ -11,58 +11,18 @@ Usage
>>> tableprint.table(data, headers) >>> tableprint.table(data, headers)
""" """
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
from metadata import __version__
from six import string_types
from collections import namedtuple
from numbers import Number
import sys import sys
import re from numbers import Number
import numpy as np import numpy as np
from six import string_types
__all__ = ('table', 'header', 'row', 'hr', 'top', 'bottom', from .style import LineStyle, STYLES
'banner', 'dataframe', 'humantime', 'styles') from .utils import ansi_len, format_line
__all__ = ('table', 'header', 'row', 'hrule', 'top', 'bottom', 'banner', 'dataframe')
# set up table styles
LineStyle = namedtuple('LineStyle', ('begin', 'hline', 'sep', 'end'))
TableStyle = namedtuple('TableStyle', ('top', 'below_header', 'bottom', 'row'))
styles = {
'grid': TableStyle(
top=LineStyle('+', '-', '+', '+'),
below_header=LineStyle('+', '-', '+', '+'),
bottom=LineStyle('+', '-', '+', '+'),
row=LineStyle('|', '', '|', '|'),
),
'fancy_grid': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle('', '', '', ''),
bottom=LineStyle("", "", "", ""),
row=LineStyle('', '', '', ''),
),
'clean': TableStyle(
top=LineStyle(' ', '', ' ', ' '),
below_header=LineStyle(' ', '', ' ', ' '),
bottom=LineStyle(" ", "", " ", " "),
row=LineStyle(' ', '', ' ', ' '),
),
'round': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle('', '', '', ''),
bottom=LineStyle('', '', '', ''),
row=LineStyle('', '', '', ''),
),
'banner': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle("", "", "", ""),
bottom=LineStyle("", "", "", ""),
row=LineStyle('', '', '', ''),
),
'block': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle(' ', '', '', ' '),
bottom=LineStyle('', '', '', ''),
row=LineStyle(' ', '', ' ', ' '),
),
}
STYLE = 'round' STYLE = 'round'
WIDTH = 11 WIDTH = 11
FMT = '5g' FMT = '5g'
@@ -92,10 +52,10 @@ def table(data, headers=None, format_spec=FMT, width=WIDTH, style=STYLE, out=sys
A file handle or object that has write() and flush() methods (Default: sys.stdout) A file handle or object that has write() and flush() methods (Default: sys.stdout)
""" """
ncols = len(data[0]) if headers is None else len(headers) ncols = len(data[0]) if headers is None else len(headers)
tablestyle = styles[style] tablestyle = STYLES[style]
# Initialize with a hr or the header # Initialize with a hr or the header
tablestr = [hr(ncols, width, tablestyle.top)] \ tablestr = [hrule(ncols, width, tablestyle.top)] \
if headers is None else [header(headers, width, style)] if headers is None else [header(headers, width, style)]
# parse each row # parse each row
@@ -103,7 +63,7 @@ def table(data, headers=None, format_spec=FMT, width=WIDTH, style=STYLE, out=sys
# only add the final border if there was data in the table # only add the final border if there was data in the table
if len(data) > 0: if len(data) > 0:
tablestr += [hr(ncols, width, tablestyle.bottom)] tablestr += [hrule(ncols, width, tablestyle.bottom)]
# print the table # print the table
out.write('\n'.join(tablestr) + '\n') out.write('\n'.join(tablestr) + '\n')
@@ -122,24 +82,24 @@ def header(headers, width=WIDTH, style=STYLE, add_hr=True):
The width of each column (Default: 11) The width of each column (Default: 11)
style : string or tuple, optional style : string or tuple, optional
A formatting style (see styles) A formatting style (see STYLES)
Returns Returns
------- -------
headerstr : string headerstr : string
A string consisting of the full header row to print A string consisting of the full header row to print
""" """
tablestyle = styles[style] tablestyle = STYLES[style]
# string formatter # string formatter
data = map(lambda x: ('{:^%d}' % (width + _ansi_len(x))).format(x), headers) data = map(lambda x: ('{:^%d}' % (width + ansi_len(x))).format(x), headers)
# build the formatted str # build the formatted str
headerstr = _format_line(data, tablestyle.row) headerstr = format_line(data, tablestyle.row)
if add_hr: if add_hr:
upper = hr(len(headers), width, tablestyle.top) upper = hrule(len(headers), width, tablestyle.top)
lower = hr(len(headers), width, tablestyle.below_header) lower = hrule(len(headers), width, tablestyle.below_header)
headerstr = '\n'.join([upper, headerstr, lower]) headerstr = '\n'.join([upper, headerstr, lower])
return headerstr return headerstr
@@ -167,9 +127,9 @@ def row(values, width=WIDTH, format_spec=FMT, style=STYLE):
rowstr : string rowstr : string
A string consisting of the full row of data to print A string consisting of the full row of data to print
""" """
tablestyle = styles[style] tablestyle = STYLES[style]
assert isinstance(format_spec, string_types) | (type(format_spec) is list), \ assert isinstance(format_spec, string_types) | isinstance(format_spec, list), \
"format_spec must be a string or list of strings" "format_spec must be a string or list of strings"
if isinstance(format_spec, string_types): if isinstance(format_spec, string_types):
@@ -182,7 +142,7 @@ def row(values, width=WIDTH, format_spec=FMT, style=STYLE):
datum, prec = val datum, prec = val
if isinstance(datum, string_types): if isinstance(datum, string_types):
return ('{:>%i}' % (width + _ansi_len(datum))).format(datum) return ('{:>%i}' % (width + ansi_len(datum))).format(datum)
elif isinstance(datum, Number): elif isinstance(datum, Number):
return ('{:>%i.%s}' % (width, prec)).format(datum) return ('{:>%i.%s}' % (width, prec)).format(datum)
@@ -194,10 +154,10 @@ def row(values, width=WIDTH, format_spec=FMT, style=STYLE):
data = map(mapdata, zip(values, format_spec)) data = map(mapdata, zip(values, format_spec))
# build the row string # build the row string
return _format_line(data, tablestyle.row) return format_line(data, tablestyle.row)
def hr(n, width=WIDTH, linestyle=LineStyle('', '', '', '')): def hrule(n=1, width=WIDTH, linestyle=LineStyle('', '', '', '')):
"""Returns a formatted string used as a border between table rows """Returns a formatted string used as a border between table rows
Parameters Parameters
@@ -223,12 +183,12 @@ def hr(n, width=WIDTH, linestyle=LineStyle('', '─', '─', '')):
def top(n, width=WIDTH, style=STYLE): def top(n, width=WIDTH, style=STYLE):
"""Prints the top row of a table""" """Prints the top row of a table"""
return hr(n, width, linestyle=styles[style].top) return hrule(n, width, linestyle=STYLES[style].top)
def bottom(n, width=WIDTH, style=STYLE): def bottom(n, width=WIDTH, style=STYLE):
"""Prints the top row of a table""" """Prints the top row of a table"""
return hr(n, width, linestyle=styles[style].bottom) return hrule(n, width, linestyle=STYLES[style].bottom)
def banner(message, width=30, style='banner', out=sys.stdout): def banner(message, width=30, style='banner', out=sys.stdout):
@@ -261,70 +221,3 @@ def dataframe(df, **kwargs):
A pandas DataFrame with the table to print A pandas DataFrame with the table to print
""" """
table(np.array(df), list(df.columns), **kwargs) table(np.array(df), list(df.columns), **kwargs)
def humantime(t):
"""Converts a time in seconds to a reasonable human readable time
Parameters
----------
t : float
The number of seconds
Returns
-------
time : string
The human readable formatted value of the given time
"""
try:
t = float(t)
except (ValueError, TypeError):
raise ValueError("Input must be numeric")
# weeks
if t >= 7*60*60*24:
weeks = np.floor(t / (7.*60.*60.*24.))
timestr = "{:g} weeks, ".format(weeks) + humantime(t % (7*60*60*24))
# days
elif t >= 60*60*24:
days = np.floor(t / (60.*60.*24.))
timestr = "{:g} days, ".format(days) + humantime(t % (60*60*24))
# hours
elif t >= 60*60:
hours = np.floor(t / (60.*60.))
timestr = "{:g} hours, ".format(hours) + humantime(t % (60*60))
# minutes
elif t >= 60:
minutes = np.floor(t / 60.)
timestr = "{:g} min., ".format(minutes) + humantime(t % 60)
# seconds
elif (t >= 1) | (t == 0):
timestr = "{:g} s".format(t)
# milliseconds
elif t >= 1e-3:
timestr = "{:g} ms".format(t*1e3)
# microseconds
elif t >= 1e-6:
timestr = "{:g} \u03BCs".format(t*1e6)
# nanoseconds or smaller
else:
timestr = "{:g} ns".format(t*1e9)
return timestr
def _ansi_len(string):
"""Extra length due to any ANSI sequences in the string."""
return len(string) - len(re.compile(r'\x1b[^m]*m').sub('', string))
def _format_line(data, linestyle):
"""Formats a list of elements using the given line style"""
return linestyle.begin + linestyle.sep.join(data) + linestyle.end

48
tableprint/style.py Normal file
View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
"""
Table styles
"""
from collections import namedtuple
__all__ = ('STYLES', 'LineStyle', 'TableStyle')
LineStyle = namedtuple('LineStyle', ('begin', 'hline', 'sep', 'end'))
TableStyle = namedtuple('TableStyle', ('top', 'below_header', 'bottom', 'row'))
STYLES = {
'grid': TableStyle(
top=LineStyle('+', '-', '+', '+'),
below_header=LineStyle('+', '-', '+', '+'),
bottom=LineStyle('+', '-', '+', '+'),
row=LineStyle('|', '', '|', '|'),
),
'fancy_grid': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle('', '', '', ''),
bottom=LineStyle("", "", "", ""),
row=LineStyle('', '', '', ''),
),
'clean': TableStyle(
top=LineStyle(' ', '', ' ', ' '),
below_header=LineStyle(' ', '', ' ', ' '),
bottom=LineStyle(" ", "", " ", " "),
row=LineStyle(' ', '', ' ', ' '),
),
'round': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle('', '', '', ''),
bottom=LineStyle('', '', '', ''),
row=LineStyle('', '', '', ''),
),
'banner': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle("", "", "", ""),
bottom=LineStyle("", "", "", ""),
row=LineStyle('', '', '', ''),
),
'block': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle(' ', '', '', ' '),
bottom=LineStyle('', '', '', ''),
row=LineStyle(' ', '', ' ', ' '),
),
}

75
tableprint/utils.py Normal file
View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
"""
Tableprint utilities
"""
import re
import numpy as np
__all__ = ('humantime',)
def humantime(time):
"""Converts a time in seconds to a reasonable human readable time
Parameters
----------
t : float
The number of seconds
Returns
-------
time : string
The human readable formatted value of the given time
"""
try:
time = float(time)
except (ValueError, TypeError):
raise ValueError("Input must be numeric")
# weeks
if time >= 7 * 60 * 60 * 24:
weeks = np.floor(time / (7 * 60 * 60 * 24))
timestr = "{:g} weeks, ".format(weeks) + humantime(time % (7 * 60 * 60 * 24))
# days
elif time >= 60 * 60 * 24:
days = np.floor(time / (60 * 60 * 24))
timestr = "{:g} days, ".format(days) + humantime(time % (60 * 60 * 24))
# hours
elif time >= 60 * 60:
hours = np.floor(time / (60 * 60))
timestr = "{:g} hours, ".format(hours) + humantime(time % (60 * 60))
# minutes
elif time >= 60:
minutes = np.floor(time / 60.)
timestr = "{:g} min., ".format(minutes) + humantime(time % 60)
# seconds
elif (time >= 1) | (time == 0):
timestr = "{:g} s".format(time)
# milliseconds
elif time >= 1e-3:
timestr = "{:g} ms".format(time * 1e3)
# microseconds
elif time >= 1e-6:
timestr = "{:g} \u03BCs".format(time * 1e6)
# nanoseconds or smaller
else:
timestr = "{:g} ns".format(time * 1e9)
return timestr
def ansi_len(string):
"""Extra length due to any ANSI sequences in the string."""
return len(string) - len(re.compile(r'\x1b[^m]*m').sub('', string))
def format_line(data, linestyle):
"""Formats a list of elements using the given line style"""
return linestyle.begin + linestyle.sep.join(data) + linestyle.end

View File

@@ -5,6 +5,7 @@ import pytest
def test_borders(): def test_borders():
"""Tests printing of the top and bottom borders"""
# top # top
assert top(5, width=2, style='round') == '╭──┬──┬──┬──┬──╮' assert top(5, width=2, style='round') == '╭──┬──┬──┬──┬──╮'
@@ -16,11 +17,12 @@ def test_borders():
def test_row(): def test_row():
"""Tests printing of a single row of data"""
# valid # valid
assert row("abc", width=3, style='round') == '│ a│ b│ c│' assert row("abc", width=3, style='round') == '│ a│ b│ c│'
assert row([1, 2, 3], width=3, style='clean') == ' 1 2 3 ' assert row([1, 2, 3], width=3, style='clean') == ' 1 2 3 '
# invalid # invalid
with pytest.raises(ValueError) as context: with pytest.raises(ValueError):
row([{}]) row([{}])

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from tableprint import table, banner, dataframe, hr from tableprint import table, banner, dataframe, hrule
from io import StringIO from io import StringIO
import numpy as np import numpy as np
@@ -23,6 +23,7 @@ def test_frame():
def __init__(self, data, headers): def __init__(self, data, headers):
self.data = data self.data = data
self.columns = headers self.columns = headers
def __array__(self): def __array__(self):
return self.data return self.data
@@ -44,8 +45,8 @@ def test_banner():
assert output.getvalue() == '╒═╕\n│!│\n╘═╛\n' assert output.getvalue() == '╒═╕\n│!│\n╘═╛\n'
def test_hr(): def test_hrule():
output = hr(1, width=11) output = hrule(1, width=11)
assert len(output) == 11 assert len(output) == 11
assert '───────────' assert '───────────'

View File

@@ -1,20 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from tableprint import humantime, _format_line, LineStyle from tableprint import humantime, LineStyle
from tableprint.utils import format_line
import pytest import pytest
def test_format_line(): def test_format_line():
# using ASCII # using ASCII
assert _format_line(['foo', 'bar'], LineStyle('(', '_', '+', ')')) == '(foo+bar)' assert format_line(['foo', 'bar'], LineStyle('(', '_', '+', ')')) == '(foo+bar)'
assert _format_line("abc", LineStyle('[', '*', '.', ']')) == '[a.b.c]' assert format_line("abc", LineStyle('[', '*', '.', ']')) == '[a.b.c]'
assert _format_line(["_"], LineStyle('o', '', '!', 'o')) == 'o_o' assert format_line(["_"], LineStyle('o', '', '!', 'o')) == 'o_o'
assert _format_line([], LineStyle(':', '', '', ')')) == ':)' assert format_line([], LineStyle(':', '', '', ')')) == ':)'
# using unicode # using unicode
assert _format_line(['.', '.', '.'], LineStyle('', '_', '', '')) == '★...☆' assert format_line(['.', '.', '.'], LineStyle('', '_', '', '')) == '★...☆'
assert _format_line("☚☛", LineStyle('', '*', '', '')) == '♪☚♩☛♫' assert format_line("☚☛", LineStyle('', '*', '', '')) == '♪☚♩☛♫'
def test_humantime(): def test_humantime():