1import datetime as dt 2import io 3import random 4import sqlite3 5from math import e, pi, sqrt 6from typing import Any, List 7 8import pytest 9 10import prettytable 11from prettytable import ( 12 ALL, 13 DEFAULT, 14 DOUBLE_BORDER, 15 FRAME, 16 HEADER, 17 MARKDOWN, 18 MSWORD_FRIENDLY, 19 NONE, 20 ORGMODE, 21 PLAIN_COLUMNS, 22 RANDOM, 23 PrettyTable, 24 from_csv, 25 from_db_cursor, 26 from_html, 27 from_html_one, 28 from_json, 29) 30 31 32def helper_table(rows=3): 33 t = PrettyTable(["Field 1", "Field 2", "Field 3"]) 34 v = 1 35 for row in range(rows): 36 # Some have spaces, some not, to help test padding columns of different widths 37 t.add_row([f"value {v}", f"value{v+1}", f"value{v+2}"]) 38 v += 3 39 return t 40 41 42@pytest.fixture 43def row_prettytable(): 44 # Row by row... 45 row = PrettyTable() 46 row.field_names = ["City name", "Area", "Population", "Annual Rainfall"] 47 row.add_row(["Adelaide", 1295, 1158259, 600.5]) 48 row.add_row(["Brisbane", 5905, 1857594, 1146.4]) 49 row.add_row(["Darwin", 112, 120900, 1714.7]) 50 row.add_row(["Hobart", 1357, 205556, 619.5]) 51 row.add_row(["Sydney", 2058, 4336374, 1214.8]) 52 row.add_row(["Melbourne", 1566, 3806092, 646.9]) 53 row.add_row(["Perth", 5386, 1554769, 869.4]) 54 return row 55 56 57@pytest.fixture 58def col_prettytable(): 59 # Column by column... 60 col = PrettyTable() 61 col.add_column( 62 "City name", 63 ["Adelaide", "Brisbane", "Darwin", "Hobart", "Sydney", "Melbourne", "Perth"], 64 ) 65 col.add_column("Area", [1295, 5905, 112, 1357, 2058, 1566, 5386]) 66 col.add_column( 67 "Population", [1158259, 1857594, 120900, 205556, 4336374, 3806092, 1554769] 68 ) 69 col.add_column( 70 "Annual Rainfall", [600.5, 1146.4, 1714.7, 619.5, 1214.8, 646.9, 869.4] 71 ) 72 return col 73 74 75@pytest.fixture 76def mix_prettytable(): 77 # A mix of both! 78 mix = PrettyTable() 79 mix.field_names = ["City name", "Area"] 80 mix.add_row(["Adelaide", 1295]) 81 mix.add_row(["Brisbane", 5905]) 82 mix.add_row(["Darwin", 112]) 83 mix.add_row(["Hobart", 1357]) 84 mix.add_row(["Sydney", 2058]) 85 mix.add_row(["Melbourne", 1566]) 86 mix.add_row(["Perth", 5386]) 87 mix.add_column( 88 "Population", [1158259, 1857594, 120900, 205556, 4336374, 3806092, 1554769] 89 ) 90 mix.add_column( 91 "Annual Rainfall", [600.5, 1146.4, 1714.7, 619.5, 1214.8, 646.9, 869.4] 92 ) 93 return mix 94 95 96class TestBuildEquivalence: 97 """Make sure that building a table row-by-row and column-by-column yield the same 98 results""" 99 100 @pytest.mark.parametrize( 101 ["left_hand", "right_hand"], 102 [ 103 ( 104 pytest.lazy_fixture("row_prettytable"), 105 pytest.lazy_fixture("col_prettytable"), 106 ), 107 ( 108 pytest.lazy_fixture("row_prettytable"), 109 pytest.lazy_fixture("mix_prettytable"), 110 ), 111 ], 112 ) 113 def test_equivalence_ASCII(self, left_hand: PrettyTable, right_hand: PrettyTable): 114 assert left_hand.get_string() == right_hand.get_string() 115 116 @pytest.mark.parametrize( 117 ["left_hand", "right_hand"], 118 [ 119 ( 120 pytest.lazy_fixture("row_prettytable"), 121 pytest.lazy_fixture("col_prettytable"), 122 ), 123 ( 124 pytest.lazy_fixture("row_prettytable"), 125 pytest.lazy_fixture("mix_prettytable"), 126 ), 127 ], 128 ) 129 def test_equivalence_HTML(self, left_hand: PrettyTable, right_hand: PrettyTable): 130 assert left_hand.get_html_string() == right_hand.get_html_string() 131 132 @pytest.mark.parametrize( 133 ["left_hand", "right_hand"], 134 [ 135 ( 136 pytest.lazy_fixture("row_prettytable"), 137 pytest.lazy_fixture("col_prettytable"), 138 ), 139 ( 140 pytest.lazy_fixture("row_prettytable"), 141 pytest.lazy_fixture("mix_prettytable"), 142 ), 143 ], 144 ) 145 def test_equivalence_latex(self, left_hand: PrettyTable, right_hand: PrettyTable): 146 assert left_hand.get_latex_string() == right_hand.get_latex_string() 147 148 149class TestDeleteColumn: 150 def test_delete_column(self): 151 with_del = PrettyTable() 152 with_del.add_column("City name", ["Adelaide", "Brisbane", "Darwin"]) 153 with_del.add_column("Area", [1295, 5905, 112]) 154 with_del.add_column("Population", [1158259, 1857594, 120900]) 155 with_del.del_column("Area") 156 157 without_row = PrettyTable() 158 without_row.add_column("City name", ["Adelaide", "Brisbane", "Darwin"]) 159 without_row.add_column("Population", [1158259, 1857594, 120900]) 160 161 assert with_del.get_string() == without_row.get_string() 162 163 def test_delete_illegal_column_raises_exception(self): 164 table = PrettyTable() 165 table.add_column("City name", ["Adelaide", "Brisbane", "Darwin"]) 166 167 with pytest.raises(Exception): 168 table.del_column("City not-a-name") 169 170 171@pytest.fixture(scope="function") 172def field_name_less_table(): 173 x = PrettyTable() 174 x.add_row(["Adelaide", 1295, 1158259, 600.5]) 175 x.add_row(["Brisbane", 5905, 1857594, 1146.4]) 176 x.add_row(["Darwin", 112, 120900, 1714.7]) 177 x.add_row(["Hobart", 1357, 205556, 619.5]) 178 x.add_row(["Sydney", 2058, 4336374, 1214.8]) 179 x.add_row(["Melbourne", 1566, 3806092, 646.9]) 180 x.add_row(["Perth", 5386, 1554769, 869.4]) 181 return x 182 183 184class TestFieldNameLessTable: 185 186 """Make sure that building and stringing a table with no fieldnames works fine""" 187 188 def test_can_string_ASCII(self, field_name_less_table: prettytable): 189 output = field_name_less_table.get_string() 190 assert "| Field 1 | Field 2 | Field 3 | Field 4 |" in output 191 assert "| Adelaide | 1295 | 1158259 | 600.5 |" in output 192 193 def test_can_string_HTML(self, field_name_less_table: prettytable): 194 output = field_name_less_table.get_html_string() 195 assert "<th>Field 1</th>" in output 196 assert "<td>Adelaide</td>" in output 197 198 def test_can_string_latex(self, field_name_less_table: prettytable): 199 output = field_name_less_table.get_latex_string() 200 assert "Field 1 & Field 2 & Field 3 & Field 4 \\\\" in output 201 assert "Adelaide & 1295 & 1158259 & 600.5 \\\\" in output 202 203 def test_add_field_names_later(self, field_name_less_table: prettytable): 204 field_name_less_table.field_names = [ 205 "City name", 206 "Area", 207 "Population", 208 "Annual Rainfall", 209 ] 210 assert ( 211 "City name | Area | Population | Annual Rainfall" 212 in field_name_less_table.get_string() 213 ) 214 215 216@pytest.fixture(scope="function") 217def city_data_prettytable(): 218 """Just build the Australian capital city data example table.""" 219 pt = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"]) 220 pt.add_row(["Adelaide", 1295, 1158259, 600.5]) 221 pt.add_row(["Brisbane", 5905, 1857594, 1146.4]) 222 pt.add_row(["Darwin", 112, 120900, 1714.7]) 223 pt.add_row(["Hobart", 1357, 205556, 619.5]) 224 pt.add_row(["Sydney", 2058, 4336374, 1214.8]) 225 pt.add_row(["Melbourne", 1566, 3806092, 646.9]) 226 pt.add_row(["Perth", 5386, 1554769, 869.4]) 227 return pt 228 229 230@pytest.fixture(scope="function") 231def city_data_from_csv(): 232 csv_string = """City name, Area, Population, Annual Rainfall 233 Sydney, 2058, 4336374, 1214.8 234 Melbourne, 1566, 3806092, 646.9 235 Brisbane, 5905, 1857594, 1146.4 236 Perth, 5386, 1554769, 869.4 237 Adelaide, 1295, 1158259, 600.5 238 Hobart, 1357, 205556, 619.5 239 Darwin, 0112, 120900, 1714.7""" 240 csv_fp = io.StringIO(csv_string) 241 return from_csv(csv_fp) 242 243 244class TestOptionOverride: 245 246 """Make sure all options are properly overwritten by get_string.""" 247 248 def test_border(self, city_data_prettytable: prettytable): 249 default = city_data_prettytable.get_string() 250 override = city_data_prettytable.get_string(border=False) 251 assert default != override 252 253 def test_header(self, city_data_prettytable): 254 default = city_data_prettytable.get_string() 255 override = city_data_prettytable.get_string(header=False) 256 assert default != override 257 258 def test_hrules_all(self, city_data_prettytable): 259 default = city_data_prettytable.get_string() 260 override = city_data_prettytable.get_string(hrules=ALL) 261 assert default != override 262 263 def test_hrules_none(self, city_data_prettytable): 264 default = city_data_prettytable.get_string() 265 override = city_data_prettytable.get_string(hrules=NONE) 266 assert default != override 267 268 269class TestOptionAttribute: 270 271 """Make sure all options which have an attribute interface work as they should. 272 Also make sure option settings are copied correctly when a table is cloned by 273 slicing.""" 274 275 def test_set_for_all_columns(self, city_data_prettytable): 276 city_data_prettytable.field_names = sorted(city_data_prettytable.field_names) 277 city_data_prettytable.align = "l" 278 city_data_prettytable.max_width = 10 279 city_data_prettytable.start = 2 280 city_data_prettytable.end = 4 281 city_data_prettytable.sortby = "Area" 282 city_data_prettytable.reversesort = True 283 city_data_prettytable.header = True 284 city_data_prettytable.border = False 285 city_data_prettytable.hrules = True 286 city_data_prettytable.int_format = "4" 287 city_data_prettytable.float_format = "2.2" 288 city_data_prettytable.padding_width = 2 289 city_data_prettytable.left_padding_width = 2 290 city_data_prettytable.right_padding_width = 2 291 city_data_prettytable.vertical_char = "!" 292 city_data_prettytable.horizontal_char = "~" 293 city_data_prettytable.junction_char = "*" 294 city_data_prettytable.top_junction_char = "@" 295 city_data_prettytable.bottom_junction_char = "#" 296 city_data_prettytable.right_junction_char = "$" 297 city_data_prettytable.left_junction_char = "%" 298 city_data_prettytable.top_right_junction_char = "^" 299 city_data_prettytable.top_left_junction_char = "&" 300 city_data_prettytable.bottom_right_junction_char = "(" 301 city_data_prettytable.bottom_left_junction_char = ")" 302 city_data_prettytable.format = True 303 city_data_prettytable.attributes = {"class": "prettytable"} 304 assert ( 305 city_data_prettytable.get_string() == city_data_prettytable[:].get_string() 306 ) 307 308 def test_set_for_one_column(self, city_data_prettytable): 309 city_data_prettytable.align["Rainfall"] = "l" 310 city_data_prettytable.max_width["Name"] = 10 311 city_data_prettytable.int_format["Population"] = "4" 312 city_data_prettytable.float_format["Area"] = "2.2" 313 assert ( 314 city_data_prettytable.get_string() == city_data_prettytable[:].get_string() 315 ) 316 317 318@pytest.fixture(scope="module") 319def db_cursor(): 320 conn = sqlite3.connect(":memory:") 321 cur = conn.cursor() 322 yield cur 323 cur.close() 324 conn.close() 325 326 327@pytest.fixture(scope="module") 328def init_db(db_cursor): 329 db_cursor.execute( 330 "CREATE TABLE cities " 331 "(name TEXT, area INTEGER, population INTEGER, rainfall REAL)" 332 ) 333 db_cursor.execute('INSERT INTO cities VALUES ("Adelaide", 1295, 1158259, 600.5)') 334 db_cursor.execute('INSERT INTO cities VALUES ("Brisbane", 5905, 1857594, 1146.4)') 335 db_cursor.execute('INSERT INTO cities VALUES ("Darwin", 112, 120900, 1714.7)') 336 db_cursor.execute('INSERT INTO cities VALUES ("Hobart", 1357, 205556, 619.5)') 337 db_cursor.execute('INSERT INTO cities VALUES ("Sydney", 2058, 4336374, 1214.8)') 338 db_cursor.execute('INSERT INTO cities VALUES ("Melbourne", 1566, 3806092, 646.9)') 339 db_cursor.execute('INSERT INTO cities VALUES ("Perth", 5386, 1554769, 869.4)') 340 yield 341 db_cursor.execute("DROP TABLE cities") 342 343 344class TestBasic: 345 """Some very basic tests.""" 346 347 def test_table_rows(self, city_data_prettytable: PrettyTable) -> None: 348 rows = city_data_prettytable.rows 349 assert len(rows) == 7 350 assert rows[0] == ["Adelaide", 1295, 1158259, 600.5] 351 352 def _test_no_blank_lines(self, table: prettytable): 353 string = table.get_string() 354 lines = string.split("\n") 355 assert "" not in lines 356 357 def _test_all_length_equal(self, table: prettytable): 358 string = table.get_string() 359 lines = string.split("\n") 360 lengths = [len(line) for line in lines] 361 lengths = set(lengths) 362 assert len(lengths) == 1 363 364 def test_no_blank_lines(self, city_data_prettytable): 365 """No table should ever have blank lines in it.""" 366 self._test_no_blank_lines(city_data_prettytable) 367 368 def test_all_lengths_equal(self, city_data_prettytable): 369 """All lines in a table should be of the same length.""" 370 self._test_all_length_equal(city_data_prettytable) 371 372 def test_no_blank_lines_with_title(self, city_data_prettytable: PrettyTable): 373 """No table should ever have blank lines in it.""" 374 city_data_prettytable.title = "My table" 375 self._test_no_blank_lines(city_data_prettytable) 376 377 def test_all_lengths_equal_with_title(self, city_data_prettytable: PrettyTable): 378 """All lines in a table should be of the same length.""" 379 city_data_prettytable.title = "My table" 380 self._test_all_length_equal(city_data_prettytable) 381 382 def test_no_blank_lines_without_border(self, city_data_prettytable: PrettyTable): 383 """No table should ever have blank lines in it.""" 384 city_data_prettytable.border = False 385 self._test_no_blank_lines(city_data_prettytable) 386 387 def test_all_lengths_equal_without_border(self, city_data_prettytable: PrettyTable): 388 """All lines in a table should be of the same length.""" 389 city_data_prettytable.border = False 390 self._test_all_length_equal(city_data_prettytable) 391 392 def test_no_blank_lines_without_header(self, city_data_prettytable: PrettyTable): 393 """No table should ever have blank lines in it.""" 394 city_data_prettytable.header = False 395 self._test_no_blank_lines(city_data_prettytable) 396 397 def test_all_lengths_equal_without_header(self, city_data_prettytable: PrettyTable): 398 """All lines in a table should be of the same length.""" 399 city_data_prettytable.header = False 400 self._test_all_length_equal(city_data_prettytable) 401 402 def test_no_blank_lines_with_hrules_none(self, city_data_prettytable: PrettyTable): 403 """No table should ever have blank lines in it.""" 404 city_data_prettytable.hrules = NONE 405 self._test_no_blank_lines(city_data_prettytable) 406 407 def test_all_lengths_equal_with_hrules_none( 408 self, city_data_prettytable: PrettyTable 409 ): 410 """All lines in a table should be of the same length.""" 411 city_data_prettytable.hrules = NONE 412 self._test_all_length_equal(city_data_prettytable) 413 414 def test_no_blank_lines_with_hrules_all(self, city_data_prettytable: PrettyTable): 415 """No table should ever have blank lines in it.""" 416 city_data_prettytable.hrules = ALL 417 self._test_no_blank_lines(city_data_prettytable) 418 419 def test_all_lengths_equal_with_hrules_all( 420 self, city_data_prettytable: PrettyTable 421 ): 422 """All lines in a table should be of the same length.""" 423 city_data_prettytable.hrules = ALL 424 self._test_all_length_equal(city_data_prettytable) 425 426 def test_no_blank_lines_with_style_msword(self, city_data_prettytable: PrettyTable): 427 """No table should ever have blank lines in it.""" 428 city_data_prettytable.set_style(MSWORD_FRIENDLY) 429 self._test_no_blank_lines(city_data_prettytable) 430 431 def test_all_lengths_equal_with_style_msword( 432 self, city_data_prettytable: PrettyTable 433 ): 434 """All lines in a table should be of the same length.""" 435 city_data_prettytable.set_style(MSWORD_FRIENDLY) 436 self._test_all_length_equal(city_data_prettytable) 437 438 def test_no_blank_lines_with_int_format(self, city_data_prettytable: PrettyTable): 439 """No table should ever have blank lines in it.""" 440 city_data_prettytable.int_format = "04" 441 self._test_no_blank_lines(city_data_prettytable) 442 443 def test_all_lengths_equal_with_int_format( 444 self, city_data_prettytable: PrettyTable 445 ): 446 """All lines in a table should be of the same length.""" 447 city_data_prettytable.int_format = "04" 448 self._test_all_length_equal(city_data_prettytable) 449 450 def test_no_blank_lines_with_float_format(self, city_data_prettytable: PrettyTable): 451 """No table should ever have blank lines in it.""" 452 city_data_prettytable.float_format = "6.2f" 453 self._test_no_blank_lines(city_data_prettytable) 454 455 def test_all_lengths_equal_with_float_format( 456 self, city_data_prettytable: PrettyTable 457 ): 458 """All lines in a table should be of the same length.""" 459 city_data_prettytable.float_format = "6.2f" 460 self._test_all_length_equal(city_data_prettytable) 461 462 def test_no_blank_lines_from_csv(self, city_data_from_csv: PrettyTable): 463 """No table should ever have blank lines in it.""" 464 self._test_no_blank_lines(city_data_from_csv) 465 466 def test_all_lengths_equal_from_csv(self, city_data_from_csv: PrettyTable): 467 """All lines in a table should be of the same length.""" 468 self._test_all_length_equal(city_data_from_csv) 469 470 @pytest.mark.usefixtures("init_db") 471 def test_no_blank_lines_from_db(self, db_cursor): 472 """No table should ever have blank lines in it.""" 473 db_cursor.execute("SELECT * FROM cities") 474 pt = from_db_cursor(db_cursor) 475 self._test_no_blank_lines(pt) 476 477 @pytest.mark.usefixtures("init_db") 478 def test_all_lengths_equal_from_db(self, db_cursor): 479 """No table should ever have blank lines in it.""" 480 db_cursor.execute("SELECT * FROM cities") 481 pt = from_db_cursor(db_cursor) 482 self._test_all_length_equal(pt) 483 484 485class TestEmptyTable: 486 """Make sure the print_empty option works""" 487 488 def test_print_empty_true(self, city_data_prettytable: PrettyTable): 489 empty_pt = PrettyTable() 490 empty_pt.field_names = ["City name", "Area", "Population", "Annual Rainfall"] 491 492 assert empty_pt.get_string(print_empty=True) != "" 493 assert empty_pt.get_string( 494 print_empty=True 495 ) != city_data_prettytable.get_string(print_empty=True) 496 497 def test_print_empty_false(self, city_data_prettytable: PrettyTable): 498 empty_pt = PrettyTable() 499 empty_pt.field_names = ["City name", "Area", "Population", "Annual Rainfall"] 500 501 assert empty_pt.get_string(print_empty=False) == "" 502 assert empty_pt.get_string( 503 print_empty=False 504 ) != city_data_prettytable.get_string(print_empty=False) 505 506 def test_interaction_with_border(self): 507 empty_pt = PrettyTable() 508 empty_pt.field_names = ["City name", "Area", "Population", "Annual Rainfall"] 509 510 assert empty_pt.get_string(border=False, print_empty=True) == "" 511 512 513class TestSlicing: 514 def test_slice_all(self, city_data_prettytable: PrettyTable): 515 sliced_pt = city_data_prettytable[:] 516 assert city_data_prettytable.get_string() == sliced_pt.get_string() 517 518 def test_slice_first_two_rows(self, city_data_prettytable: PrettyTable): 519 sliced_pt = city_data_prettytable[0:2] 520 string = sliced_pt.get_string() 521 assert len(string.split("\n")) == 6 522 assert "Adelaide" in string 523 assert "Brisbane" in string 524 assert "Melbourne" not in string 525 assert "Perth" not in string 526 527 def test_slice_last_two_rows(self, city_data_prettytable: PrettyTable): 528 sliced_pt = city_data_prettytable[-2:] 529 string = sliced_pt.get_string() 530 assert len(string.split("\n")) == 6 531 assert "Adelaide" not in string 532 assert "Brisbane" not in string 533 assert "Melbourne" in string 534 assert "Perth" in string 535 536 537class TestSorting: 538 def test_sort_by_different_per_columns(self, city_data_prettytable: PrettyTable): 539 city_data_prettytable.sortby = city_data_prettytable.field_names[0] 540 old = city_data_prettytable.get_string() 541 for field in city_data_prettytable.field_names[1:]: 542 city_data_prettytable.sortby = field 543 new = city_data_prettytable.get_string() 544 assert new != old 545 546 def test_reverse_sort(self, city_data_prettytable: PrettyTable): 547 for field in city_data_prettytable.field_names: 548 city_data_prettytable.sortby = field 549 city_data_prettytable.reversesort = False 550 forward = city_data_prettytable.get_string() 551 city_data_prettytable.reversesort = True 552 backward = city_data_prettytable.get_string() 553 forward_lines = forward.split("\n")[2:] # Discard header lines 554 backward_lines = backward.split("\n")[2:] 555 backward_lines.reverse() 556 assert forward_lines == backward_lines 557 558 def test_sort_key(self, city_data_prettytable: PrettyTable): 559 # Test sorting by length of city name 560 def key(vals): 561 vals[0] = len(vals[0]) 562 return vals 563 564 city_data_prettytable.sortby = "City name" 565 city_data_prettytable.sort_key = key 566 assert ( 567 city_data_prettytable.get_string().strip() 568 == """ 569+-----------+------+------------+-----------------+ 570| City name | Area | Population | Annual Rainfall | 571+-----------+------+------------+-----------------+ 572| Perth | 5386 | 1554769 | 869.4 | 573| Darwin | 112 | 120900 | 1714.7 | 574| Hobart | 1357 | 205556 | 619.5 | 575| Sydney | 2058 | 4336374 | 1214.8 | 576| Adelaide | 1295 | 1158259 | 600.5 | 577| Brisbane | 5905 | 1857594 | 1146.4 | 578| Melbourne | 1566 | 3806092 | 646.9 | 579+-----------+------+------------+-----------------+ 580""".strip() 581 ) 582 583 def test_sort_slice(self): 584 """Make sure sorting and slicing interact in the expected way""" 585 x = PrettyTable(["Foo"]) 586 for i in range(20, 0, -1): 587 x.add_row([i]) 588 new_style = x.get_string(sortby="Foo", end=10) 589 assert "10" in new_style 590 assert "20" not in new_style 591 oldstyle = x.get_string(sortby="Foo", end=10, oldsortslice=True) 592 assert "10" not in oldstyle 593 assert "20" in oldstyle 594 595 596@pytest.fixture(scope="function") 597def float_pt(): 598 pt = PrettyTable(["Constant", "Value"]) 599 pt.add_row(["Pi", pi]) 600 pt.add_row(["e", e]) 601 pt.add_row(["sqrt(2)", sqrt(2)]) 602 return pt 603 604 605class TestFloatFormat: 606 def test_no_decimals(self, float_pt: PrettyTable): 607 float_pt.float_format = ".0f" 608 float_pt.caching = False 609 assert "." not in float_pt.get_string() 610 611 def test_round_to_5DP(self, float_pt: PrettyTable): 612 float_pt.float_format = ".5f" 613 string = float_pt.get_string() 614 assert "3.14159" in string 615 assert "3.141592" not in string 616 assert "2.71828" in string 617 assert "2.718281" not in string 618 assert "2.718282" not in string 619 assert "1.41421" in string 620 assert "1.414213" not in string 621 622 def test_pad_with_2Zeroes(self, float_pt: PrettyTable): 623 float_pt.float_format = "06.2f" 624 string = float_pt.get_string() 625 assert "003.14" in string 626 assert "002.72" in string 627 assert "001.41" in string 628 629 630class TestBreakLine: 631 @pytest.mark.parametrize( 632 ["rows", "hrule", "expected_result"], 633 [ 634 ( 635 [["value 1", "value2\nsecond line"], ["value 3", "value4"]], 636 ALL, 637 """ 638+---------+-------------+ 639| Field 1 | Field 2 | 640+---------+-------------+ 641| value 1 | value2 | 642| | second line | 643+---------+-------------+ 644| value 3 | value4 | 645+---------+-------------+ 646""", 647 ), 648 ( 649 [ 650 ["value 1", "value2\nsecond line"], 651 ["value 3\n\nother line", "value4\n\n\nvalue5"], 652 ], 653 ALL, 654 """ 655+------------+-------------+ 656| Field 1 | Field 2 | 657+------------+-------------+ 658| value 1 | value2 | 659| | second line | 660+------------+-------------+ 661| value 3 | value4 | 662| | | 663| other line | | 664| | value5 | 665+------------+-------------+ 666""", 667 ), 668 ( 669 [ 670 ["value 1", "value2\nsecond line"], 671 ["value 3\n\nother line", "value4\n\n\nvalue5"], 672 ], 673 FRAME, 674 """ 675+------------+-------------+ 676| Field 1 | Field 2 | 677+------------+-------------+ 678| value 1 | value2 | 679| | second line | 680| value 3 | value4 | 681| | | 682| other line | | 683| | value5 | 684+------------+-------------+ 685""", 686 ), 687 ], 688 ) 689 def test_break_line_ASCII( 690 self, rows: List[List[Any]], hrule: int, expected_result: str 691 ): 692 t = PrettyTable(["Field 1", "Field 2"]) 693 for row in rows: 694 t.add_row(row) 695 result = t.get_string(hrules=hrule) 696 assert result.strip() == expected_result.strip() 697 698 def test_break_line_HTML(self): 699 t = PrettyTable(["Field 1", "Field 2"]) 700 t.add_row(["value 1", "value2\nsecond line"]) 701 t.add_row(["value 3", "value4"]) 702 result = t.get_html_string(hrules=ALL) 703 assert ( 704 result.strip() 705 == """ 706<table> 707 <thead> 708 <tr> 709 <th>Field 1</th> 710 <th>Field 2</th> 711 </tr> 712 </thead> 713 <tbody> 714 <tr> 715 <td>value 1</td> 716 <td>value2<br>second line</td> 717 </tr> 718 <tr> 719 <td>value 3</td> 720 <td>value4</td> 721 </tr> 722 </tbody> 723</table> 724""".strip() 725 ) 726 727 728class TestAnsiWidth: 729 colored = "\033[31mC\033[32mO\033[31mL\033[32mO\033[31mR\033[32mE\033[31mD\033[0m" 730 731 def test_color(self): 732 t = PrettyTable(["Field 1", "Field 2"]) 733 t.add_row([self.colored, self.colored]) 734 t.add_row(["nothing", "neither"]) 735 result = t.get_string() 736 assert ( 737 result.strip() 738 == f""" 739+---------+---------+ 740| Field 1 | Field 2 | 741+---------+---------+ 742| {self.colored} | {self.colored} | 743| nothing | neither | 744+---------+---------+ 745""".strip() 746 ) 747 748 def test_reset(self): 749 t = PrettyTable(["Field 1", "Field 2"]) 750 t.add_row(["abc def\033(B", "\033[31mabc def\033[m"]) 751 t.add_row(["nothing", "neither"]) 752 result = t.get_string() 753 assert ( 754 result.strip() 755 == """ 756+---------+---------+ 757| Field 1 | Field 2 | 758+---------+---------+ 759| abc def\033(B | \033[31mabc def\033[m | 760| nothing | neither | 761+---------+---------+ 762""".strip() 763 ) 764 765 766class TestFromDB: 767 @pytest.mark.usefixtures("init_db") 768 def test_non_select_cursor(self, db_cursor): 769 db_cursor.execute( 770 'INSERT INTO cities VALUES ("Adelaide", 1295, 1158259, 600.5)' 771 ) 772 assert from_db_cursor(db_cursor) is None 773 774 775class TestJSONOutput: 776 def test_JSON_output(self): 777 t = helper_table() 778 result = t.get_json_string() 779 assert ( 780 result.strip() 781 == """ 782[ 783 [ 784 "Field 1", 785 "Field 2", 786 "Field 3" 787 ], 788 { 789 "Field 1": "value 1", 790 "Field 2": "value2", 791 "Field 3": "value3" 792 }, 793 { 794 "Field 1": "value 4", 795 "Field 2": "value5", 796 "Field 3": "value6" 797 }, 798 { 799 "Field 1": "value 7", 800 "Field 2": "value8", 801 "Field 3": "value9" 802 } 803]""".strip() 804 ) 805 806 def test_JSON_output_options(self): 807 t = helper_table() 808 result = t.get_json_string(header=False, indent=None, separators=(",", ":")) 809 assert ( 810 result 811 == """[{"Field 1":"value 1","Field 2":"value2","Field 3":"value3"},""" 812 """{"Field 1":"value 4","Field 2":"value5","Field 3":"value6"},""" 813 """{"Field 1":"value 7","Field 2":"value8","Field 3":"value9"}]""" 814 ) 815 816 817class TestHtmlOutput: 818 def test_HtmlOutput(self): 819 t = helper_table() 820 result = t.get_html_string() 821 assert ( 822 result.strip() 823 == """ 824<table> 825 <thead> 826 <tr> 827 <th>Field 1</th> 828 <th>Field 2</th> 829 <th>Field 3</th> 830 </tr> 831 </thead> 832 <tbody> 833 <tr> 834 <td>value 1</td> 835 <td>value2</td> 836 <td>value3</td> 837 </tr> 838 <tr> 839 <td>value 4</td> 840 <td>value5</td> 841 <td>value6</td> 842 </tr> 843 <tr> 844 <td>value 7</td> 845 <td>value8</td> 846 <td>value9</td> 847 </tr> 848 </tbody> 849</table> 850""".strip() 851 ) 852 853 def test_HtmlOutputFormatted(self): 854 t = helper_table() 855 result = t.get_html_string(format=True) 856 assert ( 857 result.strip() 858 == """ 859<table frame="box" rules="cols"> 860 <thead> 861 <tr> 862 <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> 863 <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> 864 <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> 865 </tr> 866 </thead> 867 <tbody> 868 <tr> 869 <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td> 870 <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td> 871 <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td> 872 </tr> 873 <tr> 874 <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td> 875 <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td> 876 <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td> 877 </tr> 878 <tr> 879 <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td> 880 <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td> 881 <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td> 882 </tr> 883 </tbody> 884</table> 885""".strip() # noqa: E501 886 ) 887 888 889class TestPositionalJunctions: 890 """Verify different cases for positional-junction characters""" 891 892 def test_Default(self, city_data_prettytable: PrettyTable): 893 city_data_prettytable.set_style(DOUBLE_BORDER) 894 895 assert ( 896 city_data_prettytable.get_string().strip() 897 == """ 898╔═══════════╦══════╦════════════╦═════════════════╗ 899║ City name ║ Area ║ Population ║ Annual Rainfall ║ 900╠═══════════╬══════╬════════════╬═════════════════╣ 901║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ 902║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ 903║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ 904║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ 905║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ 906║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ 907║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ 908╚═══════════╩══════╩════════════╩═════════════════╝""".strip() 909 ) 910 911 def test_NoHeader(self, city_data_prettytable: PrettyTable): 912 city_data_prettytable.set_style(DOUBLE_BORDER) 913 city_data_prettytable.header = False 914 915 assert ( 916 city_data_prettytable.get_string().strip() 917 == """ 918╔═══════════╦══════╦═════════╦════════╗ 919║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ 920║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ 921║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ 922║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ 923║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ 924║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ 925║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ 926╚═══════════╩══════╩═════════╩════════╝""".strip() 927 ) 928 929 def test_WithTitle(self, city_data_prettytable: PrettyTable): 930 city_data_prettytable.set_style(DOUBLE_BORDER) 931 city_data_prettytable.title = "Title" 932 933 assert ( 934 city_data_prettytable.get_string().strip() 935 == """ 936╔═════════════════════════════════════════════════╗ 937║ Title ║ 938╠═══════════╦══════╦════════════╦═════════════════╣ 939║ City name ║ Area ║ Population ║ Annual Rainfall ║ 940╠═══════════╬══════╬════════════╬═════════════════╣ 941║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ 942║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ 943║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ 944║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ 945║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ 946║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ 947║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ 948╚═══════════╩══════╩════════════╩═════════════════╝""".strip() 949 ) 950 951 def test_WithTitleNoHeader(self, city_data_prettytable: PrettyTable): 952 city_data_prettytable.set_style(DOUBLE_BORDER) 953 city_data_prettytable.title = "Title" 954 city_data_prettytable.header = False 955 assert ( 956 city_data_prettytable.get_string().strip() 957 == """ 958╔═════════════════════════════════════╗ 959║ Title ║ 960╠═══════════╦══════╦═════════╦════════╣ 961║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ 962║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ 963║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ 964║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ 965║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ 966║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ 967║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ 968╚═══════════╩══════╩═════════╩════════╝""".strip() 969 ) 970 971 def test_HruleAll(self, city_data_prettytable: PrettyTable): 972 city_data_prettytable.set_style(DOUBLE_BORDER) 973 city_data_prettytable.title = "Title" 974 city_data_prettytable.hrules = ALL 975 assert ( 976 city_data_prettytable.get_string().strip() 977 == """ 978╔═════════════════════════════════════════════════╗ 979║ Title ║ 980╠═══════════╦══════╦════════════╦═════════════════╣ 981║ City name ║ Area ║ Population ║ Annual Rainfall ║ 982╠═══════════╬══════╬════════════╬═════════════════╣ 983║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ 984╠═══════════╬══════╬════════════╬═════════════════╣ 985║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ 986╠═══════════╬══════╬════════════╬═════════════════╣ 987║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ 988╠═══════════╬══════╬════════════╬═════════════════╣ 989║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ 990╠═══════════╬══════╬════════════╬═════════════════╣ 991║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ 992╠═══════════╬══════╬════════════╬═════════════════╣ 993║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ 994╠═══════════╬══════╬════════════╬═════════════════╣ 995║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ 996╚═══════════╩══════╩════════════╩═════════════════╝""".strip() 997 ) 998 999 def test_VrulesNone(self, city_data_prettytable: PrettyTable): 1000 city_data_prettytable.set_style(DOUBLE_BORDER) 1001 city_data_prettytable.vrules = NONE 1002 assert ( 1003 city_data_prettytable.get_string().strip() 1004 == "═══════════════════════════════════════════════════\n" 1005 " City name Area Population Annual Rainfall \n" 1006 "═══════════════════════════════════════════════════\n" 1007 " Adelaide 1295 1158259 600.5 \n" 1008 " Brisbane 5905 1857594 1146.4 \n" 1009 " Darwin 112 120900 1714.7 \n" 1010 " Hobart 1357 205556 619.5 \n" 1011 " Sydney 2058 4336374 1214.8 \n" 1012 " Melbourne 1566 3806092 646.9 \n" 1013 " Perth 5386 1554769 869.4 \n" 1014 "═══════════════════════════════════════════════════".strip() 1015 ) 1016 1017 def test_VrulesFrameWithTitle(self, city_data_prettytable: PrettyTable): 1018 city_data_prettytable.set_style(DOUBLE_BORDER) 1019 city_data_prettytable.vrules = FRAME 1020 city_data_prettytable.title = "Title" 1021 assert ( 1022 city_data_prettytable.get_string().strip() 1023 == """ 1024╔═════════════════════════════════════════════════╗ 1025║ Title ║ 1026╠═════════════════════════════════════════════════╣ 1027║ City name Area Population Annual Rainfall ║ 1028╠═════════════════════════════════════════════════╣ 1029║ Adelaide 1295 1158259 600.5 ║ 1030║ Brisbane 5905 1857594 1146.4 ║ 1031║ Darwin 112 120900 1714.7 ║ 1032║ Hobart 1357 205556 619.5 ║ 1033║ Sydney 2058 4336374 1214.8 ║ 1034║ Melbourne 1566 3806092 646.9 ║ 1035║ Perth 5386 1554769 869.4 ║ 1036╚═════════════════════════════════════════════════╝""".strip() 1037 ) 1038 1039 1040class TestStyle: 1041 @pytest.mark.parametrize( 1042 "style, expected", 1043 [ 1044 pytest.param( 1045 DEFAULT, 1046 """ 1047+---------+---------+---------+ 1048| Field 1 | Field 2 | Field 3 | 1049+---------+---------+---------+ 1050| value 1 | value2 | value3 | 1051| value 4 | value5 | value6 | 1052| value 7 | value8 | value9 | 1053+---------+---------+---------+ 1054""", 1055 id="DEFAULT", 1056 ), 1057 pytest.param( 1058 MARKDOWN, 1059 """ 1060| Field 1 | Field 2 | Field 3 | 1061|:-------:|:-------:|:-------:| 1062| value 1 | value2 | value3 | 1063| value 4 | value5 | value6 | 1064| value 7 | value8 | value9 | 1065""", 1066 id="MARKDOWN", 1067 ), 1068 pytest.param( 1069 MSWORD_FRIENDLY, 1070 """ 1071| Field 1 | Field 2 | Field 3 | 1072| value 1 | value2 | value3 | 1073| value 4 | value5 | value6 | 1074| value 7 | value8 | value9 | 1075""", 1076 id="MSWORD_FRIENDLY", 1077 ), 1078 pytest.param( 1079 ORGMODE, 1080 """ 1081|---------+---------+---------| 1082| Field 1 | Field 2 | Field 3 | 1083|---------+---------+---------| 1084| value 1 | value2 | value3 | 1085| value 4 | value5 | value6 | 1086| value 7 | value8 | value9 | 1087|---------+---------+---------| 1088""", 1089 id="ORGMODE", 1090 ), 1091 pytest.param( 1092 PLAIN_COLUMNS, 1093 """ 1094Field 1 Field 2 Field 3 1095value 1 value2 value3 1096value 4 value5 value6 1097value 7 value8 value9 1098""", # noqa: W291 1099 id="PLAIN_COLUMNS", 1100 ), 1101 pytest.param( 1102 RANDOM, 1103 """ 1104'^^^^^^^^^^^'^^^^^^^^^^'^^^^^^^^^^' 1105% value 1% value2% value3% 1106'^^^^^^^^^^^'^^^^^^^^^^'^^^^^^^^^^' 1107% value 4% value5% value6% 1108'^^^^^^^^^^^'^^^^^^^^^^'^^^^^^^^^^' 1109% value 7% value8% value9% 1110'^^^^^^^^^^^'^^^^^^^^^^'^^^^^^^^^^' 1111""", 1112 id="RANDOM", 1113 ), 1114 pytest.param( 1115 DOUBLE_BORDER, 1116 """ 1117╔═════════╦═════════╦═════════╗ 1118║ Field 1 ║ Field 2 ║ Field 3 ║ 1119╠═════════╬═════════╬═════════╣ 1120║ value 1 ║ value2 ║ value3 ║ 1121║ value 4 ║ value5 ║ value6 ║ 1122║ value 7 ║ value8 ║ value9 ║ 1123╚═════════╩═════════╩═════════╝ 1124""", 1125 ), 1126 ], 1127 ) 1128 def test_style(self, style, expected): 1129 # Arrange 1130 t = helper_table() 1131 random.seed(1234) 1132 1133 # Act 1134 t.set_style(style) 1135 1136 # Assert 1137 result = t.get_string() 1138 assert result.strip() == expected.strip() 1139 1140 def test_style_invalid(self): 1141 # Arrange 1142 t = helper_table() 1143 1144 # Act / Assert 1145 # This is an hrule style, not a table style 1146 with pytest.raises(Exception): 1147 t.set_style(ALL) 1148 1149 @pytest.mark.parametrize( 1150 "style, expected", 1151 [ 1152 pytest.param( 1153 MARKDOWN, 1154 """ 1155| Align left | Align centre | Align right | 1156|:-----------|:------------:|------------:| 1157| value 1 | value2 | value3 | 1158| value 4 | value5 | value6 | 1159| value 7 | value8 | value9 | 1160""", 1161 id="MARKDOWN", 1162 ), 1163 ], 1164 ) 1165 def test_style_align(self, style, expected): 1166 # Arrange 1167 t = helper_table() 1168 t.field_names = ["Align left", "Align centre", "Align right"] 1169 1170 # Act 1171 t.set_style(style) 1172 t.align["Align left"] = "l" 1173 t.align["Align centre"] = "c" 1174 t.align["Align right"] = "r" 1175 1176 # Assert 1177 result = t.get_string() 1178 assert result.strip() == expected.strip() 1179 1180 1181class TestCsvOutput: 1182 def test_csv_output(self): 1183 t = helper_table() 1184 assert t.get_csv_string(delimiter="\t", header=False) == ( 1185 "value 1\tvalue2\tvalue3\r\n" 1186 "value 4\tvalue5\tvalue6\r\n" 1187 "value 7\tvalue8\tvalue9\r\n" 1188 ) 1189 assert t.get_csv_string() == ( 1190 "Field 1,Field 2,Field 3\r\n" 1191 "value 1,value2,value3\r\n" 1192 "value 4,value5,value6\r\n" 1193 "value 7,value8,value9\r\n" 1194 ) 1195 1196 1197class TestLatexOutput: 1198 def testLatexOutput(self): 1199 t = helper_table() 1200 assert t.get_latex_string() == ( 1201 "\\begin{tabular}{ccc}\r\n" 1202 "Field 1 & Field 2 & Field 3 \\\\\r\n" 1203 "value 1 & value2 & value3 \\\\\r\n" 1204 "value 4 & value5 & value6 \\\\\r\n" 1205 "value 7 & value8 & value9 \\\\\r\n" 1206 "\\end{tabular}" 1207 ) 1208 options = {"fields": ["Field 1", "Field 3"]} 1209 assert t.get_latex_string(**options) == ( 1210 "\\begin{tabular}{cc}\r\n" 1211 "Field 1 & Field 3 \\\\\r\n" 1212 "value 1 & value3 \\\\\r\n" 1213 "value 4 & value6 \\\\\r\n" 1214 "value 7 & value9 \\\\\r\n" 1215 "\\end{tabular}" 1216 ) 1217 1218 def testLatexOutputFormatted(self): 1219 t = helper_table() 1220 assert t.get_latex_string(format=True) == ( 1221 "\\begin{tabular}{|c|c|c|}\r\n" 1222 "\\hline\r\n" 1223 "Field 1 & Field 2 & Field 3 \\\\\r\n" 1224 "value 1 & value2 & value3 \\\\\r\n" 1225 "value 4 & value5 & value6 \\\\\r\n" 1226 "value 7 & value8 & value9 \\\\\r\n" 1227 "\\hline\r\n" 1228 "\\end{tabular}" 1229 ) 1230 1231 options = {"fields": ["Field 1", "Field 3"]} 1232 assert t.get_latex_string(format=True, **options) == ( 1233 "\\begin{tabular}{|c|c|}\r\n" 1234 "\\hline\r\n" 1235 "Field 1 & Field 3 \\\\\r\n" 1236 "value 1 & value3 \\\\\r\n" 1237 "value 4 & value6 \\\\\r\n" 1238 "value 7 & value9 \\\\\r\n" 1239 "\\hline\r\n" 1240 "\\end{tabular}" 1241 ) 1242 1243 options = {"vrules": FRAME} 1244 assert t.get_latex_string(format=True, **options) == ( 1245 "\\begin{tabular}{|ccc|}\r\n" 1246 "\\hline\r\n" 1247 "Field 1 & Field 2 & Field 3 \\\\\r\n" 1248 "value 1 & value2 & value3 \\\\\r\n" 1249 "value 4 & value5 & value6 \\\\\r\n" 1250 "value 7 & value8 & value9 \\\\\r\n" 1251 "\\hline\r\n" 1252 "\\end{tabular}" 1253 ) 1254 1255 options = {"hrules": ALL} 1256 assert t.get_latex_string(format=True, **options) == ( 1257 "\\begin{tabular}{|c|c|c|}\r\n" 1258 "\\hline\r\n" 1259 "Field 1 & Field 2 & Field 3 \\\\\r\n" 1260 "\\hline\r\n" 1261 "value 1 & value2 & value3 \\\\\r\n" 1262 "\\hline\r\n" 1263 "value 4 & value5 & value6 \\\\\r\n" 1264 "\\hline\r\n" 1265 "value 7 & value8 & value9 \\\\\r\n" 1266 "\\hline\r\n" 1267 "\\end{tabular}" 1268 ) 1269 1270 def testLatexOutputHeader(self): 1271 t = helper_table() 1272 assert t.get_latex_string(format=True, hrules=HEADER) == ( 1273 "\\begin{tabular}{|c|c|c|}\r\n" 1274 "Field 1 & Field 2 & Field 3 \\\\\r\n" 1275 "\\hline\r\n" 1276 "value 1 & value2 & value3 \\\\\r\n" 1277 "value 4 & value5 & value6 \\\\\r\n" 1278 "value 7 & value8 & value9 \\\\\r\n" 1279 "\\end{tabular}" 1280 ) 1281 1282 1283class TestJSONConstructor: 1284 def test_JSONAndBack(self, city_data_prettytable: PrettyTable): 1285 json_string = city_data_prettytable.get_json_string() 1286 new_table = from_json(json_string) 1287 assert new_table.get_string() == city_data_prettytable.get_string() 1288 1289 1290class TestHtmlConstructor: 1291 def test_HtmlAndBack(self, city_data_prettytable: PrettyTable): 1292 html_string = city_data_prettytable.get_html_string() 1293 new_table = from_html(html_string)[0] 1294 assert new_table.get_string() == city_data_prettytable.get_string() 1295 1296 def test_HtmlOneAndBack(self, city_data_prettytable: PrettyTable): 1297 html_string = city_data_prettytable.get_html_string() 1298 new_table = from_html_one(html_string) 1299 assert new_table.get_string() == city_data_prettytable.get_string() 1300 1301 def test_HtmlOneFailOnMany(self, city_data_prettytable: PrettyTable): 1302 html_string = city_data_prettytable.get_html_string() 1303 html_string += city_data_prettytable.get_html_string() 1304 with pytest.raises(Exception): 1305 from_html_one(html_string) 1306 1307 1308@pytest.fixture 1309def japanese_pretty_table(): 1310 pt = PrettyTable(["Kanji", "Hiragana", "English"]) 1311 pt.add_row(["神戸", "こうべ", "Kobe"]) 1312 pt.add_row(["京都", "きょうと", "Kyoto"]) 1313 pt.add_row(["長崎", "ながさき", "Nagasaki"]) 1314 pt.add_row(["名古屋", "なごや", "Nagoya"]) 1315 pt.add_row(["大阪", "おおさか", "Osaka"]) 1316 pt.add_row(["札幌", "さっぽろ", "Sapporo"]) 1317 pt.add_row(["東京", "とうきょう", "Tokyo"]) 1318 pt.add_row(["横浜", "よこはま", "Yokohama"]) 1319 return pt 1320 1321 1322@pytest.fixture 1323def emoji_pretty_table(): 1324 thunder1 = [ 1325 '\033[38;5;226m _`/""\033[38;5;250m.-. \033[0m', 1326 "\033[38;5;226m ,\\_\033[38;5;250m( ). \033[0m", 1327 "\033[38;5;226m /\033[38;5;250m(___(__) \033[0m", 1328 "\033[38;5;228;5m ⚡\033[38;5;111;25mʻ ʻ\033[38;5;228;5m" 1329 "⚡\033[38;5;111;25mʻ ʻ \033[0m", 1330 "\033[38;5;111m ʻ ʻ ʻ ʻ \033[0m", 1331 ] 1332 thunder2 = [ 1333 "\033[38;5;240;1m .-. \033[0m", 1334 "\033[38;5;240;1m ( ). \033[0m", 1335 "\033[38;5;240;1m (___(__) \033[0m", 1336 "\033[38;5;21;1m ‚ʻ\033[38;5;228;5m⚡\033[38;5;21;25mʻ‚\033[38;5;228;5m" 1337 "⚡\033[38;5;21;25m‚ʻ \033[0m", 1338 "\033[38;5;21;1m ‚ʻ‚ʻ\033[38;5;228;5m⚡\033[38;5;21;25mʻ‚ʻ \033[0m", 1339 ] 1340 pt = PrettyTable(["Thunderbolt", "Lightning"]) 1341 for i in range(len(thunder1)): 1342 pt.add_row([thunder1[i], thunder2[i]]) 1343 return pt 1344 1345 1346class TestMultiPattern: 1347 @pytest.mark.parametrize( 1348 ["pt", "expected_output", "test_type"], 1349 [ 1350 ( 1351 pytest.lazy_fixture("city_data_prettytable"), 1352 """ 1353+-----------+------+------------+-----------------+ 1354| City name | Area | Population | Annual Rainfall | 1355+-----------+------+------------+-----------------+ 1356| Adelaide | 1295 | 1158259 | 600.5 | 1357| Brisbane | 5905 | 1857594 | 1146.4 | 1358| Darwin | 112 | 120900 | 1714.7 | 1359| Hobart | 1357 | 205556 | 619.5 | 1360| Sydney | 2058 | 4336374 | 1214.8 | 1361| Melbourne | 1566 | 3806092 | 646.9 | 1362| Perth | 5386 | 1554769 | 869.4 | 1363+-----------+------+------------+-----------------+ 1364""", 1365 "English Table", 1366 ), 1367 ( 1368 pytest.lazy_fixture("japanese_pretty_table"), 1369 """ 1370+--------+------------+----------+ 1371| Kanji | Hiragana | English | 1372+--------+------------+----------+ 1373| 神戸 | こうべ | Kobe | 1374| 京都 | きょうと | Kyoto | 1375| 長崎 | ながさき | Nagasaki | 1376| 名古屋 | なごや | Nagoya | 1377| 大阪 | おおさか | Osaka | 1378| 札幌 | さっぽろ | Sapporo | 1379| 東京 | とうきょう | Tokyo | 1380| 横浜 | よこはま | Yokohama | 1381+--------+------------+----------+ 1382 1383""", 1384 "Japanese table", 1385 ), 1386 ( 1387 pytest.lazy_fixture("emoji_pretty_table"), 1388 """ 1389+-----------------+-----------------+ 1390| Thunderbolt | Lightning | 1391+-----------------+-----------------+ 1392| \x1b[38;5;226m _`/""\x1b[38;5;250m.-. \x1b[0m | \x1b[38;5;240;1m .-. \x1b[0m | 1393| \x1b[38;5;226m ,\\_\x1b[38;5;250m( ). \x1b[0m | \x1b[38;5;240;1m ( ). \x1b[0m | 1394| \x1b[38;5;226m /\x1b[38;5;250m(___(__) \x1b[0m | \x1b[38;5;240;1m (___(__) \x1b[0m | 1395| \x1b[38;5;228;5m ⚡\x1b[38;5;111;25mʻ ʻ\x1b[38;5;228;5m⚡\x1b[38;5;111;25mʻ ʻ \x1b[0m | \x1b[38;5;21;1m ‚ʻ\x1b[38;5;228;5m⚡\x1b[38;5;21;25mʻ‚\x1b[38;5;228;5m⚡\x1b[38;5;21;25m‚ʻ \x1b[0m | 1396| \x1b[38;5;111m ʻ ʻ ʻ ʻ \x1b[0m | \x1b[38;5;21;1m ‚ʻ‚ʻ\x1b[38;5;228;5m⚡\x1b[38;5;21;25mʻ‚ʻ \x1b[0m | 1397+-----------------+-----------------+ 1398 """, # noqa: E501 1399 "Emoji table", 1400 ), 1401 ], 1402 ) 1403 def test_multi_pattern_outputs( 1404 self, pt: PrettyTable, expected_output: str, test_type: str 1405 ): 1406 printed_table = pt.get_string() 1407 assert ( 1408 printed_table.strip() == expected_output.strip() 1409 ), f"Error output for test output of type {test_type}" 1410 1411 1412def test_paginate(): 1413 # Arrange 1414 t = helper_table(rows=7) 1415 expected_page_1 = """ 1416+----------+---------+---------+ 1417| Field 1 | Field 2 | Field 3 | 1418+----------+---------+---------+ 1419| value 1 | value2 | value3 | 1420| value 4 | value5 | value6 | 1421| value 7 | value8 | value9 | 1422| value 10 | value11 | value12 | 1423+----------+---------+---------+ 1424 """.strip() 1425 expected_page_2 = """ 1426+----------+---------+---------+ 1427| Field 1 | Field 2 | Field 3 | 1428+----------+---------+---------+ 1429| value 13 | value14 | value15 | 1430| value 16 | value17 | value18 | 1431| value 19 | value20 | value21 | 1432+----------+---------+---------+ 1433""".strip() 1434 1435 # Act 1436 paginated = t.paginate(page_length=4) 1437 1438 # Assert 1439 paginated = paginated.strip() 1440 assert paginated.startswith(expected_page_1) 1441 assert "\f" in paginated 1442 assert paginated.endswith(expected_page_2) 1443 1444 1445def test_add_rows(): 1446 """A table created with multiple add_row calls 1447 is the same as one created with a single add_rows 1448 """ 1449 # Arrange 1450 t1 = PrettyTable(["A", "B", "C"]) 1451 t2 = PrettyTable(["A", "B", "C"]) 1452 t1.add_row([1, 2, 3]) 1453 t1.add_row([4, 5, 6]) 1454 rows = [ 1455 [1, 2, 3], 1456 [4, 5, 6], 1457 ] 1458 1459 # Act 1460 t2.add_rows(rows) 1461 1462 # Assert 1463 assert str(t1) == str(t2) 1464 1465 1466def test_autoindex(): 1467 1468 """Testing that a table with a custom index row is 1469 equal to the one produced by the function 1470 .add_autoindex() 1471 """ 1472 table1 = PrettyTable() 1473 table1.field_names = ["City name", "Area", "Population", "Annual Rainfall"] 1474 table1.add_row(["Adelaide", 1295, 1158259, 600.5]) 1475 table1.add_row(["Brisbane", 5905, 1857594, 1146.4]) 1476 table1.add_row(["Darwin", 112, 120900, 1714.7]) 1477 table1.add_row(["Hobart", 1357, 205556, 619.5]) 1478 table1.add_row(["Sydney", 2058, 4336374, 1214.8]) 1479 table1.add_row(["Melbourne", 1566, 3806092, 646.9]) 1480 table1.add_row(["Perth", 5386, 1554769, 869.4]) 1481 table1.add_autoindex(fieldname="Test") 1482 1483 table2 = PrettyTable() 1484 table2.field_names = ["Test", "City name", "Area", "Population", "Annual Rainfall"] 1485 table2.add_row([1, "Adelaide", 1295, 1158259, 600.5]) 1486 table2.add_row([2, "Brisbane", 5905, 1857594, 1146.4]) 1487 table2.add_row([3, "Darwin", 112, 120900, 1714.7]) 1488 table2.add_row([4, "Hobart", 1357, 205556, 619.5]) 1489 table2.add_row([5, "Sydney", 2058, 4336374, 1214.8]) 1490 table2.add_row([6, "Melbourne", 1566, 3806092, 646.9]) 1491 table2.add_row([7, "Perth", 5386, 1554769, 869.4]) 1492 1493 assert str(table1) == str(table2) 1494 1495 1496@pytest.fixture(scope="function") 1497def unpadded_pt(): 1498 pt = PrettyTable(header=False, padding_width=0) 1499 pt.add_row("abc") 1500 pt.add_row("def") 1501 pt.add_row("g..") 1502 return pt 1503 1504 1505class TestUnpaddedTable: 1506 def test_unbordered(self, unpadded_pt: PrettyTable): 1507 unpadded_pt.border = False 1508 result = unpadded_pt.get_string() 1509 expected = """ 1510abc 1511def 1512g.. 1513""" 1514 assert result.strip() == expected.strip() 1515 1516 def test_bordered(self, unpadded_pt: PrettyTable): 1517 unpadded_pt.border = True 1518 result = unpadded_pt.get_string() 1519 expected = """ 1520+-+-+-+ 1521|a|b|c| 1522|d|e|f| 1523|g|.|.| 1524+-+-+-+ 1525""" 1526 assert result.strip() == expected.strip() 1527 1528 1529class TestCustomFormatter: 1530 def test_init_custom_format_is_empty(self): 1531 pt = PrettyTable() 1532 assert pt.custom_format == {} 1533 1534 def test_init_custom_format_set_value(self): 1535 pt = PrettyTable( 1536 custom_format={"col1": (lambda col_name, value: f"{value:.2}")} 1537 ) 1538 assert len(pt.custom_format) == 1 1539 1540 def test_init_custom_format_throw_error_is_not_callable(self): 1541 with pytest.raises(Exception) as e: 1542 PrettyTable(custom_format={"col1": "{:.2}"}) 1543 1544 assert "Invalid value for custom_format.col1. Must be a function." in str( 1545 e.value 1546 ) 1547 1548 def test_can_set_custom_format_from_property_setter(self): 1549 pt = PrettyTable() 1550 pt.custom_format = {"col1": (lambda col_name, value: f"{value:.2}")} 1551 assert len(pt.custom_format) == 1 1552 1553 def test_set_custom_format_to_none_set_empty_dict(self): 1554 pt = PrettyTable() 1555 pt.custom_format = None 1556 assert len(pt.custom_format) == 0 1557 assert isinstance(pt.custom_format, dict) 1558 1559 def test_set_custom_format_invalid_type_throw_error(self): 1560 pt = PrettyTable() 1561 with pytest.raises(Exception) as e: 1562 pt.custom_format = "Some String" 1563 assert "The custom_format property need to be a dictionary or callable" in str( 1564 e.value 1565 ) 1566 1567 def test_use_custom_formatter_for_int(self, city_data_prettytable: PrettyTable): 1568 city_data_prettytable.custom_format["Annual Rainfall"] = lambda n, v: f"{v:.2f}" 1569 assert ( 1570 city_data_prettytable.get_string().strip() 1571 == """ 1572+-----------+------+------------+-----------------+ 1573| City name | Area | Population | Annual Rainfall | 1574+-----------+------+------------+-----------------+ 1575| Adelaide | 1295 | 1158259 | 600.50 | 1576| Brisbane | 5905 | 1857594 | 1146.40 | 1577| Darwin | 112 | 120900 | 1714.70 | 1578| Hobart | 1357 | 205556 | 619.50 | 1579| Sydney | 2058 | 4336374 | 1214.80 | 1580| Melbourne | 1566 | 3806092 | 646.90 | 1581| Perth | 5386 | 1554769 | 869.40 | 1582+-----------+------+------------+-----------------+ 1583""".strip() 1584 ) 1585 1586 def test_custom_format_multi_type(self): 1587 pt = PrettyTable(["col_date", "col_str", "col_float", "col_int"]) 1588 pt.add_row([dt.date(2021, 1, 1), "January", 12345.12345, 12345678]) 1589 pt.add_row([dt.date(2021, 2, 1), "February", 54321.12345, 87654321]) 1590 pt.custom_format["col_date"] = lambda f, v: v.strftime("%d %b %Y") 1591 pt.custom_format["col_float"] = lambda f, v: f"{v:.3f}" 1592 pt.custom_format["col_int"] = lambda f, v: f"{v:,}" 1593 assert ( 1594 pt.get_string().strip() 1595 == """ 1596+-------------+----------+-----------+------------+ 1597| col_date | col_str | col_float | col_int | 1598+-------------+----------+-----------+------------+ 1599| 01 Jan 2021 | January | 12345.123 | 12,345,678 | 1600| 01 Feb 2021 | February | 54321.123 | 87,654,321 | 1601+-------------+----------+-----------+------------+ 1602""".strip() 1603 ) 1604 1605 def test_custom_format_multi_type_using_on_function(self): 1606 pt = PrettyTable(["col_date", "col_str", "col_float", "col_int"]) 1607 pt.add_row([dt.date(2021, 1, 1), "January", 12345.12345, 12345678]) 1608 pt.add_row([dt.date(2021, 2, 1), "February", 54321.12345, 87654321]) 1609 1610 def my_format(col: str, value: Any) -> str: 1611 if col == "col_date": 1612 return value.strftime("%d %b %Y") 1613 if col == "col_float": 1614 return f"{value:.3f}" 1615 if col == "col_int": 1616 return f"{value:,}" 1617 return str(value) 1618 1619 pt.custom_format = my_format 1620 assert ( 1621 pt.get_string().strip() 1622 == """ 1623+-------------+----------+-----------+------------+ 1624| col_date | col_str | col_float | col_int | 1625+-------------+----------+-----------+------------+ 1626| 01 Jan 2021 | January | 12345.123 | 12,345,678 | 1627| 01 Feb 2021 | February | 54321.123 | 87,654,321 | 1628+-------------+----------+-----------+------------+ 1629""".strip() 1630 ) 1631 1632 1633class TestRepr: 1634 def test_default_repr(self, row_prettytable: PrettyTable): 1635 assert row_prettytable.__str__() == row_prettytable.__repr__() 1636 1637 def test_jupyter_repr(self, row_prettytable: PrettyTable): 1638 assert row_prettytable._repr_html_() == row_prettytable.get_html_string() 1639