Skip to content

Commit ca72aee

Browse files
committed
Added example that exercises all of the column customizations. Fixed some bugs in handling row objects with object formatter functions.
1 parent 4a8ec64 commit ca72aee

File tree

2 files changed

+159
-3
lines changed

2 files changed

+159
-3
lines changed

examples/columns.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python
2+
# coding=utf-8
3+
"""
4+
Demonstration of all of the per-Column customizations that are available.
5+
"""
6+
import tableformatter as tf
7+
8+
9+
class MyRowObject(object):
10+
"""Simple object to demonstrate using a list of objects with TableFormatter"""
11+
def __init__(self, field1: str, field2: str, field3: int, field4: int):
12+
self.field1 = field1
13+
self.field2 = field2
14+
self._field3 = field3
15+
self.field4 = field4
16+
17+
def get_field3(self):
18+
"""Demonstrates accessing object functions"""
19+
return self._field3
20+
21+
22+
def multiply(row_obj: MyRowObject):
23+
"""Demonstrates an object formatter function"""
24+
return str(row_obj.get_field3() * row_obj.field4)
25+
26+
27+
def int2word(num, separator="-"):
28+
"""Demonstrates a field formatter function
29+
From: https://codereview.stackexchange.com/questions/156590/create-the-english-word-for-a-number
30+
"""
31+
ones_and_teens = {0: "Zero", 1: 'One', 2: 'Two', 3: 'Three',
32+
4: 'Four', 5: 'Five', 6: 'Six', 7: 'Seven',
33+
8: 'Eight', 9: 'Nine', 10: 'Ten', 11: 'Eleven',
34+
12: 'Twelve', 13: 'Thirteen', 14: 'Fourteen',
35+
15: 'Fifteen', 16: 'Sixteen', 17: 'Seventeen',
36+
18: 'Eighteen', 19: 'Nineteen'}
37+
twenty2ninety = {2: 'Twenty', 3: 'Thirty', 4: 'Forty', 5: 'Fifty',
38+
6: 'Sixty', 7: 'Seventy', 8: 'Eighty', 9: 'Ninety', 0: ""}
39+
40+
if 0 <= num < 19:
41+
return ones_and_teens[num]
42+
elif 20 <= num <= 99:
43+
tens, below_ten = divmod(num, 10)
44+
if below_ten > 0:
45+
words = twenty2ninety[tens] + separator + \
46+
ones_and_teens[below_ten].lower()
47+
else:
48+
words = twenty2ninety[tens]
49+
return words
50+
51+
elif 100 <= num <= 999:
52+
hundreds, below_hundred = divmod(num, 100)
53+
tens, below_ten = divmod(below_hundred, 10)
54+
if below_hundred == 0:
55+
words = ones_and_teens[hundreds] + separator + "hundred"
56+
elif below_ten == 0:
57+
words = ones_and_teens[hundreds] + separator + \
58+
"hundred" + separator + twenty2ninety[tens].lower()
59+
else:
60+
if tens > 0:
61+
words = ones_and_teens[hundreds] + separator + "hundred" + separator + twenty2ninety[
62+
tens].lower() + separator + ones_and_teens[below_ten].lower()
63+
else:
64+
words = ones_and_teens[
65+
hundreds] + separator + "hundred" + separator + ones_and_teens[below_ten].lower()
66+
return words
67+
68+
else:
69+
print("num out of range")
70+
71+
72+
rows = [MyRowObject('Longer text that will trigger the column wrapping', 'A2', 5, 56),
73+
MyRowObject('B1', 'B2\nB2\nB2', 23, 8),
74+
MyRowObject('C1', 'C2', 4, 9),
75+
MyRowObject('D1', 'D2', 7, 5)]
76+
77+
78+
columns = (tf.Column('First', width=20, attrib='field1'),
79+
tf.Column('Second', attrib='field2'),
80+
tf.Column('Num 1', width=3, attrib='get_field3'),
81+
tf.Column('Num 2', attrib='field4'),
82+
tf.Column('Multiplied', obj_formatter=multiply))
83+
print("First: Wrapped\nMultiplied: object formatter")
84+
print(tf.generate_table(rows, columns))
85+
86+
87+
columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.WRAP_WITH_INDENT),
88+
tf.Column('Second', attrib='field2'),
89+
tf.Column('Num 1', width=3, attrib='get_field3', header_halign=tf.ColumnAlignment.AlignCenter),
90+
tf.Column('Num 2', attrib='field4'),
91+
tf.Column('Multiplied', attrib=None, obj_formatter=multiply))
92+
print("First: Wrapped with indent\nNum 1: header align center")
93+
print(tf.generate_table(rows, columns))
94+
95+
96+
columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.WRAP_WITH_INDENT,
97+
wrap_prefix='>>> '),
98+
tf.Column('Second', attrib='field2', cell_halign=tf.ColumnAlignment.AlignCenter),
99+
tf.Column('Num 1', width=3, attrib='get_field3', header_halign=tf.ColumnAlignment.AlignRight),
100+
tf.Column('Num 2', attrib='field4', header_valign=tf.ColumnAlignment.AlignTop),
101+
tf.Column('Multiplied', attrib=None, obj_formatter=multiply))
102+
print("First: Wrapped with indent, custom wrap prefix\n"
103+
"Second: Header align center\n"
104+
"Num 1: header align right\n"
105+
"Num 2: header align top")
106+
print(tf.generate_table(rows, columns))
107+
108+
109+
columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_END),
110+
tf.Column('Second', attrib='field2', cell_padding=3),
111+
tf.Column('Num 1', width=3, attrib='get_field3'),
112+
tf.Column('Num 2', attrib='field4'),
113+
tf.Column('Multiplied', attrib=None, obj_formatter=multiply))
114+
print("First: Truncate end\n"
115+
"Second: cell padding 3 spaces")
116+
print(tf.generate_table(rows, columns))
117+
118+
119+
columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_FRONT),
120+
tf.Column('Second', attrib='field2', cell_padding=5, cell_halign=tf.ColumnAlignment.AlignRight),
121+
tf.Column('Num 1', attrib='get_field3'),
122+
tf.Column('Num 2', attrib='field4'),
123+
tf.Column('Multiplied', attrib=None, obj_formatter=multiply))
124+
125+
126+
print("First; Truncate Front\n"
127+
"Second: cell align right, cell padding=5")
128+
print(tf.generate_table(rows, columns))
129+
130+
columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_MIDDLE),
131+
tf.Column('Second', attrib='field2'),
132+
tf.Column('Num 1', attrib='get_field3'),
133+
tf.Column('Num 2', attrib='field4', cell_valign=tf.ColumnAlignment.AlignBottom),
134+
tf.Column('Multiplied', attrib=None, obj_formatter=multiply))
135+
136+
137+
print("First: Truncate Middle\nNum 2: cell align bottom")
138+
print(tf.generate_table(rows, columns))
139+
140+
141+
columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_HARD),
142+
tf.Column('Second', attrib='field2'),
143+
tf.Column('Num 1', attrib='get_field3'),
144+
tf.Column('Num 2', attrib='field4', formatter=int2word),
145+
tf.Column('Multiplied', attrib=None, obj_formatter=multiply))
146+
147+
148+
print("First: Truncate Hard\nNum 2: Field formatter")
149+
print(tf.generate_table(rows, columns))

