diff --git a/.gitignore b/.gitignore index ab599fc..d90d325 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ dist .coverage .cover/* +/.venv/ diff --git a/prettytable/__init__.py b/prettytable/__init__.py index bef5807..0b24e56 100644 --- a/prettytable/__init__.py +++ b/prettytable/__init__.py @@ -34,4 +34,4 @@ from .prettytable import PrettyTable from .prettytable import ALL, HEADER, MSWORD_FRIENDLY, NONE, PLAIN_COLUMNS -from .factory import from_csv, from_db_cursor, from_html, from_html_one +from .factory import from_csv, from_json, from_db_cursor, from_html, from_html_one diff --git a/prettytable/factory.py b/prettytable/factory.py index 9e7d742..b8611d9 100644 --- a/prettytable/factory.py +++ b/prettytable/factory.py @@ -2,6 +2,7 @@ Table factories """ import csv +import json from .prettytable import PrettyTable from ._compact import py3k, HTMLParser @@ -34,6 +35,28 @@ def from_csv(fp, field_names=None, **kwargs): return table +def from_json(fp, field_names=None, **kwargs): + reader = json.load(fp) + + table = PrettyTable(**kwargs) + table_field_names = [] + if field_names: + table_field_names.extend(field_names) + else: + for row in reader: + table_field_names.extend([x for x in row.keys() if x not in table_field_names]) + + table.field_names = table_field_names + + for row in reader: + d = {k: "" for k in table_field_names} + d.update(row) + add_row = [x for x in d.values()] + table.add_row(add_row) + + return table + + def from_db_cursor(cursor, **kwargs): if cursor.description: table = PrettyTable(**kwargs) diff --git a/prettytable/prettytable.py b/prettytable/prettytable.py index 02dab66..e0e0f2d 100644 --- a/prettytable/prettytable.py +++ b/prettytable/prettytable.py @@ -421,7 +421,7 @@ def align(self): def align(self, val): if not self._field_names: self._align = {} - elif val is None or (isinstance(val, dict) and len(val) is 0): + elif val is None or (isinstance(val, dict) and len(val) == 0): for field in self._field_names: self._align[field] = "c" else: @@ -441,7 +441,7 @@ def valign(self): def valign(self, val): if not self._field_names: self._valign = {} - elif val is None or (isinstance(val, dict) and len(val) is 0): + elif val is None or (isinstance(val, dict) and len(val) == 0): for field in self._field_names: self._valign[field] = "t" else: @@ -459,7 +459,7 @@ def max_width(self): @max_width.setter def max_width(self, val): - if val is None or (isinstance(val, dict) and len(val) is 0): + if val is None or (isinstance(val, dict) and len(val) == 0): self._max_width = {} else: self._validate_option("max_width", val) @@ -489,7 +489,7 @@ def min_width(self): @min_width.setter def min_width(self, val): - if val is None or (isinstance(val, dict) and len(val) is 0): + if val is None or (isinstance(val, dict) and len(val) == 0): self._min_width = {} else: self._validate_option("min_width", val) @@ -687,7 +687,7 @@ def int_format(self): @int_format.setter def int_format(self, val): - if val is None or (isinstance(val, dict) and len(val) is 0): + if val is None or (isinstance(val, dict) and len(val) == 0): self._int_format = {} else: self._validate_option("int_format", val) @@ -704,7 +704,7 @@ def float_format(self): @float_format.setter def float_format(self, val): - if val is None or (isinstance(val, dict) and len(val) is 0): + if val is None or (isinstance(val, dict) and len(val) == 0): self._float_format = {} else: self._validate_option("float_format", val) @@ -1295,7 +1295,7 @@ def _stringify_header(self, options): fieldname = field.lower() else: fieldname = field - bits.append(" " * lpad + self._justify(fieldname, width, self._align[field]) + " " * rpad) + bits.append(" " * lpad + self._justify(fieldname, width, self._align.get(field, 'c')) + " " * rpad) if options["border"]: if options["vrules"] == ALL: bits.append(options["vertical_char"]) @@ -1343,7 +1343,7 @@ def _stringify_row(self, row, options): for field, value, width, in zip(self._field_names, row, self._widths): - valign = self._valign[field] + valign = self._valign.get(field, 'm') lines = value.split("\n") dHeight = row_height - len(lines) if dHeight: @@ -1359,7 +1359,7 @@ def _stringify_row(self, row, options): if options["fields"] and field not in options["fields"]: continue - bits[y].append(" " * lpad + self._justify(l, width, self._align[field]) + " " * rpad) + bits[y].append(" " * lpad + self._justify(l, width, self._align.get(field, 'c')) + " " * rpad) if options["border"]: if options["vrules"] == ALL: bits[y].append(self.vertical_char) diff --git a/tests/test_prettytable.py b/tests/test_prettytable.py index 064e2b0..05bd427 100644 --- a/tests/test_prettytable.py +++ b/tests/test_prettytable.py @@ -3,7 +3,7 @@ from prettytable import PrettyTable from prettytable import ALL, HEADER, MSWORD_FRIENDLY, NONE -from prettytable import from_csv, from_db_cursor, from_html, from_html_one +from prettytable import from_csv, from_json, from_db_cursor, from_html, from_html_one from prettytable._compact import StringIO try: @@ -64,29 +64,28 @@ def testRowMixEquivalenceHTML(self): self.assertEqual(self.row.get_html_string(), self.mix.get_html_string()) -# class FieldnamelessTableTest(unittest.TestCase): -# -# """Make sure that building and stringing a table with no fieldnames works fine""" -# -# def setUp(self): -# self.x = PrettyTable() -# self.x.add_row(["Adelaide",1295, 1158259, 600.5]) -# self.x.add_row(["Brisbane",5905, 1857594, 1146.4]) -# self.x.add_row(["Darwin", 112, 120900, 1714.7]) -# self.x.add_row(["Hobart", 1357, 205556, 619.5]) -# self.x.add_row(["Sydney", 2058, 4336374, 1214.8]) -# self.x.add_row(["Melbourne", 1566, 3806092, 646.9]) -# self.x.add_row(["Perth", 5386, 1554769, 869.4]) -# -# def testCanStringASCII(self): -# self.x.get_string() -# -# def testCanStringHTML(self): -# self.x.get_html_string() -# -# def testAddFieldnamesLater(self): -# self.x.field_names = ["City name", "Area", "Population", "Annual Rainfall"] -# self.x.get_string() +class FieldnamelessTableTest(unittest.TestCase): + """Make sure that building and stringing a table with no fieldnames works fine""" + + def setUp(self): + self.x = PrettyTable() + self.x.add_row(["Adelaide", 1295, 1158259, 600.5]) + self.x.add_row(["Brisbane", 5905, 1857594, 1146.4]) + self.x.add_row(["Darwin", 112, 120900, 1714.7]) + self.x.add_row(["Hobart", 1357, 205556, 619.5]) + self.x.add_row(["Sydney", 2058, 4336374, 1214.8]) + self.x.add_row(["Melbourne", 1566, 3806092, 646.9]) + self.x.add_row(["Perth", 5386, 1554769, 869.4]) + + def testCanStringASCII(self): + self.x.get_string() + + def testCanStringHTML(self): + self.x.get_html_string() + + def testAddFieldnamesLater(self): + self.x.field_names = ["City name", "Area", "Population", "Annual Rainfall"] + self.x.get_string() class CityDataTest(unittest.TestCase): @@ -595,6 +594,59 @@ def setUp(self): self.x = from_csv(csv_fp) +class JsonConstructorTest(BasicTests): + def setUp(self): + json_string = """[ + { + "City name": "Sydney", + "Area": "2058", + "Population": "4336374", + "Annual Rainfall": "1214.8" + }, + { + "City name": "Melbourne", + "Area": "1566", + "Population": "3806092", + "Annual Rainfall": "646.9" + }, + { + "City name": "Brisbane", + "Area": "5905", + "Population": "1857594", + "Annual Rainfall": "1146.4" + }, + { + "City name": "Perth", + "Area": "5386", + "Population": "1554769", + "Coordinates": "31.9523° S, 115.8613° E", + "Annual Rainfall": "869.4" + }, + { + "City name": "Adelaide", + "Area": "1295", + "Population": "1158259", + "Annual Rainfall": "600.5" + }, + { + "City name": "Hobart", + "Area": "1357", + "Population": "205556", + "Annual Rainfall": "619.5" + }, + { + "City name": "Darwin", + "Area": "0112", + "Population": "120900", + "Annual Rainfall": "1714.7" + } +]""" + json_fp = StringIO.StringIO(json_string) + self.x = from_json(json_fp) + print("Generated using json:") + print(self.x) + + if _have_sqlite: class DatabaseConstructorTest(BasicTests): def setUp(self):