1# -*- coding: utf-8 -*-
2
3"""Test support of the various forms of tabular data."""
4
5from __future__ import print_function
6from __future__ import unicode_literals
7from tabulate import tabulate
8from common import assert_equal, assert_in, assert_raises, SkipTest
9
10
11def test_iterable_of_iterables():
12    "Input: an interable of iterables."
13    ii = iter(map(lambda x: iter(x), [range(5), range(5, 0, -1)]))
14    expected = "\n".join(
15        ["-  -  -  -  -", "0  1  2  3  4", "5  4  3  2  1", "-  -  -  -  -"]
16    )
17    result = tabulate(ii)
18    assert_equal(expected, result)
19
20
21def test_iterable_of_iterables_headers():
22    "Input: an interable of iterables with headers."
23    ii = iter(map(lambda x: iter(x), [range(5), range(5, 0, -1)]))
24    expected = "\n".join(
25        [
26            "  a    b    c    d    e",
27            "---  ---  ---  ---  ---",
28            "  0    1    2    3    4",
29            "  5    4    3    2    1",
30        ]
31    )
32    result = tabulate(ii, "abcde")
33    assert_equal(expected, result)
34
35
36def test_iterable_of_iterables_firstrow():
37    "Input: an interable of iterables with the first row as headers"
38    ii = iter(map(lambda x: iter(x), ["abcde", range(5), range(5, 0, -1)]))
39    expected = "\n".join(
40        [
41            "  a    b    c    d    e",
42            "---  ---  ---  ---  ---",
43            "  0    1    2    3    4",
44            "  5    4    3    2    1",
45        ]
46    )
47    result = tabulate(ii, "firstrow")
48    assert_equal(expected, result)
49
50
51def test_list_of_lists():
52    "Input: a list of lists with headers."
53    ll = [["a", "one", 1], ["b", "two", None]]
54    expected = "\n".join(
55        [
56            "    string      number",
57            "--  --------  --------",
58            "a   one              1",
59            "b   two",
60        ]
61    )
62    result = tabulate(ll, headers=["string", "number"])
63    assert_equal(expected, result)
64
65
66def test_list_of_lists_firstrow():
67    "Input: a list of lists with the first row as headers."
68    ll = [["string", "number"], ["a", "one", 1], ["b", "two", None]]
69    expected = "\n".join(
70        [
71            "    string      number",
72            "--  --------  --------",
73            "a   one              1",
74            "b   two",
75        ]
76    )
77    result = tabulate(ll, headers="firstrow")
78    assert_equal(expected, result)
79
80
81def test_list_of_lists_keys():
82    "Input: a list of lists with column indices as headers."
83    ll = [["a", "one", 1], ["b", "two", None]]
84    expected = "\n".join(
85        ["0    1      2", "---  ---  ---", "a    one    1", "b    two"]
86    )
87    result = tabulate(ll, headers="keys")
88    assert_equal(expected, result)
89
90
91def test_dict_like():
92    "Input: a dict of iterables with keys as headers."
93    # columns should be padded with None, keys should be used as headers
94    dd = {"a": range(3), "b": range(101, 105)}
95    # keys' order (hence columns' order) is not deterministic in Python 3
96    # => we have to consider both possible results as valid
97    expected1 = "\n".join(
98        ["  a    b", "---  ---", "  0  101", "  1  102", "  2  103", "     104"]
99    )
100    expected2 = "\n".join(
101        ["  b    a", "---  ---", "101    0", "102    1", "103    2", "104"]
102    )
103    result = tabulate(dd, "keys")
104    print("Keys' order: %s" % dd.keys())
105    assert_in(result, [expected1, expected2])
106
107
108def test_numpy_2d():
109    "Input: a 2D NumPy array with headers."
110    try:
111        import numpy
112
113        na = (numpy.arange(1, 10, dtype=numpy.float32).reshape((3, 3)) ** 3) * 0.5
114        expected = "\n".join(
115            [
116                "    a      b      c",
117                "-----  -----  -----",
118                "  0.5    4     13.5",
119                " 32     62.5  108",
120                "171.5  256    364.5",
121            ]
122        )
123        result = tabulate(na, ["a", "b", "c"])
124        assert_equal(expected, result)
125    except ImportError:
126        print("test_numpy_2d is skipped")
127        raise SkipTest()  # this test is optional
128
129
130def test_numpy_2d_firstrow():
131    "Input: a 2D NumPy array with the first row as headers."
132    try:
133        import numpy
134
135        na = numpy.arange(1, 10, dtype=numpy.int32).reshape((3, 3)) ** 3
136        expected = "\n".join(
137            ["  1    8    27", "---  ---  ----", " 64  125   216", "343  512   729"]
138        )
139        result = tabulate(na, headers="firstrow")
140        assert_equal(expected, result)
141    except ImportError:
142        print("test_numpy_2d_firstrow is skipped")
143        raise SkipTest()  # this test is optional
144
145
146def test_numpy_2d_keys():
147    "Input: a 2D NumPy array with column indices as headers."
148    try:
149        import numpy
150
151        na = (numpy.arange(1, 10, dtype=numpy.float32).reshape((3, 3)) ** 3) * 0.5
152        expected = "\n".join(
153            [
154                "    0      1      2",
155                "-----  -----  -----",
156                "  0.5    4     13.5",
157                " 32     62.5  108",
158                "171.5  256    364.5",
159            ]
160        )
161        result = tabulate(na, headers="keys")
162        assert_equal(expected, result)
163    except ImportError:
164        print("test_numpy_2d_keys is skipped")
165        raise SkipTest()  # this test is optional
166
167
168def test_numpy_record_array():
169    "Input: a 2D NumPy record array without header."
170    try:
171        import numpy
172
173        na = numpy.asarray(
174            [("Alice", 23, 169.5), ("Bob", 27, 175.0)],
175            dtype={
176                "names": ["name", "age", "height"],
177                "formats": ["a32", "uint8", "float32"],
178            },
179        )
180        expected = "\n".join(
181            [
182                "-----  --  -----",
183                "Alice  23  169.5",
184                "Bob    27  175",
185                "-----  --  -----",
186            ]
187        )
188        result = tabulate(na)
189        assert_equal(expected, result)
190    except ImportError:
191        print("test_numpy_2d_keys is skipped")
192        raise SkipTest()  # this test is optional
193
194
195def test_numpy_record_array_keys():
196    "Input: a 2D NumPy record array with column names as headers."
197    try:
198        import numpy
199
200        na = numpy.asarray(
201            [("Alice", 23, 169.5), ("Bob", 27, 175.0)],
202            dtype={
203                "names": ["name", "age", "height"],
204                "formats": ["a32", "uint8", "float32"],
205            },
206        )
207        expected = "\n".join(
208            [
209                "name      age    height",
210                "------  -----  --------",
211                "Alice      23     169.5",
212                "Bob        27     175",
213            ]
214        )
215        result = tabulate(na, headers="keys")
216        assert_equal(expected, result)
217    except ImportError:
218        print("test_numpy_2d_keys is skipped")
219        raise SkipTest()  # this test is optional
220
221
222def test_numpy_record_array_headers():
223    "Input: a 2D NumPy record array with user-supplied headers."
224    try:
225        import numpy
226
227        na = numpy.asarray(
228            [("Alice", 23, 169.5), ("Bob", 27, 175.0)],
229            dtype={
230                "names": ["name", "age", "height"],
231                "formats": ["a32", "uint8", "float32"],
232            },
233        )
234        expected = "\n".join(
235            [
236                "person      years     cm",
237                "--------  -------  -----",
238                "Alice          23  169.5",
239                "Bob            27  175",
240            ]
241        )
242        result = tabulate(na, headers=["person", "years", "cm"])
243        assert_equal(expected, result)
244    except ImportError:
245        print("test_numpy_2d_keys is skipped")
246        raise SkipTest()  # this test is optional
247
248
249def test_pandas():
250    "Input: a Pandas DataFrame."
251    try:
252        import pandas
253
254        df = pandas.DataFrame([["one", 1], ["two", None]], index=["a", "b"])
255        expected = "\n".join(
256            [
257                "    string      number",
258                "--  --------  --------",
259                "a   one              1",
260                "b   two            nan",
261            ]
262        )
263        result = tabulate(df, headers=["string", "number"])
264        assert_equal(expected, result)
265    except ImportError:
266        print("test_pandas is skipped")
267        raise SkipTest()  # this test is optional
268
269
270def test_pandas_firstrow():
271    "Input: a Pandas DataFrame with the first row as headers."
272    try:
273        import pandas
274
275        df = pandas.DataFrame(
276            [["one", 1], ["two", None]], columns=["string", "number"], index=["a", "b"]
277        )
278        expected = "\n".join(
279            ["a    one      1.0", "---  -----  -----", "b    two      nan"]
280        )
281        result = tabulate(df, headers="firstrow")
282        assert_equal(expected, result)
283    except ImportError:
284        print("test_pandas_firstrow is skipped")
285        raise SkipTest()  # this test is optional
286
287
288def test_pandas_keys():
289    "Input: a Pandas DataFrame with keys as headers."
290    try:
291        import pandas
292
293        df = pandas.DataFrame(
294            [["one", 1], ["two", None]], columns=["string", "number"], index=["a", "b"]
295        )
296        expected = "\n".join(
297            [
298                "    string      number",
299                "--  --------  --------",
300                "a   one              1",
301                "b   two            nan",
302            ]
303        )
304        result = tabulate(df, headers="keys")
305        assert_equal(expected, result)
306    except ImportError:
307        print("test_pandas_keys is skipped")
308        raise SkipTest()  # this test is optional
309
310
311def test_sqlite3():
312    "Input: an sqlite3 cursor"
313    try:
314        import sqlite3
315
316        conn = sqlite3.connect(":memory:")
317        cursor = conn.cursor()
318        cursor.execute("CREATE TABLE people (name, age, height)")
319        for values in [("Alice", 23, 169.5), ("Bob", 27, 175.0)]:
320            cursor.execute("INSERT INTO people VALUES (?, ?, ?)", values)
321        cursor.execute("SELECT name, age, height FROM people ORDER BY name")
322        result = tabulate(cursor, headers=["whom", "how old", "how tall"])
323        expected = """\
324whom      how old    how tall
325------  ---------  ----------
326Alice          23       169.5
327Bob            27       175"""
328        assert_equal(expected, result)
329    except ImportError:
330        print("test_sqlite3 is skipped")
331        raise SkipTest()  # this test is optional
332
333
334def test_sqlite3_keys():
335    "Input: an sqlite3 cursor with keys as headers"
336    try:
337        import sqlite3
338
339        conn = sqlite3.connect(":memory:")
340        cursor = conn.cursor()
341        cursor.execute("CREATE TABLE people (name, age, height)")
342        for values in [("Alice", 23, 169.5), ("Bob", 27, 175.0)]:
343            cursor.execute("INSERT INTO people VALUES (?, ?, ?)", values)
344        cursor.execute(
345            'SELECT name "whom", age "how old", height "how tall" FROM people ORDER BY name'
346        )
347        result = tabulate(cursor, headers="keys")
348        expected = """\
349whom      how old    how tall
350------  ---------  ----------
351Alice          23       169.5
352Bob            27       175"""
353        assert_equal(expected, result)
354    except ImportError:
355        print("test_sqlite3_keys is skipped")
356        raise SkipTest()  # this test is optional
357
358
359def test_list_of_namedtuples():
360    "Input: a list of named tuples with field names as headers."
361    from collections import namedtuple
362
363    NT = namedtuple("NT", ["foo", "bar"])
364    lt = [NT(1, 2), NT(3, 4)]
365    expected = "\n".join(["-  -", "1  2", "3  4", "-  -"])
366    result = tabulate(lt)
367    assert_equal(expected, result)
368
369
370def test_list_of_namedtuples_keys():
371    "Input: a list of named tuples with field names as headers."
372    from collections import namedtuple
373
374    NT = namedtuple("NT", ["foo", "bar"])
375    lt = [NT(1, 2), NT(3, 4)]
376    expected = "\n".join(
377        ["  foo    bar", "-----  -----", "    1      2", "    3      4"]
378    )
379    result = tabulate(lt, headers="keys")
380    assert_equal(expected, result)
381
382
383def test_list_of_dicts():
384    "Input: a list of dictionaries."
385    lod = [{"foo": 1, "bar": 2}, {"foo": 3, "bar": 4}]
386    expected1 = "\n".join(["-  -", "1  2", "3  4", "-  -"])
387    expected2 = "\n".join(["-  -", "2  1", "4  3", "-  -"])
388    result = tabulate(lod)
389    assert_in(result, [expected1, expected2])
390
391
392def test_list_of_dicts_keys():
393    "Input: a list of dictionaries, with keys as headers."
394    lod = [{"foo": 1, "bar": 2}, {"foo": 3, "bar": 4}]
395    expected1 = "\n".join(
396        ["  foo    bar", "-----  -----", "    1      2", "    3      4"]
397    )
398    expected2 = "\n".join(
399        ["  bar    foo", "-----  -----", "    2      1", "    4      3"]
400    )
401    result = tabulate(lod, headers="keys")
402    assert_in(result, [expected1, expected2])
403
404
405def test_list_of_dicts_with_missing_keys():
406    "Input: a list of dictionaries, with missing keys."
407    lod = [{"foo": 1}, {"bar": 2}, {"foo": 4, "baz": 3}]
408    expected = "\n".join(
409        [
410            "  foo    bar    baz",
411            "-----  -----  -----",
412            "    1",
413            "           2",
414            "    4             3",
415        ]
416    )
417    result = tabulate(lod, headers="keys")
418    assert_equal(expected, result)
419
420
421def test_list_of_dicts_firstrow():
422    "Input: a list of dictionaries, with the first dict as headers."
423    lod = [{"foo": "FOO", "bar": "BAR"}, {"foo": 3, "bar": 4, "baz": 5}]
424    # if some key is missing in the first dict, use the key name instead
425    expected1 = "\n".join(
426        ["  FOO    BAR    baz", "-----  -----  -----", "    3      4      5"]
427    )
428    expected2 = "\n".join(
429        ["  BAR    FOO    baz", "-----  -----  -----", "    4      3      5"]
430    )
431    result = tabulate(lod, headers="firstrow")
432    assert_in(result, [expected1, expected2])
433
434
435def test_list_of_dicts_with_dict_of_headers():
436    "Input: a dict of user headers for a list of dicts (issue #23)"
437    table = [{"letters": "ABCDE", "digits": 12345}]
438    headers = {"digits": "DIGITS", "letters": "LETTERS"}
439    expected1 = "\n".join(
440        ["  DIGITS  LETTERS", "--------  ---------", "   12345  ABCDE"]
441    )
442    expected2 = "\n".join(
443        ["LETTERS      DIGITS", "---------  --------", "ABCDE         12345"]
444    )
445    result = tabulate(table, headers=headers)
446    assert_in(result, [expected1, expected2])
447
448
449def test_list_of_dicts_with_list_of_headers():
450    "Input: ValueError on a list of headers with a list of dicts (issue #23)"
451    table = [{"letters": "ABCDE", "digits": 12345}]
452    headers = ["DIGITS", "LETTERS"]
453    with assert_raises(ValueError):
454        tabulate(table, headers=headers)
455
456
457def test_py27orlater_list_of_ordereddicts():
458    "Input: a list of OrderedDicts."
459    from collections import OrderedDict
460
461    od = OrderedDict([("b", 1), ("a", 2)])
462    lod = [od, od]
463    expected = "\n".join(["  b    a", "---  ---", "  1    2", "  1    2"])
464    result = tabulate(lod, headers="keys")
465    assert_equal(expected, result)
466