tableformatter.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ def set_default_grid(grid: Grid) -> None:
587587
DEFAULT_GRID = grid
588588

589589

590-
def generate_table(rows: Iterable[Iterable], columns: Collection[Union[str, Tuple[str, dict]]]=None,
590+
def generate_table(rows: Iterable[Union[Iterable, object]], columns: Collection[Union[str, Tuple[str, dict]]]=None,
591591
grid_style: Optional[Grid]=None, transpose: bool=False) -> str:
592592
"""Convenience function to easily generate a table from rows/columns"""
593593
show_headers = True
@@ -599,9 +599,15 @@ def generate_table(rows: Iterable[Iterable], columns: Collection[Union[str, Tupl
599599
for column in columns:
600600
if isinstance(column, tuple) and len(column) > 1 and isinstance(column[1], dict):
601601
if TableFormatter.COL_OPT_ATTRIB_NAME in column[1].keys():
602+
# Does this column specify an object attribute to use?
602603
attrib = column[1][TableFormatter.COL_OPT_ATTRIB_NAME]
603604
if isinstance(attrib, str) and len(attrib) > 0:
604605
attrib_count += 1
606+
elif TableFormatter.COL_OPT_OBJECT_FORMATTER in column[1].keys():
607+
# If no column attribute, does this column have an object formatter?
608+
func = column[1][TableFormatter.COL_OPT_OBJECT_FORMATTER]
609+
if callable(func):
610+
attrib_count += 1
605611
if attrib_count == len(columns):
606612
use_attrib = True
607613

@@ -756,7 +762,8 @@ def __init__(self,
756762
if use_attribs:
757763
for col_index, attrib in enumerate(self._column_attribs):
758764
if attrib is None:
759-
raise ValueError('Attribute name is required for {}'.format(self._column_names[col_index]))
765+
if TableFormatter.COL_OPT_OBJECT_FORMATTER not in self._column_opts[col_index]:
766+
raise ValueError('Attribute name or Object formatter is required for {}'.format(self._column_names[col_index]))
760767

761768
def set_default_header_alignment(self,
762769
horiz_align: ColumnAlignment = ColumnAlignment.AlignLeft,
@@ -881,7 +888,7 @@ def generate_table(self, entries: List[Iterable], force_transpose=False):
881888

882889
for column_index, attrib_name in enumerate(self._column_attribs):
883890
field_obj = None
884-
if hasattr(entry_obj, attrib_name):
891+
if isinstance(attrib_name, str) and hasattr(entry_obj, attrib_name):
885892
field_obj = getattr(entry_obj, attrib_name, '')
886893
# if the object attribute is callable, go ahead and call it and get the result
887894
if callable(field_obj):

0 commit comments

Comments
 (0)